日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 运维知识 > 数据库 >内容正文

数据库

关于MySQL线程池,这也许是目前最全面的实用帖!(转载)

發(fā)布時(shí)間:2025/3/8 数据库 35 豆豆
生活随笔 收集整理的這篇文章主要介紹了 关于MySQL线程池,这也许是目前最全面的实用帖!(转载) 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

關(guān)于MySQL線程池,這也許是目前最全面的實(shí)用帖!

最近出現(xiàn)多次由于上層組件異常導(dǎo)致DB雪崩的情況,筆者將部分監(jiān)控DB啟用了線程池功能,在使用線程池的過(guò)程中不斷深入學(xué)習(xí)的同時(shí),也遇到了不少問(wèn)題。

本文就來(lái)詳細(xì)講述一下MySQL線程池相關(guān)的知識(shí),以幫助廣大DBA快速了解MySQL的線程池機(jī)制,快速配置MySQL的線程池以及里面存在的一些坑。 其實(shí)我想說(shuō),了解和使用MySQL線程池,看這篇文章就夠了。

一、為何要使用MySQL線程池

在介紹為什么要使用線程池之前,我們都知道隨著DB訪問(wèn)量越來(lái)越大,DB的響應(yīng)時(shí)間也會(huì)隨之越來(lái)越大,如下圖:

而DB的訪問(wèn)大到一定程度時(shí),DB的吞吐量也會(huì)出現(xiàn)下降,并且會(huì)越來(lái)越差,如下圖所示:

那么是否有什么方式,能實(shí)現(xiàn)隨著DB的訪問(wèn)量越來(lái)越大,DB始終表現(xiàn)出最佳的性能呢?類(lèi)似下圖的表現(xiàn):

答案就是今天要重點(diǎn)介紹的線程池功能??偨Y(jié)一下,使用線程池的理由有兩個(gè):

1、減少線程重復(fù)創(chuàng)建與銷(xiāo)毀部分的開(kāi)銷(xiāo),提高性能

線程池技術(shù)通過(guò)預(yù)先創(chuàng)建一定數(shù)量的線程,在監(jiān)聽(tīng)到有新的請(qǐng)求時(shí),線程池直接從現(xiàn)有的線程中分配一個(gè)線程來(lái)提供服務(wù),服務(wù)結(jié)束后這個(gè)線程不會(huì)直接銷(xiāo)毀,而是又去處理其他的請(qǐng)求。這樣就避免了線程和內(nèi)存對(duì)象頻繁創(chuàng)建和銷(xiāo)毀,減少了上下文切換,提高了資源利用率,從而在一定程度上提高了系統(tǒng)的性能和穩(wěn)定性。

2、對(duì)系統(tǒng)起到保護(hù)作用

線程池技術(shù)限制了并發(fā)線程數(shù),相當(dāng)于限制了MySQL的runing線程數(shù),無(wú)論系統(tǒng)目前有多少連接或者請(qǐng)求,超過(guò)最大設(shè)置的線程數(shù)的都需要排隊(duì),讓系統(tǒng)保持高性能水平,從而防止DB出現(xiàn)雪崩,對(duì)底層DB起到保護(hù)作用。

可能有人會(huì)問(wèn),使用連接池能否也達(dá)到類(lèi)似的效果?

也許有的DBA會(huì)把線程池和連接池混淆,但其實(shí)兩者是有很大區(qū)別的:連接池一般在客戶端設(shè)置,而線程池是在DB服務(wù)器上配置;另外連接池可以起到避免了連接頻繁創(chuàng)建和銷(xiāo)毀,但是無(wú)法控制MySQL活動(dòng)線程數(shù)的目標(biāo),在高并發(fā)場(chǎng)景下,無(wú)法起到保護(hù)DB的作用。比較好的方式是將連接池和線程池結(jié)合起來(lái)使用。

二、MySQL線程池介紹

MySQL線程池簡(jiǎn)介

為了解決one-thread-per-connection(每個(gè)連接一個(gè)線程)存在的頻繁創(chuàng)建和銷(xiāo)毀大量線程以及高并發(fā)情況下DB雪崩的問(wèn)題,實(shí)現(xiàn)DB在高并發(fā)環(huán)境依然能保持較高的性能。

Oracle和MariaDB都推出了ThreadPool方案,目前Oracle的Thread pool實(shí)現(xiàn)為Plugin方式,并且只添加到在Enterprise版本中,Percona移植了MariaDB的Thread pool功能,并做了進(jìn)一步的優(yōu)化。本文的環(huán)境就基于Percona MySQL 5.7版本。

MySQL線程池架構(gòu)

MySQL的Thread pool(線程池)被劃分為多個(gè)group(組),每個(gè)組又有對(duì)應(yīng)的工作線程,整體的工作邏輯還是比較復(fù)雜,下面我試圖通過(guò)簡(jiǎn)單的方式來(lái)介紹MySQL線程池的工作原理。

1、架構(gòu)圖

首先來(lái)看看Thread Pool的架構(gòu)圖。

2、Thread Pool的組成

從架構(gòu)圖中可以看到Thread Pool由一個(gè)Timer線程和多個(gè)Thread Group組成,而每個(gè)Thread Group又由兩個(gè)隊(duì)列、一個(gè)listener線程和多個(gè)worker線程構(gòu)成。下面分別來(lái)介紹各個(gè)部分的作用:

  • 隊(duì)列(高優(yōu)先級(jí)隊(duì)列和低優(yōu)先級(jí)隊(duì)列)

用來(lái)存放待執(zhí)行的IO任務(wù),分為高優(yōu)先級(jí)隊(duì)列和低優(yōu)先級(jí)隊(duì)列,高優(yōu)先級(jí)隊(duì)列的任務(wù)會(huì)優(yōu)先被處理。

什么任務(wù)會(huì)放在高優(yōu)先級(jí)隊(duì)列呢?

事務(wù)中的語(yǔ)句會(huì)放到高優(yōu)先級(jí)隊(duì)列中,比如一個(gè)事務(wù)中有兩個(gè)update的SQL,有1個(gè)已經(jīng)執(zhí)行,那么另外一個(gè)update的任務(wù)就會(huì)放在高優(yōu)先級(jí)中。這里需要注意,如果是非事務(wù)引擎,或者開(kāi)啟了Autocommit的事務(wù)引擎,都會(huì)放到低優(yōu)先級(jí)隊(duì)列中。

還有一種情況會(huì)將任務(wù)放到高優(yōu)先級(jí)隊(duì)列中,如果語(yǔ)句在低優(yōu)先級(jí)隊(duì)列停留太久,該語(yǔ)句也會(huì)移到高優(yōu)先級(jí)隊(duì)列中,防止餓死。

  • listener線程

listener線程監(jiān)聽(tīng)該線程group的語(yǔ)句,并確定當(dāng)自己轉(zhuǎn)變成worker線程,是立即執(zhí)行對(duì)應(yīng)的語(yǔ)句還是放到隊(duì)列中,判斷的標(biāo)準(zhǔn)是看隊(duì)列中是否有待執(zhí)行的語(yǔ)句。

如果隊(duì)列中待執(zhí)行的語(yǔ)句數(shù)量為0,而listener線程轉(zhuǎn)換成worker線程,并立即執(zhí)行對(duì)應(yīng)的語(yǔ)句。如果隊(duì)列中待執(zhí)行的語(yǔ)句數(shù)量不為0,則認(rèn)為任務(wù)比較多,將語(yǔ)句放入隊(duì)列中,讓其他的線程來(lái)處理。這里的機(jī)制是為了減少線程的創(chuàng)建,因?yàn)橐话鉙QL執(zhí)行都非???。

  • worker線程

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

  • Timer線程

Timer線程是用來(lái)周期性檢查group是否處于處于阻塞狀態(tài),當(dāng)出現(xiàn)阻塞的時(shí)候,會(huì)通過(guò)喚醒線程或者新建線程來(lái)解決。

具體的檢測(cè)方法為:通過(guò)queue_event_count的值和IO任務(wù)隊(duì)列是否為空來(lái)判斷線程組是否為阻塞狀態(tài)。

每次worker線程檢查隊(duì)列中任務(wù)的時(shí)候,queue_event_count會(huì)+1,每次Timer檢查完group是否阻塞的時(shí)候會(huì)將queue_event_count清0,如果檢查的時(shí)候任務(wù)隊(duì)列不為空,而queue_event_count為0,則說(shuō)明任務(wù)隊(duì)列沒(méi)有被正常處理,此時(shí)該group出現(xiàn)了阻塞,Timer線程會(huì)喚醒worker線程或者新建一個(gè)wokrer線程來(lái)處理隊(duì)列中的任務(wù),防止group長(zhǎng)時(shí)間被阻塞。

3、Thread Pool的是如何運(yùn)作的?

下面描述極簡(jiǎn)的Thread Pool運(yùn)作,只是簡(jiǎn)單描述,省略了大量的復(fù)雜邏輯,請(qǐng)不要挑刺~

Step1:請(qǐng)求連接到MySQL,根據(jù)threadid%thread_pool_size確定落在哪個(gè)group;

Step2:group中的listener線程監(jiān)聽(tīng)到所在的group有新的請(qǐng)求以后,檢查隊(duì)列中是否有請(qǐng)求還未處理。如果沒(méi)有,則自己轉(zhuǎn)換為worker線程立即處理該請(qǐng)求,如果隊(duì)列中還有未處理的請(qǐng)求,則將對(duì)應(yīng)請(qǐng)求放到隊(duì)列中,讓其他的線程處理;

Step3:group中的thread線程檢查隊(duì)列的請(qǐng)求,如果隊(duì)列中有請(qǐng)求,則進(jìn)行處理,如果沒(méi)有請(qǐng)求,則休眠,一直沒(méi)有被喚醒,超過(guò)thread_pool_idle_timeout后就自動(dòng)退出。線程結(jié)束。當(dāng)然,獲取請(qǐng)求之前會(huì)先檢查group中的running線程數(shù)是否超過(guò)thread_pool_oversubscribe+1,如果超過(guò)也會(huì)休眠;

Step4:timer線程定期檢查各個(gè)group是否有阻塞,如果有,就對(duì)wokrer線程進(jìn)行喚醒或者創(chuàng)建一個(gè)新的worker線程。

4、Thread Pool的分配機(jī)制

線程池會(huì)根據(jù)參數(shù)thread_pool_size的大小分成若干的group,每個(gè)group各自維護(hù)客戶端發(fā)起的連接,當(dāng)客戶端發(fā)起連接到MySQL的時(shí)候,MySQL會(huì)跟進(jìn)連接的線程id(thread_id)對(duì)thread_pool_size進(jìn)行取模,從而落到對(duì)應(yīng)的group。

thread_pool_oversubscribe參數(shù)控制每個(gè)group的最大并發(fā)線程數(shù),每個(gè)group的最大并發(fā)線程數(shù)為thread_pool_oversubscribe+1個(gè)。若對(duì)應(yīng)的group達(dá)到了最大的并發(fā)線程數(shù),則對(duì)應(yīng)的連接就需要等待。這個(gè)分配機(jī)制在某個(gè)group中有多個(gè)慢SQL的場(chǎng)景下會(huì)導(dǎo)致普通的SQL運(yùn)行時(shí)間很長(zhǎng),這個(gè)問(wèn)題會(huì)在后面做詳細(xì)描述。

MySQL線程池參數(shù)說(shuō)明

關(guān)于線程池參數(shù)不多,使用show variables like 'thread%'可以看到如下圖的參數(shù),下面就一個(gè)一個(gè)來(lái)解析:

  • thread_handling

該參數(shù)是配置線程模型,默認(rèn)情況是one-thread-per-connection,即不啟用線程池;將該參數(shù)設(shè)置為pool-of-threads即啟用了線程池。

  • thread_pool_size

該參數(shù)是設(shè)置線程池的Group的數(shù)量,默認(rèn)為系統(tǒng)CPU的個(gè)數(shù),充分利用CPU資源。

  • thread_pool_oversubscribe

該參數(shù)設(shè)置group中的最大線程數(shù),每個(gè)group的最大線程數(shù)為thread_pool_oversubscribe+1,注意listener線程不包含在內(nèi)。

  • thread_pool_high_prio_mode

高優(yōu)先級(jí)隊(duì)列的控制參數(shù),有三個(gè)值(transactions/statements/none),默認(rèn)是transactions,三個(gè)值的含義如下:

transactions:對(duì)于已經(jīng)啟動(dòng)事務(wù)的語(yǔ)句放到高優(yōu)先級(jí)隊(duì)列中,不過(guò)還取決于后面的thread_pool_high_prio_tickets參數(shù)。

statements:這個(gè)模式所有的語(yǔ)句都會(huì)放到高優(yōu)先級(jí)隊(duì)列中,不會(huì)使用到低優(yōu)先級(jí)隊(duì)列。

none:這個(gè)模式不使用高優(yōu)先級(jí)隊(duì)列。

  • thread_pool_high_prio_tickets

該參數(shù)控制每個(gè)連接最多語(yǔ)序多少次被放入高優(yōu)先級(jí)隊(duì)列中,默認(rèn)為4294967295,注意這個(gè)參數(shù)只有在thread_pool_high_prio_mode為transactions的時(shí)候才有效果。

  • thread_pool_idle_timeout

worker線程最大空閑時(shí)間,默認(rèn)為60秒,超過(guò)限制后會(huì)退出。

  • thread_pool_max_threads

該參數(shù)用來(lái)限制線程池最大的線程數(shù),超過(guò)該限制后將無(wú)法再創(chuàng)建更多的線程,默認(rèn)為100000。

  • thread_pool_stall_limit

該參數(shù)設(shè)置timer線程的檢測(cè)group是否異常的時(shí)間間隔,默認(rèn)為500ms。

三、MySQL線程池的使用

線程池的使用比較簡(jiǎn)單,只需要添加配置后重啟實(shí)例即可。

具體配置如下:

#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

備注:其他參數(shù)默認(rèn)即可

以上具體的參數(shù)在前面已做詳細(xì)說(shuō)明,下面是配置中需要注意的兩個(gè)點(diǎn):

1、之所以添加performance_schema=off,是由于測(cè)試過(guò)程中發(fā)現(xiàn)Thread pool和PS同時(shí)開(kāi)啟的時(shí)候會(huì)出現(xiàn)內(nèi)存泄漏問(wèn)題(后文會(huì)詳細(xì)敘述);

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

重啟實(shí)例后,可以通過(guò)show variables like '%thread%';來(lái)查看配置的參數(shù)是否生效。

四、使用中遇到的問(wèn)題

在使用線程池的過(guò)程中,我遇到了幾個(gè)問(wèn)題,這里也順便做個(gè)總結(jié):

內(nèi)存泄漏問(wèn)題

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

不但啟用線程池后內(nèi)存飆升了8G左右,而且內(nèi)存還在持續(xù)增長(zhǎng),很明顯啟用線程池后存在內(nèi)存泄漏問(wèn)題了。

網(wǎng)上也有不少的人遇到這個(gè)問(wèn)題,確認(rèn)是percona的bug導(dǎo)致(jira.percona.com/browse/PS-3…

下面是關(guān)閉PS后的內(nèi)存使用情況對(duì)比:

備注:目前Percona server 5.7.21-20版本已經(jīng)修復(fù)了線程池和PS同時(shí)打開(kāi)內(nèi)存泄漏的問(wèn)題,從我測(cè)試的情況來(lái)看問(wèn)題也得到了解決,大家可以直接使用Percona server 5.7.21-20的版本,如下圖。

撥測(cè)異常問(wèn)題

啟用線程池以后,相當(dāng)于限制了MySQL的并發(fā)線程數(shù),當(dāng)達(dá)到最大線程數(shù)的時(shí)候,其他的線程需要等待,新連接也會(huì)卡在連接驗(yàn)證那一步,這時(shí)候會(huì)造成撥測(cè)程序連接MySQL超時(shí),撥測(cè)返回錯(cuò)誤如下:

撥測(cè)程序連接實(shí)例超時(shí)后,就會(huì)認(rèn)為master已經(jīng)出現(xiàn)問(wèn)題。極端情況下,重試多次都有異常后,就啟動(dòng)自動(dòng)切換的操作,將業(yè)務(wù)切換到從機(jī)。

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

1、啟用MySQL的旁路管理端口,監(jiān)控和高可用相關(guān)直接使用MySQL的旁路管理端口。

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

extra_max_connections = 8

extra_port = 33333

備注:建議啟用線程池后,把這個(gè)也添加上,方便緊急情況下進(jìn)行故障處理。

2、修改高可用探測(cè)腳本,將達(dá)到線程池最大活動(dòng)線程數(shù)返回的錯(cuò)誤做異常處理,當(dāng)作超過(guò)最大連接數(shù)的場(chǎng)景。(備注:超過(guò)最大連接數(shù)只告警,不進(jìn)行自動(dòng)切換)

慢SQL引入的問(wèn)題

隨著對(duì)撥測(cè)超時(shí)的問(wèn)題的深入分析,線程池滿只是監(jiān)控?fù)軠y(cè)出現(xiàn)超時(shí)的其中一種情況,還有一種情況是線程池并沒(méi)有滿,線上的兩個(gè)配置:

thread_pool_oversubscribe=3

thread_pool_size=24

按照上面的兩個(gè)配置來(lái)計(jì)算的話,總共能并發(fā)運(yùn)行24x(3+1)=96,但是根據(jù)多次問(wèn)題的追中,發(fā)現(xiàn)有多次線程池并沒(méi)有達(dá)到96,也就是說(shuō)整體的線程池并沒(méi)有滿。那會(huì)是什么問(wèn)題導(dǎo)致?lián)軠y(cè)失敗呢?

鑒于線程池的結(jié)構(gòu)和分配機(jī)制,通過(guò)前面線程池部分的描述,大家都知道了在內(nèi)部是將線程池分成一個(gè)一個(gè)的group,我們線上配置了24個(gè)group,而線程池的分配機(jī)制是對(duì)Threadid進(jìn)行取模,然后確定該線程是落在哪個(gè)group。

出現(xiàn)超時(shí)的時(shí)候,有很多的load線程到導(dǎo)入數(shù)據(jù)。也就是說(shuō)那個(gè)時(shí)候有部分線程比較慢的情況。那么會(huì)不會(huì)是某個(gè)group的線程滿了,從而導(dǎo)致新分配的線程等待?

有了這個(gè)猜想以后,接下來(lái)就是來(lái)驗(yàn)證這個(gè)問(wèn)題。驗(yàn)證分為兩步:

1、抓取線上運(yùn)行的processlist,然后對(duì)threadid取模,看看是否有多個(gè)load線程落在同一個(gè)group的情況;

2、在測(cè)試環(huán)境模擬這種場(chǎng)景,看看是否符合預(yù)期。

線上場(chǎng)景分析

先來(lái)看線上的場(chǎng)景,通過(guò)抓取撥測(cè)超時(shí)時(shí)間點(diǎn)的processlist,找出當(dāng)時(shí)正在load的線程,根據(jù)threadid進(jìn)行去模,并進(jìn)行匯總統(tǒng)計(jì)后,得出如下結(jié)果:

可以看出,當(dāng)時(shí)第4和第7個(gè)group的請(qǐng)求個(gè)數(shù)都超過(guò)了4個(gè),說(shuō)明是單個(gè)group滿導(dǎo)致的撥測(cè)異常。當(dāng)然,也會(huì)導(dǎo)致部分運(yùn)行很快的SQL變慢。

測(cè)試環(huán)境模擬場(chǎng)景分析

為了構(gòu)建快速重現(xiàn)環(huán)境,我將參數(shù)調(diào)整如下:

thread_pool_oversubscribe=1

thread_pool_size=2

通過(guò)上面參數(shù)的調(diào)整,可以計(jì)算出最大并發(fā)線程為2x(1+1)=4,如下圖,當(dāng)活動(dòng)線程數(shù)超過(guò)4個(gè)后,其他的線程就必須等待:

我模擬線上環(huán)境的方法為開(kāi)啟1個(gè)線程的慢SQL,這時(shí)測(cè)試環(huán)境的線程池情況如下:

按照之前的推測(cè),這時(shí)Group1的處理能力相當(dāng)于Group2的處理能力的50%,如果之前的推論是正確的,那么分配在Group1上的線程就會(huì)出現(xiàn)阻塞。

比如此時(shí)來(lái)了20個(gè)線程請(qǐng)求,按照線程池的分配原則,此時(shí)Group1和Group2都會(huì)分到10個(gè)線程請(qǐng)求。如果所有的線程請(qǐng)求耗時(shí)都是一樣的,那么分配到Group1上的線程請(qǐng)求整體處理時(shí)間應(yīng)該是分配到Group2上整體處理時(shí)間的2倍。

我使用腳本,并發(fā)起12個(gè)線程請(qǐng)求,每個(gè)線程請(qǐng)求都運(yùn)行select sleep(2),那么在Group1和Group2都空閑的情況下,運(yùn)行情況如下:

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個(gè)線程,總共運(yùn)行了6秒。

接下來(lái)在Group1被1個(gè)長(zhǎng)時(shí)間運(yùn)行的線程沾滿以后,看看測(cè)試結(jié)果是怎么樣的:

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

從上面的結(jié)果中可以看出,在沒(méi)有阻塞的時(shí)候,每次都是4個(gè)線程,而后面有1個(gè)線程長(zhǎng)時(shí)間運(yùn)行的時(shí)候,就會(huì)出現(xiàn)那個(gè)長(zhǎng)時(shí)間線程對(duì)應(yīng)的group出現(xiàn)排隊(duì)的情況,最后雖然有3個(gè)空閑的線程,但是卻只有1個(gè)線程在處理(標(biāo)紅部分結(jié)果)。

解決方法有兩個(gè):

1、將thread_pool_oversubscribe適當(dāng)調(diào)大,這個(gè)辦法只能緩解類(lèi)似問(wèn)題,無(wú)法根治;

2、找到慢的SQL,解決慢的問(wèn)題。

與50位技術(shù)專(zhuān)家面對(duì)面20年技術(shù)見(jiàn)證,附贈(zèng)技術(shù)全景圖

總結(jié)

以上是生活随笔為你收集整理的关于MySQL线程池,这也许是目前最全面的实用帖!(转载)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。