ElasticSearch線程池設(shè)計

前言

ES的操作中充斥著不同的操作任務(wù)類型,如Index、Search、刷新合并、recover等等。各種類型任務(wù)之間不但有著不同的實效性的要求(如有的任務(wù)要求盡快完成,而另外一些類型任務(wù)可能不要求),而且CPU資源消耗也可能因為應(yīng)用場景不同而有區(qū)別(如ELK是一種寫多的應(yīng)用場景,而站內(nèi)搜索可能是寫少讀多的應(yīng)用場景)。

在Java線程池框架的基礎(chǔ)上,ES擴展設(shè)計了自己的線程池方案。首先,ES中線程池的類結(jié)構(gòu)如圖:
ES線程池類圖

基于這種結(jié)構(gòu),ES定義了若干種類型的線程池,下面分開一一介紹。

Fixed類型線程池

Fixed類型線程池基于EsThreadPoolExecutor創(chuàng)建,是EsThreadPoolExecutor的實例。這種類型的線程池的特點是線程數(shù)固定,任務(wù)隊列大小可配
??1.當(dāng)隊列大小配置為-1時,任務(wù)隊列是一種無界隊列,基于Java的LinkedTransferQueue實現(xiàn)。
??2.當(dāng)配置大小非負時,任務(wù)隊列是有界隊列,是在LinkedTransferQueue基礎(chǔ)上增加了size管控的自定義隊列(SizeBlockingQueue類型)。

怎么理解線程數(shù)固定呢?在Java標(biāo)準(zhǔn)的線程池里面,主要有3個重要的參數(shù)會影響線程數(shù)的變化:corePoolSize、maximumPoolSize和keepAliveTime。
??1.corePoolSize,線程池剛創(chuàng)建時,線程數(shù)量為0,當(dāng)每次執(zhí)行execute添加新的任務(wù)時會在線程池創(chuàng)建一個新的線程,直到線程數(shù)量達到corePoolSize為止。
??2.maximumPoolSize,當(dāng)任務(wù)隊列已滿,放不下新的任務(wù),再添加新的任務(wù)則線程池會再創(chuàng)建新的線程,線程數(shù)量大于corePoolSize但不會超過maximumPoolSize。
??3.keepAliveTime,當(dāng)線程數(shù)量大于corePoolSize時,如果有線程空閑時間超過keepAliveTime,則線程會被銷毀,最終保證線程池線程個數(shù)為corePoolSize。

在ES的Fixed類型線程池中,corePoolSize和maximumPoolSize設(shè)置的值一樣,keepAliveTime設(shè)置的值為0。根據(jù)上面描述,開始提交任務(wù)時,會不斷創(chuàng)建新的線程,直至線程數(shù)達到配置的線程數(shù)。

在這之后,再創(chuàng)建任務(wù),如果任務(wù)隊列沒滿,任務(wù)會進入任務(wù)隊列;如果隊列滿了,再提交任務(wù)會被阻塞。因此,此時不管隊列滿不滿,都不會再創(chuàng)建新的線程,線程數(shù)不會增加。當(dāng)不無任務(wù)可做導(dǎo)致線程空閑時,也不會銷毀和回收線程,線程數(shù)也不會減少

Fixed_auto_queue_size類型線程池

此種類型線程池基于QueueResizingEsThreadPoolExecutor實現(xiàn)。這種類型線程池和Fixed類型線程池很類似,線程數(shù)固定,但是不允許用無界任務(wù)隊列。任務(wù)隊列(ResizableBlockingQueue)的容量根據(jù)利特尓法則(Little's Law)不斷調(diào)整,以適應(yīng)系統(tǒng)不斷變化的負載情況。

Little's Law: L = λW, 其中λ計算方式:單位時間內(nèi)進來的task個數(shù)。W是配置的單個task期望處理時間(target_response_time)。根據(jù)此公式計算出來的L就是理想的Queue 的容量。因此,如果任務(wù)平均的響應(yīng)時間超過target_response_time的話,任務(wù)隊列容量會減少以控制進入線程池的任務(wù)個數(shù)。

那么什么時候來計算和調(diào)整呢?答案是每次提交的任務(wù)執(zhí)行完后(afterExecute),判斷已經(jīng)執(zhí)行的任務(wù)個數(shù)是否達到了配置的每幀任務(wù)數(shù)(auto_queue_frame_size)。如果是,就開始計算并調(diào)整。

任務(wù)隊列的容量調(diào)整也是參數(shù)可配的。queue_size 參數(shù)配置任務(wù)隊列的初始容量。min_queue_size 配置了任務(wù)隊列的容量下限,max_queue_size 配置了任務(wù)隊列的容量上限。如下是一個Search操作(該操作使用就是本類型線程池)的線程池參數(shù)配置:

thread_pool:
?? search:
???? size: 30
???? queue_size: 500
???? min_queue_size: 10
???? max_queue_size: 1000
???? auto_queue_frame_size: 2000
???? target_response_time: 1s

Scaling類型線程池

Scaling類型線程池基于EsThreadPoolExecutor創(chuàng)建。這種類型線程池和Java標(biāo)準(zhǔn)的線程池有點類似,也有三類參數(shù)corePoolSize、maximumPoolSize和keepAliveTime,但是線程創(chuàng)建和任務(wù)進入隊列行為卻有區(qū)別:
??1.當(dāng)線程池個數(shù)低于corePoolSize時,每次提交新任務(wù)都會觸發(fā)新的線程創(chuàng)建。當(dāng)線程池個數(shù)超過corePoolSize且任務(wù)隊列未滿時,再有新任務(wù)提交,java標(biāo)準(zhǔn)線程池會把任務(wù)加入到隊列里面。
??2.ES里面的Scaling類型線程池會首先檢查創(chuàng)建的線程數(shù)是否達到了maximumPoolSize,如果沒有,會繼續(xù)創(chuàng)建新的線程。在創(chuàng)建的線程數(shù)達到了maximumPoolSize,新提交的任務(wù)才會進入任務(wù)隊列。
??3. 線程空閑時間超過keepAliveTime且線程數(shù)大于corePoolSize,線程都會被銷毀。Java線程池是隊列滿了才創(chuàng)建了多余的線程,而Scaling是先創(chuàng)建的線程后入的隊列。在某種情況下,Scaling的線程相對來說,更容易空閑一些。

Direct類型線程池

基于AbstractExecutorService的內(nèi)部匿名類實現(xiàn),特點是不支持shutdown。

Prioritized 類型線程池

基于PrioritizedEsThreadPoolExecutor實現(xiàn),此類型線程池線程個數(shù)固定為1。提交的任務(wù),會被按照優(yōu)先級的順序來執(zhí)行。如果兩個任務(wù)有相同的優(yōu)先級,先到的任務(wù)先執(zhí)行。

總結(jié)

基于上面介紹的幾種線程池類型,ES定義了若干線程池的實例并用于不同的業(yè)務(wù)操作,如下圖:


不同操作的線程池
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容