最近研究的MySQL線程池問題,都整理在這了

最近出現多次由于上層組件異常導致DB雪崩的情況,將部分監控DB啟用了線程池功能。在使用線程池的過程中不斷的深入學習,期間也遇到了不少問題。本文就來詳細講述一下MySQL線程池相關的知識,以幫助廣大DBA快速了解MySQL的線程池機制,快速配置MySQL的線程池以及了解里面存在的一些坑。 其實,我想說的是,了解和使用MySQL線程池,看這篇文章就夠了。

一、為什么要使用MySQL線程池

在介紹為什么要使用線程池之前,我們都知道,隨著DB訪問量越來越大,DB的響應時間也會隨之越來越大,如下圖:

而DB的訪問大到一定程度的時候,DB的吞吐量也會出現下降,并且會越來越差,如下圖所示:

那么是否有什么方式,能讓對著DB的訪問量越來越大,DB始終能表現出最佳的性能?類似下圖的表現:

答案就是今天要重點介紹的線程池功能,總結一下,使用線程池的理由有如下兩個:

1、減少線程重復創建與銷毀部分的開銷,提高性能

? ? 線程池技術通過預先創建一定數量的線程,在監聽到有新的請求的時候,線程池直接從現有的線程中分配一個線程來提供服務,服務結束后這個線程不會直接銷毀,而是又去處理其他的請求。這樣就避免了線程和內存對象頻繁創建和銷毀,減少了上下文切換,提高了資源利用率,從而在一定程度上提高了系統的性能和穩定性。

2、對系統起到保護作用

? ? 線程池技術限制了并發線程數,相當于限制了MySQL的runing線程數,無論系統目前有多少連接或者請求,超過最大設置的線程數的都需要排隊,讓系統保持高性能水平。從而防止DB出現雪崩,對底層DB起到保護作用。

? ? 可能有人會問,使用連接池是否也能達到類似的效果?

? ? 可能有的DBA會把線程池和連接池混淆,其實兩者是有很大區別的,連接池一般在客戶端設置,而線程池是在DB服務器上配置;另外連接池可以取到避免了連接頻繁創建和銷毀,但是無法取到控制MySQL活動線程數的目標,在高并發場景下,無法取到保護DB的作用。比較好的方式是將連接池和線程池結合起來使用。

二、MySQL線程池介紹

(一)、MySQL線程池簡介

為了解決one-thread-per-connection(每個連接一個線程)存在的頻繁創建和銷毀大量線程以及高并發情況下DB雪崩的問題,實現DB在高并發環境依然能保持較高的性能。Oracle和MariaDB都推出了ThreadPool方案,目前Oracle的threadpool實現為plugin方式,并且只添加到在Enterprise版本中,Percona移植了MariaDB的threadpool功能,并做了進一步的優化。本文的環境是基于Percona MySQL 5.7版本。

(二)、MySQL線程池架構

MySQL的threadpool(線程池)被劃分為多個group(組),每個組又有對應的工作線程,整體的工作邏輯還是比較復雜,下面我試圖通過簡單的方式來介紹MySQL線程池的工作原理。

1、架構圖

首先來看看threadpool的架構圖

2、Thread Pool的組成

? ? 從架構圖中可以看到Thread Pool由一個Timer線程和多個Thread Group組成,而每個Thread Group又由兩個隊列、一個listener線程和多個worker線程構成。下面分別來介紹每個各個部分的作用:

a、隊列(高優先級隊列和低優先級隊列)

? ? 用來存放待執行的IO任務,分為高優先級隊列和低優先級隊列,高優先級隊列的任務會優先被處理。

?? ?什么任務會放在高優先級隊列呢?

? ? 事務中的語句會放到高優先級隊列中,比如一個事務中有兩個update的SQL,有1個已經執行,那么另外一個update的任務就會放在高優先級中。這里需要注意,如果是非事務引擎,或者開啟了autocommit的事務引擎,都會放到低優先級隊列中。

?? ?還有一種情況會將任務放到高優先級隊列中,如果語句在低優先級隊列停留太久,該語句也會移到高優先級隊列中,防止餓死的問題。xxxxxxxxx

b、listener線程

? ? listener線程監聽該線程group的語句,并確定是自己轉變成worker線程立即執行對應的語句還是放到隊列中,判斷的標準是看隊列中是否有待執行的語句。如果隊列中待執行的語句數量為0,而listener線程轉換成worker線程,并立即執行對應的語句。如果隊列中待執行的語句數量不為0,則認為任務比較多,將語句放入隊列中,讓其他的線程來處理。這里的機制是為了減少線程的創建,因為一般SQL執行都非常快。

c、worker線程

? ? worker線程是真正干活的線程

d、Timer線程

? ? Timer線程是用來周期性檢查group是否處于處于阻塞狀態,當出現阻塞的時候,會通過喚醒線程或者新建線程來解決。具體的檢測方法為,通過queue_event_count的值和IO任務隊列是否為空來判斷線程組是否為阻塞狀態。每次worker線程檢查隊列中任務的時候,queue_event_count會+1,每次Timer檢查完group是否阻塞的時候會將queue_event_count清0,如果檢查的時候任務隊列不為空,而queue_event_count為0,則說明任務隊列沒有被正常處理,此時該group出現了阻塞,Timer線程會喚醒worker線程或者新建一個wokrer線程來處理隊列中的任務,防止group長時間被阻塞。

3、Thread Pool的是如何運作的?

?下面描述極簡的Thread Pool運作,只是簡單描述,省略了大量的復雜邏輯,請不要挑刺,~~

a、請求連接到MySQL,根據threadid%thread_pool_size確定落在哪個group

b、group中的listener線程監聽到所在的group有新的請求以后,檢查隊列中是否有請求還未處理,如果沒有,則自己轉換為worker線程立即處理該請求,如果隊列中還有未處理的請求,則將對應請求放到隊列中,讓其他的線程處理。

c、group中的thread線程檢查隊列的請求,如果隊列中有請求,則進行處理,如果沒有請求,則休眠,一直沒有被喚醒,超過thread_pool_idle_timeout后就自動退出。線程結束。當然,獲取請求之前會先檢查group中的running線程數是否超過thread_pool_oversubscribe+1,如果超過也會休眠。

d、timer線程定期檢查各個group是否有阻塞,如果有,就對wokrer線程進行喚醒或者創建一個新的worker線程。

4、Thread Pool的分配機制

?? ?線程池會根據參數thread_pool_size的大小分成若干的group,每個group各自維護客戶端發起的連接,當客戶端發起連接到MySQL的時候,MySQL會跟進連接的線程id(thread_id)對thread_pool_size進行取模,從而落到對應的group。thread_pool_oversubscribe參數控制每個group的最大并發線程數,每個group的最大并發線程數為thread_pool_oversubscribe+1個,若對應的group達到了最大的并發線程數,則對應的連接就需要等待。這個分配機制在某個group中有多個慢SQL的場景下會導致普通的SQL運行時間很長,這個問題在后面的會做詳細描述。

(三)、MySQL線程池參數說明

關于線程池參數不多,使用show variables like 'thread%'可以看到如下圖的參數,下面就一個一個來解析:

thread_handling

該參數是配置線程模型,默認情況是one-thread-per-connection,也就是不啟用線程池。將該參數設置為pool-of-threads即啟用了線程池

thread_pool_size

該參數是設置線程池的Group的數量,默認為系統CPU的個數,充分利用CPU資源

thread_pool_oversubscribe

該參數設置group中的最大線程數,每個group的最大線程數為thread_pool_oversubscribe+1,注意listener線程不包含在內。

thread_pool_high_prio_mode

高優先級隊列的控制參數,有三個值(transactions/statements/none),默認是transactions,三個值的含義如下:

transactions:對于已經啟動事務的語句放到高優先級隊列中,不過還取決于后面的thread_pool_high_prio_tickets參數

statements:這個模式所有的語句都會放到高優先級隊列中,不會使用到低優先級隊列

none:這個模式不使用高優先級隊列

thread_pool_high_prio_tickets

該參數控制每個連接最多語序多少次被放入高優先級隊列中,默認為4294967295,注意這個參數只有在thread_pool_high_prio_mode為transactions的時候才有效果。

thread_pool_idle_timeout

worker線程最大空閑時間,默認為60秒,超過限制后會退出

thread_pool_max_threads

該參數用來限制線程池最大的線程數,超過該限制后將無法再創建更多的線程,默認為100000

thread_pool_stall_limit

該參數設置timer線程的檢測group是否異常的時間間隔,默認為500ms

三、MySQL線程池的使用

線程池的使用比較簡單,只需要添加配置后重啟實例即可

具體配置如下:

#thread pool

thread_handling=pool-of-threads

thread_pool_oversubscribe=3

thread_pool_size=24

performance_schema=off

#extra connection

extra_max_connections = 8

extra_port = 33333

備注:其他參數默認即可

以上具體的參數在前面已經有做詳細說明。下面是配置中需要注意的兩個點:

1、只所以添加performance_schema=off,是由于測試過程中發現Thread pool和PS同時開啟的時候會出現內存泄漏,在后面遇到的問題部分有詳細描述;

2、添加extra connection是防止線程池滿的情況下無法登錄MySQL,因此特意已用管理端口,以備緊急的情況下使用;

重啟實例后,可以通過show variables like '%thread%';來查看配置的參數是否生效。

四、使用MySQL線程池遇到的問題

在使用線程池的過程中,遇到了幾個問題,也順便總結一下:

(一)內存泄漏問題

DB啟用線程池后,內存飆升了8G左右,如下圖:

不但啟用線程池后內存飆升了8G左右,而且內存還在持續增長,很明顯啟用線程池后存在內存泄漏了。

這個問題在網上也有不少的人遇到,確認是percona的bug導致(https://jira.percona.com/browse/PS-3734),只有開啟Performance_Schema和ThreadPool的時候才會出現,解決辦法是關閉Performance_Schema即可,具體操作方法是在配置文件添加performance_schema=off,然后重啟MySQL就OK。下面是關閉PS后的內存使用情況對比:

備注:

目前Percona server?5.7.21-20版本已經修復了線程池和PS同時打開內存泄漏的問題,從我測試的情況來看問題也得到了解決,大家也可以直接使用Percona server?5.7.21-20的版本,如下圖

(二)撥測異常問題

啟用線程池以后,相當于限制了MySQL的并發線程數,當達到最大線程數的時候,其他的線程需要等待,新連接也會卡在連接驗證那一步,這時候會造成撥測程序連接MySQL超時,撥測返回錯誤如下:

撥測程序連接實例超時后,就會認為master已經出現問題,極端情況下,重試多次都有異常后,就啟動自動切換的操作,將業務切換到從機。

這種情況有兩種解決辦法:

1、啟用MySQL的旁路管理端口,監控和高可用相關直接使用MySQL的旁路管理端口

? ? 具體做法為是在my.cnf中添加如下配置后重啟,就可以通過旁路端口登錄MySQL了,不受線程池最大線程數的影響:

? ? extra_max_connections = 8

? ? extra_port = 33333

? ? 備注:建議啟用線程池后,這個也添加上,方便緊急情況下進行故障處理。

2、修改高可用探測腳本,將達到線程池最大活動線程數返回的錯誤做異常處理,類似于當作超過最大連接數的場景(備注:超過最大連接數只告警,不進行自動切換)

(三)慢SQL引入的問題

隨著對撥測超時的問題的深入分析,線程池滿只是監控撥測出現超時的其中一種情況,還有一種情況是線程池并沒有滿,線上的兩個配置:

thread_pool_oversubscribe=3

thread_pool_size=24

按照上面的兩個配置來計算的話,總共能并發運行24x(3+1)=96,但是根據多次問題的追中,發現有多次線程池并沒有達到96,也就是說整體的線程池并沒有滿。那會是什么問題導致撥測失敗呢?

鑒于線程池的結構和分配機制,通過前面線程池部分的描述,大家都知道了在內部是將線程池分成一個一個的group,我們線上配置了24個group,而線程池的分配機制是對Threadid進行取模,然后確定該線程是落在哪個group。出現超時的時候,有很多的load線程到導入數據。也就是說那個時候有部分線程比較慢的情況。那么會不會是某個group的線程滿了,從而導致新分配的線程等待?

有了這個猜想以后,接下來就是來驗證這個問題。驗證分為兩步:

1、抓取線上運行的processlist,然后對threadid取模,看看是否有多個load線程落在統一個group的情況

2、在測試環境模擬這種場景,看看是否符合預期

線上場景分析:

先來看線上的場景,通過抓取撥測超時時間點的processlist,找出當時正在load的線程,根據threadid進行去模,并進行匯總統計后,得出如下結果:

可以看出,當時第4和第7個group的請求個數都超過了4個,說明是單個group滿導致的撥測異常,當然,也會導致部分運行很快的SQL變慢。

測試環境模擬場景分析:

為了構建快速重現環境,我將參數調整如下:

thread_pool_oversubscribe=1

thread_pool_size=2

通過上面參數的調整,可以計算出最大并發線程為2x(1+1)=4,如下圖,當活動線程數超過4個后,其他的線程就必須等待:

我模擬線上環境的方法為,開啟1個線程的慢SQL,這個時候,測試環境的線程池情況如下:

按照之前的推測,這個時候Group1的處理能力相當于Group2的處理能力的50%,如果之前的推論是正確的,那么分配在Group1上的線程就會出現阻塞。比如此時來了20個線程請求,按照線程池的分配原則,此時Group1和Group2都會分到10個線程請求。稼穡所有的線程請求耗時都是一樣的,那么分配到Group1上的線程請求整體處理時間應該是分配到Group2上整體處理時間的2倍。

我使用腳本,并發發起12個線程請求,每個線程請求都運行select sleep(2),那么在Group1和Group2都空閑的情況下,運行情況如下:

2018-03-18-20:23:53

2018-03-18-20:23:53

2018-03-18-20:23:53

2018-03-18-20:23:53

2018-03-18-20:23:55

2018-03-18-20:23:55

2018-03-18-20:23:55

2018-03-18-20:23:55

2018-03-18-20:23:57

2018-03-18-20:23:57

2018-03-18-20:23:57

2018-03-18-20:23:57

每次4個線程,總共運行了6秒

接下來在Group1被1個長時間運行的線程沾滿以后,看看測試結果是怎么樣的

2018-03-18-20:24:35

2018-03-18-20:24:35

2018-03-18-20:24:35

2018-03-18-20:24:37

2018-03-18-20:24:37

2018-03-18-20:24:37

2018-03-18-20:24:39

2018-03-18-20:24:39

2018-03-18-20:24:39

2018-03-18-20:24:41

2018-03-18-20:24:43

2018-03-18-20:24:45

從上面的結果中可以看出,在沒有阻塞的時候,每次都是4個線程,而后面有1個線程長時間運行的時候,就會出現那個長時間線程對應的group出現排隊的情況,最后雖然有3個空閑的線程,但是卻是只有1個線程在處理(粗體部分結果)。

解決把法有兩個:

1、將thread_pool_oversubscribe適當調大,這個辦法只能緩解類似問題,無法根治;

2、找到慢的SQL,解決慢的問題;

五、參考文獻:

https://www.percona.com/doc/percona-server/LATEST/performance/threadpool.html

https://www.percona.com/blog/2013/03/16/simcity-outages-traffic-control-and-thread-pool-for-mysql/

http://www.cnblogs.com/cchust/p/4510039.html

http://blog.jobbole.com/109695/

http://blog.csdn.net/u012662731/article/details/54375137

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 【MySQL】Linux下MySQL 5.5、5.6和5.7的RPM、二進制和源碼安裝 1.1BLOG文檔結構圖 ...
    小麥苗DB寶閱讀 10,586評論 0 31
  • 個人筆記,方便自己查閱使用 Contents Java LangAssignment, ReferenceData...
    freenik閱讀 1,405評論 0 6
  • 心理的痛苦比身體的痛苦更容易感受到,五年多的時間都在尋求心理上的解脫,因為覺得心理太苦了!求不得苦,放不下苦!求了...
    竺子閱讀 182評論 0 0
  • 梨花又開放 陳明版本 每次都是臨時抱佛腳,上課前短短十來分鐘的士上開始用耳機聽歌。 我在車上望出窗外繁忙的深南大道...
    Orange_miki閱讀 485評論 0 0
  • 今年湖北的降雨量,據說是50年一遇,等同于11.2個東湖同時傾盆而下,一時間網絡上的報道鋪天蓋地。來自湖北的我,自...
    精靈Arora閱讀 452評論 0 1