mysql5.6 线程池_[MySQL5.6] Percona Server 5.6.14的线程池浅析
Percona的線程池基本上是從Mariadb中引入,其實現思路也比較簡單,就是在線程調度器那增加了一組新的回調函數。線程池可以有效改善在大并發下的性能;
Thread pool的原理在Percona的這篇博客描述的很生動;其實就是限制同時運行的線程數,讓大家不要一起擠進來,有序運行負載。線程池的目的不是為了提高性能,而是為了保持性能的穩定。
在使用線程池的場景下,就不是通常的一個連接一個線程(one-thread-per-connection),你可以創建幾千甚至上萬個Socket連接,MySQL只會創建有限個線程來
以下從不同的點來介紹下相關的代碼,這里沒有太多的深入,也沒有性能測試數據,只是閱讀時帶著疑問做的一些筆記;
Q:如何控制同時運行的并發線程數
參數thread_pool_size的命令可能會讓大家產生誤解,它不是指的線程池的大小,而是線程組的大小。類似所有創建的線程都在某一個group里,group的編號從1~thread_pool_size,每個group里的線程數可以通過參數thread_pool_oversubscribe來控制(默認為3),如果把thread_pool_oversubscribe設置為1,那么thread_pool_size就被嚴格限制為能夠同時活躍的最大線程數。
thread_pool_size默認值為CPU核心數,最大為128(MAX_THREAD_GROUPS),在啟動時,就會把128個Group對應的結構體(all_groups)初始化好。每個group(編號小于等于thread_pool_size)會創建一個epoll對象;
Q:如何處理新連接
當客戶端發起一個連接請求時,會被main線程捕捉到(handle_connections_sockets),然后調用tp_add_connection將新的連接加入到某個線程池的group中(根據thread_id%thread_pool_size),每個連接創建一個connection_t結構體對象(這個connection結構體里包含了連接的相關信息,例如是否登錄,thd,是否正在等待,tickets等等),
創建好的connection被加入到其所屬group的隊列中(thread_group->queue),如果當前該group沒有active的線程(thread_group->active_thread_count == 0)嘗試去喚醒一個工作線程或者新建一個線程(wake_or_create_thread):a.首先嘗試喚醒當前空閑的線程(空閑的線程對象在thread_group->waiting_threads中),如果有的話,則將其從waiting_threads中移除,并發送信號 ; b.否則表示現在該group內還沒有線程, 需要創建一個新的worker線程(create_worker); 在創建worker線程時,一個比較有趣的代碼段:
if (tp_stats.num_worker_threads >= (int)threadpool_max_threads
&& thread_group->thread_count >= 2)
{
err= 1;
max_threads_reached= true;
goto end;
}
實際上thread_pool_max_threads不是完全嚴格的限制總共的線程數,只有當當前線程數大于該max值,且當前group已有2個及以上worker線程時才拒絕新建線程
從上一步可以保證有一個活躍線程被喚醒來處理新連接的登錄請求,worker線程的回調函數為worker_main,也就是處理socket請求的真正函數邏輯。
Q:如何處理新請求
worker線程調用worker_main,在一個循環內干兩件事兒:
#get_event
顧名思義,該函數的目的就是為了獲取一個事件,大約有如下的流程:
a.如果當前活躍線程數大于thread_pool_oversubscribe,并且該group的stall狀態為false(何時設置?),暗示這時候該group的活躍線程數太多了(oversubscribed)。
b.如果oversubscribed為false,則取從隊列中取connection_t對象(queue_get),先從高優先級隊列(thread_group->high_prio_queue)取,如果沒有的話,再從普通隊列(thread_group->queue)取。如果存在的話,取得該對象返回
c.如果當前group里沒有正在監聽的線程(這時候沒有任何請求),則把當前worker線程設置為監聽線程,進入epoll監聽socket請求
對于監聽到的新請求,如果當前group里的沒有event,,則由監聽線程自己來處理監聽到的第一個任務,剩下的任務放到隊列中,否則把任務加入到隊列中,由其他worker線程來處理。
加入隊列的規則:
1.當該連接的ticket沒用完(初始值為thread_pool_high_prio_tickets, Percona引入) 并且事務是活躍時,將該請求加入到高優先級隊列,將tickets—
2.否則,將ticket設為threadpool_high_prio_tickets,并加入到普通隊列中;
這實際上和innodb的ticket類似,保證一個大事務不要占用worker線程太長時間。以免引起其他事務餓死。
然后會去喚醒別的worker線程
d.oversubscribed為false時,再次嘗試一次通過epoll看看有沒有新請求(io_poll_wait),有的話直接處理該請求,這里的等待時間為0
f.將該worker線程加入等待隊列(thread_group->waiting_threads),進入condtion wait,等待被喚醒,等待時間為thread_pool_idle_timeout
超時線程將直接退出;如果是被喚醒的,則跳轉到a
#handle_event
worker線程拿到一個新請求后,進入處理階段,這里就比較簡單了。
如果connection還沒有認證(connection->logged_in為false),則調用threadpool_add_connection(connection->the) 進行賬戶認證
否則,調用threadpool_process_request處理新請求。
在處理完后,設置connection的超時時間,以決定是否在超時后斷開鏈接(pool_timer.next_timeout_check會被設置成最小的值),具體則由timer線程來檢查;
然后再次將該socket的fd加入到其group所屬的epoll中。
Q:異常場景下的處理
后臺有一個timer線程在初始化thread pool時(tp_init)被啟動,線程回調函數為timer_thread,用于檢查每個線程組是否被阻塞了(check_stall)。大體思路是,在一段時間內,如果任務隊列不為空,且從上次檢查到現在沒有從隊列中取出任務(即thread_group->queue_event_count為0),那么就回去嘗試喚醒或新創建一個worker線程;
如果查詢運行的時間都非常長,那么最終可能退化成thread-per-connection這種原始模式;
timer線程也負責檢查connetion是否net timeout.
thread_pool_stall_limit在Percona版本里似乎暫時沒用到,根據其語義應該是用來控制timer線程循環檢查的間隔時間
wait_begin/wait_end在進入等待前調用(我們在寫代碼時要注意這一點),其中wait_begin會先確保要有活躍的線程來處理請求,然后才會去讓當前線程進入等待;
BluePrint
(Percona開發中,當前是基于Percona 5.6.14, 你看到這篇博客時,下面的幾點可能已經實現了):
提供三種控制模式:
當設置為transaction模式時,只有一個已經開啟了事務的SQL才會進入到高優先級隊列中;
當設置為statements模式時,單獨的SQL總是進入高優先級隊列;
當設置為none時,則總是不走優先隊列
通過這種方式來讓客戶端自己選擇;當然更進一步的,可以在grant 權限時為其設置一個模式
基于計時器的優先調度? 語焉不詳。猜測大概意思是在低優先級隊列如果超過一定的時間,就自動轉移到高優先隊列。
描述了一種場景:大多數SQL在等待某個事務釋放行鎖,而該事務所對應的事件由于oversubscribe了,無法被調度的問題;
從代碼?(尚未正式發布)來看,定義了一種線程忙的狀態(就是active的線程加上wait的線程大于thread_pool_oversubscribe)。這種狀態下不允許低優先級隊列中的任務執行;那么對于上述場景,已經開啟的事務擁有ticket且處于活躍狀態,能夠被優先更快的執行掉。
總結
以上是生活随笔為你收集整理的mysql5.6 线程池_[MySQL5.6] Percona Server 5.6.14的线程池浅析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: mysql 随机update_MySQL
- 下一篇: mysql clob转string_Ja