MYSQL執行緒池總結(一)
執行緒池是Mysql5.6的一個核心功能,對於伺服器應用而言,無論是web應用服務還是DB服務,高併發請求始終是一個繞不開的話題。當有大量請求併發訪問時,一定伴隨著資源的不斷建立和釋放,導致資源利用率低,降低了服務質量。執行緒池是一種通用的技術,通過預先建立一定數量的執行緒,當有請求達到時,執行緒池分配一個執行緒提供服務,請求結束後,該執行緒又去服務其他請求。 通過這種方式,避免了執行緒和記憶體物件的頻繁建立和釋放,降低了服務端的併發度,減少了上下文切換和資源的競爭,提高資源利用效率。所有服務的執行緒池本質都是位了提高資源利用效率,並且實現方式也大體相同。本文主要說明MySQL執行緒池的實現原理。
在Mysql5.6出現以前,Mysql處理連線的方式是One-Connection-Per-Thread,即對於每一個資料庫連線,Mysql-Server都會建立一個獨立的執行緒服務,請求結束後,銷燬執行緒。再來一個連線請求,則再建立一個連線,結束後再進行銷燬。這種方式在高併發情況下,會導致執行緒的頻繁建立和釋放。當然,通過thread-cache,我們可以將執行緒快取起來,以供下次使用,避免頻繁建立和釋放的問題,但是無法解決高連線數的問題。One-Connection-Per-Thread方式隨著連線數暴增,導致需要建立同樣多的服務執行緒,高併發執行緒意味著高的記憶體消耗,更多的上下文切換(cpu cache命中率降低)以及更多的資源競爭,導致服務出現抖動。相對於One-Thread-Per-Connection方式,一個執行緒對應一個連線,Thread-Pool實現方式中,執行緒處理的最小單位是statement(語句),一個執行緒可以處理多個連線的請求。這樣,在保證充分利用硬體資源情況下(合理設定執行緒池大小),可以避免瞬間連線數暴增導致的伺服器抖動。
排程方式實現
Mysql-Server同時支援3種連線管理方式,包括No-Threads,One-Thread-Per-Connection和Pool-Threads。No-Threads表示處理連線使用主執行緒處理,不額外建立執行緒,這種方式主要用於除錯;One-Thread-Per-Connection是執行緒池出現以前最常用的方式,為每一個連線建立一個執行緒服務;Pool-Threads則是本文所討論的執行緒池方式。Mysql-Server通過一組函式指標來同時支援3種連線管理方式,對於特定的方式,將函式指標設定成特定的回撥函式,連線管理方式通過thread_handling引數控制,程式碼如下:
if (thread_handling <= SCHEDULER_ONE_THREAD_PER_CONNECTION) one_thread_per_connection_scheduler(thread_scheduler, &max_connections, &connection_count); else if (thread_handling == SCHEDULER_NO_THREADS) one_thread_scheduler(thread_scheduler); else pool_of_threads_scheduler(thread_scheduler, &max_connections,&connection_count);
連線管理流程
- 通過poll監聽mysql埠的連線請求
- 收到連線後,呼叫accept介面,建立通訊socket
- 初始化thd例項,vio物件等
- 根據thread_handling方式設定,初始化thd例項的scheduler函式指標
- 呼叫scheduler特定的add_connection函式新建連線
下面程式碼展示了scheduler_functions模板和執行緒池對模板回撥函式的實現,這個是多種連線管理的核心。
struct scheduler_functions { uint max_threads; uint *connection_count; ulong *max_connections; bool (*init)(void); bool (*init_new_connection_thread)(void); void (*add_connection)(THD *thd); void (*thd_wait_begin)(THD *thd, int wait_type); void (*thd_wait_end)(THD *thd); void (*post_kill_notification)(THD *thd); bool (*end_thread)(THD *thd, bool cache_thread); void (*end)(void); };
static scheduler_functions tp_scheduler_functions= { 0, // max_threads NULL, NULL, tp_init, // init NULL, // init_new_connection_thread tp_add_connection, // add_connection tp_wait_begin, // thd_wait_begin tp_wait_end, // thd_wait_end tp_post_kill_notification, // post_kill_notification NULL, // end_thread tp_end // end };
執行緒池的相關引數
- thread_handling:表示執行緒池模型。
- thread_pool_size:表示執行緒池的group個數,一般設定為當前CPU核心數目。理想情況下,一個group一個活躍的工作執行緒,達到充分利用CPU的目的。
- thread_pool_stall_limit:用於timer執行緒定期檢查group是否“停滯”,參數列示檢測的間隔。
- thread_pool_idle_timeout:當一個worker空閒一段時間後會自動退出,保證執行緒池中的工作執行緒在滿足請求的情況下,保持比較低的水平。
- thread_pool_oversubscribe:該引數用於控制CPU核心上“超頻”的執行緒數。這個引數設定值不含listen執行緒計數。
- threadpool_high_prio_mode:表示優先佇列的模式。
執行緒池實現
上面描述了Mysql-Server如何管理連線,這節重點描述執行緒池的實現框架,以及關鍵介面。如圖1
圖 1(執行緒池框架圖)
每一個綠色的方框代表一個group,group數目由thread_pool_size引數決定。每個group包含一個優先佇列和普通佇列,包含一個listener執行緒和若干個工作執行緒,listener執行緒和worker執行緒可以動態轉換,worker執行緒數目由工作負載決定,同時受到thread_pool_oversubscribe設定影響。此外,整個執行緒池有一個timer執行緒監控group,防止group“停滯”。
關鍵介面
1. tp_add_connection[處理新連線]
1) 建立一個connection物件
2) 根據thread_id%group_count確定connection分配到哪個group
3) 將connection放進對應group的佇列
4) 如果當前活躍執行緒數為0,則建立一個工作執行緒
2. worker_main[工作執行緒]
1) 呼叫get_event獲取請求
2) 如果存在請求,則呼叫handle_event進行處理
3) 否則,表示佇列中已經沒有請求,退出結束。
3. get_event[獲取請求]
1) 獲取一個連線請求
2) 如果存在,則立即返回,結束
3) 若此時group內沒有listener,則執行緒轉換為listener執行緒,阻塞等待
4) 若存在listener,則將執行緒加入等待佇列頭部
5) 執行緒休眠指定的時間(thread_pool_idle_timeout)
6) 如果依然沒有被喚醒,是超時,則執行緒結束,結束退出
7) 否則,表示佇列裡有連線請求到來,跳轉1
備註:獲取連線請求前,會判斷當前的活躍執行緒數是否超過了
thread_pool_oversubscribe+1,若超過了,則將執行緒進入休眠狀態。
4. handle_event[處理請求]
1) 判斷連線是否進行登入驗證,若沒有,則進行登入驗證
2) 關聯thd例項資訊
3) 獲取網路資料包,分析請求
4) 呼叫do_command函式迴圈處理請求
5) 獲取thd例項的套接字控制程式碼,判斷控制程式碼是否在epoll的監聽列表中
6) 若沒有,呼叫epoll_ctl進行關聯
7) 結束
5.listener[監聽執行緒]
1) 呼叫epoll_wait進行對group關聯的套接字監聽,阻塞等待
2) 若請求到來,從阻塞中恢復
3) 根據連線的優先順序別,確定是放入普通佇列還是優先佇列
4) 判斷佇列中任務是否為空
5) 若佇列為空,則listener轉換為worker執行緒
6) 若group內沒有活躍執行緒,則喚醒一個執行緒
備註:這裡epoll_wait監聽group內所有連線的套接字,然後將監聽到的連線
請求push到佇列,worker執行緒從佇列中獲取任務,然後執行。
6. timer_thread[監控執行緒]
1) 若沒有listener執行緒,並且最近沒有io_event事件
2) 則建立一個喚醒或建立一個工作執行緒
3) 若group最近一段時間沒有處理請求,並且佇列裡面有請求,則
4) 表示group已經stall,則喚醒或建立執行緒
5)檢查是否有連線超時
備註:timer執行緒通過呼叫check_stall判斷group是否處於stall狀態,通過呼叫timeout_check檢查客戶端連線是否超時。
7.tp_wait_begin[進入等待狀態流程]
1) active_thread_count減1,waiting_thread_count加1
2)設定connection->waiting= true
3) 若活躍執行緒數為0,並且任務佇列不為空,或者沒有監聽執行緒,則
4) 喚醒或建立一個執行緒
8.tp_wait_end[結束等待狀態流程]
1) 設定connection的waiting狀態為false
2) active_thread_count加1,waiting_thread_count減1
備註:
1)waiting_threads這個list裡面的執行緒是空閒執行緒,並非等待執行緒,所謂空閒執行緒是隨時可以處理任務的執行緒,而等待執行緒則是因為等待鎖,或等待io操作等無法處理任務的執行緒。
2)tp_wait_begin和tp_wait_end的主要作用是由於彙報狀態,即使更新active_thread_count和waiting_thread_count的資訊。
9. tp_init/tp_end
分別呼叫thread_group_init和thread_group_close來初始化和銷燬執行緒池
執行緒池與連線池
連線池通常實現在Client端,是指應用(客戶端)建立預先建立一定的連線,利用這些連線服務於客戶端所有的DB請求。如果某一個時刻,空閒的連線數小於DB的請求數,則需要將請求排隊,等待空閒連線處理。通過連線池可以複用連線,避免連線的頻繁建立和釋放,從而減少請求的平均響應時間,並且在請求繁忙時,通過請求排隊,可以緩衝應用對DB的衝擊。執行緒池實現在server端,通過建立一定數量的執行緒服務DB請求,相對於one-conection-per-thread的一個執行緒服務一個連線的方式,執行緒池服務的最小單位是語句,即一個執行緒可以對應多個活躍的連線。通過執行緒池,可以將server端的服務執行緒數控制在一定的範圍,減少了系統資源的競爭和執行緒上下文切換帶來的消耗,同時也避免出現高連線數導致的高併發問題。連線池和執行緒池相輔相成,通過連線池可以減少連線的建立和釋放,提高請求的平均響應時間,並能很好地控制一個應用的DB連線數,但無法控制整個應用叢集的連線數規模,從而導致高連線數,通過執行緒池則可以很好地應對高連線數,保證server端能提供穩定的服務。如圖2所示,每個web-server端維護了3個連線的連線池,對於連線池的每個連線實際不是獨佔db-server的一個worker,而是可能與其他連線共享。這裡假設db-server只有3個group,每個group只有一個worker,每個worker處理了2個連線的請求。
圖 2(連線池與執行緒池框架圖)
執行緒池優化
1.排程死鎖解決
引入執行緒池解決了多執行緒高併發的問題,但也帶來一個隱患。假設,A,B兩個事務被分配到不同的group中執行,A事務已經開始,並且持有鎖,但由於A所在的group比較繁忙,導致A執行一條語句後,不能立即獲得排程執行;而B事務依賴A事務釋放鎖資源,雖然B事務可以被排程起來,但由於無法獲得鎖資源,導致仍然需要等待,這就是所謂的排程死鎖。由於一個group會同時處理多個連線,但多個連線不是對等的。比如,有的連線是第一次傳送請求;而有的連線對應的事務已經開啟,並且持有了部分鎖資源。為了減少鎖資源爭用,後者顯然應該比前者優先處理,以達到儘早釋放鎖資源的目的。因此在group裡面,可以新增一個優先順序佇列,將已經持有鎖的連線,或者已經開啟的事務的連線發起的請求放入優先佇列,工作執行緒首先從優先佇列獲取任務執行。
2.大查詢處理
假設一種場景,某個group裡面的連線都是大查詢,那麼group裡面的工作執行緒數很快就會達到thread_pool_oversubscribe引數設定值,對於後續的連線請求,則會響應不及時(沒有更多的連線來處理),這時候group就發生了stall。通過前面分析知道,timer執行緒會定期檢查這種情況,並建立一個新的worker執行緒來處理請求。如果長查詢來源於業務請求,則此時所有group都面臨這種問題,此時主機可能會由於負載過大,導致hang住的情況。這種情況執行緒池本身無能為力,因為源頭可能是爛SQL併發,或者SQL沒有走對執行計劃導致,通過其他方法,比如SQL高低水位限流或者SQL過濾手段可以應急處理。但是,還有另外一種情況,就是dump任務。很多下游依賴於資料庫的原始資料,通常通過dump命令將資料拉到下游,而這種dump任務通常都是耗時比較長,所以也可以認為是大查詢。如果dump任務集中在一個group內,並導致其他正常業務請求無法立即響應,這個是不能容忍的,因為此時資料庫並沒有壓力,只是因為採用了執行緒池策略,才導致了請求響應不及時,為了解決這個問題,我們將group中處理dump任務的執行緒不計入thread_pool_oversubscribe累計值,避免上述問題。
相關文章
- MySQL執行緒池總結(二)MySql執行緒
- 多執行緒:執行緒池理解和使用總結執行緒
- Java-ThreadPool執行緒池總結Javathread執行緒
- 多執行緒(三)、執行緒池 ThreadPoolExecutor 知識點總結執行緒thread
- Java執行緒池一:執行緒基礎Java執行緒
- MySQL中介軟體之ProxySQL(5):執行緒、執行緒池、連線池MySql執行緒
- 六、執行緒池(一)執行緒
- 案例分析|執行緒池相關故障梳理&總結執行緒
- 【多執行緒總結(一)-基礎總結】執行緒
- JAVA 多執行緒總結(一)Java執行緒
- Java執行緒池二:執行緒池原理Java執行緒
- 淺談執行緒池(上):執行緒池的作用及CLR執行緒池執行緒
- 執行緒和執行緒池執行緒
- 多執行緒【執行緒池】執行緒
- 執行緒 執行緒池 Task執行緒
- 執行緒(一)——執行緒,執行緒池,Task概念+程式碼實踐執行緒
- java-執行緒池(一)Java執行緒
- 執行緒池執行緒
- java多執行緒總結(系列一)Java執行緒
- 執行緒池的建立和使用,執行緒池原始碼初探(篇一)執行緒原始碼
- Netty原始碼解析一——執行緒池模型之執行緒池NioEventLoopGroupNetty原始碼執行緒模型OOP
- 淺談執行緒池(中):獨立執行緒池的作用及IO執行緒池執行緒
- Java多執行緒——執行緒池Java執行緒
- Java執行緒總結Java執行緒
- 一次Java執行緒池誤用引發的血案和總結Java執行緒
- 【多執行緒總結(二)-執行緒安全與執行緒同步】執行緒
- java執行緒池趣味事:這不是執行緒池Java執行緒
- 執行緒池以及四種常見執行緒池執行緒
- Java執行緒池總結和常用開源庫的使用Java執行緒
- 執行緒池關閉的小結執行緒
- java--執行緒池--建立執行緒池的幾種方式與執行緒池操作詳解Java執行緒
- java多執行緒9:執行緒池Java執行緒
- 二. 執行緒管理之執行緒池執行緒
- kuangshenshuo-多執行緒-執行緒池執行緒
- 執行緒的建立及執行緒池執行緒
- JavaThread多執行緒執行緒池Javathread執行緒
- Java多執行緒18:執行緒池Java執行緒
- 多執行緒之手撕執行緒池執行緒