《Mysql實例MySQL的線程池原理學習教程》要點:
本文介紹了Mysql實例MySQL的線程池原理學習教程,希望對您有用。如果有疑問,可以聯系我們。
線程池是Mysql5.6的一個核心功能,對于服務器應用而言,無論是web應用服務還是DB服務,高并發哀求始終是一個繞不開的話題.當有大量哀求并發訪問時,一定伴隨著資源的不斷創建和釋放,導致資源利用率低,降低了服務質量.線程池是一種通用的技術,通過預先創建一定數量的線程,當有哀求達到時,線程池分配一個線程提供服務,哀求結束后,該線程又去服務其他哀求. 通過這種方式,避免了線程和內存對象的頻繁創建和釋放,降低了服務端的并發度,減少了上下文切換和資源的競爭,提高資源利用效率.所有服務的線程池本質都是位了提高資源利用效率,并且實現方式也大體相同.本文主要說明Mysql線程池的實現原理.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入門
?MYSQL入門
調劑方式實現MYSQL入門
Mysql-Server同時支持3種連接管理方式,包含No-Threads,One-Thread-Per-Connection和Pool-Threads.No-Threads表示處理連接使用主線程處理,不額外創建線程,這種方式主要用于調試;One-Thread-Per-Connection是線程池出現以前最常用的方式,為每一個連接創建一個線程服務;Pool-Threads則是本文所討論的線程池方式.Mysql-Server通過一組函數指針來同時支持3種連接管理方式,對于特定的方式,將函數指針設置成特定的回調函數,連接管理方式通過thread_handling參數控制,代碼如下:MYSQL入門
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);
連接治理流程MYSQL入門
通過poll監聽mysql端口的連接哀求
收到連接后,調用accept接口,創建通信socket
初始化thd實例,vio對象等
根據thread_handling方式設置,初始化thd實例的scheduler函數指針
調用scheduler特定的add_connection函數新建連接
下面代碼展示了scheduler_functions模板和線程池對模板回調函數的實現,這個是多種連接管理的核心.MYSQL入門
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 };
線程池的相關參數MYSQL入門
線程池實現MYSQL入門
上面描述了Mysql-Server如何管理連接,這節重點描述線程池的實現框架,以及癥結接口.如圖1
MYSQL入門
MYSQL入門
每一個綠色的方框代表一個group,group數目由thread_pool_size參數決定.每個group包括一個優先隊列和普通隊列,包括一個listener線程和若干個工作線程,listener線程和worker線程可以動態轉換,worker線程數目由工作負載決定,同時受到thread_pool_oversubscribe設置影響.此外,整個線程池有一個timer線程監控group,防止group“停滯”.MYSQL入門
癥結接口MYSQL入門
1. tp_add_connection[處置新連接]MYSQL入門
1) 創立一個connection對象MYSQL入門
2) 根據thread_id%group_count確定connection分派到哪個groupMYSQL入門
3) 將connection放進對應group的隊列MYSQL入門
4) 如果當前活躍線程數為0,則創立一個工作線程MYSQL入門
2. worker_main[工作線程]MYSQL入門
1) 調用get_event獲取哀求MYSQL入門
2) 如果存在哀求,則調用handle_event進行處理MYSQL入門
3) 否則,表示隊列中已經沒有哀求,退出結束.MYSQL入門
3. get_event[獲取哀求]MYSQL入門
1) 獲取一個連接哀求MYSQL入門
2) 如果存在,則立即返回,停止MYSQL入門
3) 若此時group內沒有listener,則線程轉換為listener線程,阻塞期待MYSQL入門
4) 若存在listener,則將線程參加等待隊列頭部MYSQL入門
5) 線程休眠指定的光陰(thread_pool_idle_timeout)MYSQL入門
6) 如果依然沒有被叫醒,是超時,則線程結束,結束退出MYSQL入門
7) 否則,表示隊列里有連接哀求到來,跳轉1MYSQL入門
備注:獲取連接哀求前,會判斷當前的活躍線程數是否超過了MYSQL入門
thread_pool_oversubscribe+1,若跨越了,則將線程進入休眠狀態.MYSQL入門
4. handle_event[處理哀求]MYSQL入門
1) 斷定連接是否進行登錄驗證,若沒有,則進行登錄驗證MYSQL入門
2) 聯系關系thd實例信息MYSQL入門
3) 獲取網絡數據包,分析哀求MYSQL入門
4) 調用do_command函數循環處理哀求MYSQL入門
5) 獲取thd實例的套接字句柄,斷定句柄是否在epoll的監聽列表中MYSQL入門
6) 若沒有,挪用epoll_ctl進行關聯MYSQL入門
7) 停止MYSQL入門
5.listener[監聽線程]MYSQL入門
1) 調用epoll_wait進行對group關聯的套接字監聽,阻塞期待MYSQL入門
2) 若哀求到來,從阻塞中恢復MYSQL入門
3) 依據連接的優先級別,確定是放入普通隊列還是優先隊列MYSQL入門
4) 斷定隊列中任務是否為空MYSQL入門
5) 若隊列為空,則listener轉換為worker線程MYSQL入門
6) 若group內沒有活躍線程,則叫醒一個線程MYSQL入門
備注:這里epoll_wait監聽group內所有連接的套接字,然后將監聽到的連接MYSQL入門
哀求push到隊列,worker線程從隊列中獲取任務,然后執行.MYSQL入門
6. timer_thread[監控線程]MYSQL入門
1) 若沒有listener線程,并且最近沒有io_event變亂MYSQL入門
2) 則創立一個喚醒或創立一個工作線程MYSQL入門
3) 若group最近一段時間沒有處理哀求,并且隊列里面有哀求,則MYSQL入門
4) 表現group已經stall,則喚醒或創建線程MYSQL入門
5)反省是否有連接超時MYSQL入門
備注:timer線程通過挪用check_stall判斷group是否處于stall狀態,通過挪用timeout_check檢查客戶端連接是否超時.MYSQL入門
7.tp_wait_begin[進入期待狀態流程]MYSQL入門
1) active_thread_count減1,waiting_thread_count加1MYSQL入門
2)設置connection->waiting= trueMYSQL入門
3) 若活躍線程數為0,而且任務隊列不為空,或者沒有監聽線程,則MYSQL入門
4) 喚醒或創立一個線程MYSQL入門
8.tp_wait_end[結束期待狀態流程]MYSQL入門
1) 設置connection的waiting狀態為falseMYSQL入門
2) active_thread_count加1,waiting_thread_count減1MYSQL入門
備注:MYSQL入門
1)waiting_threads這個list里面的線程是空閑線程,并非等待線程,所謂空閑線程是隨時可以處置任務的線程,而等待線程則是因為等待鎖,或等待io操作等無法處置任務的線程.MYSQL入門
2)tp_wait_begin和tp_wait_end的主要作用是由于報告請示狀態,即使更新active_thread_count和waiting_thread_count的信息.MYSQL入門
9. tp_init/tp_endMYSQL入門
分別挪用thread_group_init和thread_group_close來初始化和銷毀線程池MYSQL入門
?MYSQL入門
線程池與連接池MYSQL入門
連接池通常實現在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個連接的哀求.
MYSQL入門
MYSQL入門
線程池優化MYSQL入門
1.調度死鎖辦理MYSQL入門
引入線程池解決了多線程高并發的問題,但也帶來一個隱患.假設,A,B兩個事務被分配到不同的group中執行,A事務已經開始,并且持有鎖,但由于A所在的group比較繁忙,導致A執行一條語句后,不能立即獲得調度執行;而B事務依賴A事務釋放鎖資源,雖然B事務可以被調度起來,但由于無法獲得鎖資源,導致仍然需要等待,這就是所謂的調度死鎖.由于一個group會同時處理多個連接,但多個連接不是對等的.比如,有的連接是第一次發送哀求;而有的連接對應的事務已經開啟,并且持有了部分鎖資源.為了減少鎖資源爭用,后者顯然應該比前者優先處理,以達到盡早釋放鎖資源的目的.因此在group里面,可以添加一個優先級隊列,將已經持有鎖的連接,或者已經開啟的事務的連接發起的哀求放入優先隊列,工作線程首先從優先隊列獲取任務執行.MYSQL入門
2.大查詢處置MYSQL入門
假設一種場景,某個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入門
one-connection-per-threadMYSQL入門
依據scheduler_functions的模板,我們也可以列出one-connection-per-thread方式的幾個關鍵函數.MYSQL入門
static scheduler_functions con_per_functions= { max_connection+1, // max_threads NULL, NULL, NULL, // init Init_new_connection_handler_thread, // init_new_connection_thread create_thread_to_handle_connection, // add_connection NULL, // thd_wait_begin NULL, // thd_wait_end NULL, // post_kill_notification one_thread_per_connection_end, // end_thread NULL // end };
?MYSQL入門
1.init_new_connection_handler_threadMYSQL入門
這個接口比擬簡單,主要是調用pthread_detach,將線程設置為detach狀態,線程結束后自動釋放所有資源.MYSQL入門
2.create_thread_to_handle_connectionMYSQL入門
這個接口是處置新連接的接口,對于線程池而言,會從thread_id%group_size對應的group中獲取一個線程來處置,而one-connection-per-thread方式則會判斷是否有thread_cache可以使用,如果沒有則新建線程來處置.具體邏輯如下:MYSQL入門
(1).判斷緩存的線程數是否使用完(比擬blocked_pthread_count 和wake_pthread大小)MYSQL入門
(2).若還有緩存線程,將thd參加waiting_thd_list的隊列,喚醒一個等待COND_thread_cache的線程MYSQL入門
(3).若沒有,創建一個新的線程處置,線程的入口函數是do_handle_one_connectionMYSQL入門
(4).調用add_global_thread參加thd數組.MYSQL入門
3.do_handle_one_connectionMYSQL入門
這個接口被create_thread_to_handle_connection調用,處理哀求的主要實現接口.MYSQL入門
(1).循環調用do_command,從socket中讀取網絡包,而且解析執行;MYSQL入門
(2). 當遠程客戶端發送關閉連接COMMAND(好比COM_QUIT,COM_SHUTDOWN)時,退出循環MYSQL入門
(3).挪用close_connection關閉連接(thd->disconnect());MYSQL入門
(4).挪用one_thread_per_connection_end函數,確認是否可以復用線程MYSQL入門
(5).根據返回成果,確定退出工作線程還是繼續循環執行命令.MYSQL入門
4.one_thread_per_connection_endMYSQL入門
斷定是否可以復用線程(thread_cache)的主要函數,邏輯如下:MYSQL入門
(1).挪用remove_global_thread,移除線程對應的thd實例MYSQL入門
(2).挪用block_until_new_connection判斷是否可以重用threadMYSQL入門
(3).判斷緩存的線程是否跨越閥值,若沒有,則blocked_pthread_count++;MYSQL入門
(4).阻塞期待條件變量COND_thread_cacheMYSQL入門
(5).被喚醒后,表示有新的thd必要重用線程,將thd從waiting_thd_list中移除,使用thd初始化線程的thd->thread_stackMYSQL入門
(6).調用add_global_thread參加thd數組.MYSQL入門
(7).如果可以重用,返回false,不然返回tureMYSQL入門
?MYSQL入門
線程池與epollMYSQL入門
在引入線程池之前,server層只有一個監聽線程,負責監聽mysql端口和本地unixsocket的哀求,對于每個新的連接,都會分配一個獨立線程來處理,因此監聽線程的任務比較輕松,mysql通過poll或select方式來實現IO的多路復用.引入線程池后,除了server層的監聽線程,每個group都有一個監聽線程負責監聽group內的所有連接socket的連接哀求,工作線程不負責監聽,只處理哀求.對于overscribe為1000的線程池設置,每個監聽線程需要監聽1000個socket的哀求,監聽線程采用epoll方式來實現監聽.MYSQL入門
Select,poll,epoll都是IO多路復用機制,IO多路復用通過一種機制,可以監聽多個fd(描述符),好比socket,一旦某個fd就緒(讀就緒或寫就緒),能夠通知程序進行相應的讀寫操作.epoll相對于select和poll有了很大的改進,首先epoll通過epoll_ctl函數注冊,注冊時,將所有fd拷貝進內核,只拷貝一次不需要重復拷貝,而每次調用poll或select時,都需要將fd集合從用戶空間拷貝到內核空間(epoll通過epoll_wait進行等待);其次,epoll為每個描述符指定了一個回調函數,當設備就緒時,喚醒等待者,通過回調函數將描述符加入到就緒鏈表,無需像select,poll方式采用輪詢方式;最后select默認只支持1024個fd,epoll則沒有限制,具體數字可以參考cat /proc/sys/fs/file-max的設置.epoll貫穿在線程池使用的過程中,下面我就epoll的創建,使用和銷毀生命周期來描述epoll在線程中是如何使用的.MYSQL入門
線程池初始化,epoll通過epoll_create函數創建epoll文件描述符,實現函數是thread_group_init;
端口監聽線程監聽到哀求后,創建socket,并創建THD和connection對象,放在對應的group隊列中;
工作線程獲取該connection對象時,若還未登錄,則進行登錄驗證
若socket還未注冊到epoll,則調用epoll_ctl進行注冊,注冊方式是EPOLL_CTL_ADD,并將connection對象放入epoll_event結構體中
若是老連接的哀求,仍然需要調用epoll_ctl注冊,注冊方式是EPOLL_CTL_MOD
group內的監聽線程調用epoll_wait來監聽注冊的fd,epoll是一種同步IO方式,所以會進行等待
哀求到來時,獲取epoll_event結構體中的connection,放入到group中的隊列
線程池銷毀時,調用thread_group_close將epoll關閉.
備注:MYSQL入門
1.注冊在epoll的fd,若哀求就緒,則將對應的event放入到events數組,并將該fd的事務類型清空,因此對于老的連接哀求,依然需要調用epoll_ctl(pollfd, EPOLL_CTL_MOD, fd, &ev)來注冊.MYSQL入門
?MYSQL入門
線程池函數挪用關系MYSQL入門
(1)創立epollMYSQL入門
tp_init->thread_group_init->tp_set_threadpool_size->io_poll_create->epoll_create
(2)封閉epollMYSQL入門
tp_end->thread_group_close->thread_group_destroy->close(pollfd)
(3)聯系關系socket描述符MYSQL入門
handle_event->start_io->io_poll_associate_fd->io_poll_start_read->epoll_ctl
(4)處理連接哀求MYSQL入門
handle_event->threadpool_process_request->do_command->dispatch_command->mysql_parse->mysql_execute_command
(5)工作線程余暇時MYSQL入門
worker_main->get_event->pthread_cond_timedwait
期待thread_pool_idle_timeout后,退出.MYSQL入門
(6)監聽epollMYSQL入門
worker_main->get_event->listener->io_poll_wait->epoll_wait
(7)端口監聽線程MYSQL入門
main->mysqld_main->handle_connections_sockets->poll
?MYSQL入門
one-connection-per-thread函數挪用關系MYSQL入門
(1) 工作線程等待哀求MYSQL入門
handle_one_connection->do_handle_one_connection->do_command-> my_net_read->net_read_packet->net_read_packet_header->net_read_raw_loop-> vio_read->vio_socket_io_wait->vio_io_wait->poll
備注:與線程池的工作線程有監聽線程幫助其監聽哀求不同,one-connection-per-thread方式的工作線程在空閑時,會調用poll阻塞等待網絡包過來;MYSQL入門
而線程池的工作線程只需要專心處理哀求即可,所以使用也更充分.MYSQL入門
(2)端口監聽線程
與線程池的(7)雷同MYSQL入門
歡迎參與《Mysql實例MySQL的線程池原理學習教程》討論,分享您的想法,維易PHP學院為您提供專業教程。