白话Elasticsearch67-不随意调节jvm和thread pool的原因jvm和服务器内存分配的最佳实践
文章目錄
- 概述
- 不隨意調(diào)節(jié)jvm和thread pool的原因
- jvm gc
- threadpool
- jvm和服務(wù)器內(nèi)存分配的最佳實(shí)踐
- jvm heap分配
- 將機(jī)器上少于一半的內(nèi)存分配給es
- 為什么不要給jvm分配超過(guò)32G內(nèi)存?
- 在32G以內(nèi)的話具體應(yīng)該設(shè)置heap為多大?
- 對(duì)于有1TB內(nèi)存的超大內(nèi)存機(jī)器該如何分配?
- swapping
概述
繼續(xù)跟中華石杉老師學(xué)習(xí)ES,第67篇
課程地址: https://www.roncoo.com/view/55
不隨意調(diào)節(jié)jvm和thread pool的原因
es中有很多的配置都讓大家忍不住去調(diào)優(yōu),因?yàn)橐苍S大家都太過(guò)于迷戀性能優(yōu)化了,都認(rèn)為優(yōu)化一些配置可以大幅度提升性能,就感覺(jué)性能調(diào)優(yōu)像個(gè)魔法一樣,是個(gè)萬(wàn)能的東西。但是其實(shí)99.99%的情況下,對(duì)于es來(lái)說(shuō),大部分的參數(shù)都保留為默認(rèn)的就可以了。因?yàn)檫@些參數(shù)經(jīng)常被濫用和錯(cuò)誤的調(diào)節(jié),繼而導(dǎo)致嚴(yán)重的穩(wěn)定性問(wèn)題以及性能的急劇下降。
jvm gc
jvm使用垃圾回收器來(lái)釋放掉不用的內(nèi)存,千萬(wàn)不要去調(diào)節(jié)默認(rèn)的垃圾回收行為。es默認(rèn)用的垃圾回收器是CMS。CMS回收器是并發(fā)式的回收器,能夠跟應(yīng)用程序工作線程并發(fā)工作,最大程度減少垃圾回收時(shí)的服務(wù)停頓時(shí)間。但是CMS還是會(huì)有兩個(gè)停頓階段,同時(shí)在回收特別大的heap時(shí)也會(huì)有一些問(wèn)題。盡管有一些缺點(diǎn),但是CMS對(duì)于要求低延時(shí)請(qǐng)求響應(yīng)的軟件來(lái)說(shuō),還是最佳的垃圾回收器,因此官方的推薦就是使用CMS垃圾回收器。
有一種最新的垃圾回收器叫做G1。G1回收器可以比CMS提供更少的回收停頓時(shí)間,而且能夠這對(duì)大heap有更好的回收表現(xiàn)。它會(huì)將heap劃分為多個(gè)region,然后自動(dòng)預(yù)測(cè)哪個(gè)region會(huì)有最多可以回收的空間。通過(guò)回收那些region,就可以最小化停頓時(shí)長(zhǎng),而且可以針對(duì)大heap進(jìn)行回收。
聽起來(lái)還挺不錯(cuò)的,但是不幸的是,G1還是比較年輕的一種垃圾回收器,而且經(jīng)常會(huì)發(fā)現(xiàn)一些新的bug,這些bug可能會(huì)導(dǎo)致jvm掛掉。lucene的測(cè)試套件就檢查出來(lái)了G1的一些bug。因此es官方不推薦現(xiàn)在使用G1垃圾回收器,也許在不久的未來(lái),等G1更加穩(wěn)定的時(shí)候,可以使用G1。
threadpool
每個(gè)人都很喜歡去調(diào)優(yōu)線程池,而且大部分人都特別喜歡增加線程池的線程數(shù)量,無(wú)論是大量的寫入,還是大量的搜索,或者是感覺(jué)服務(wù)器的cpu idle空閑率太高,都會(huì)增加更多的線程。在es中,默認(rèn)的threadpool設(shè)置是非常合理的,對(duì)于所有的threadpool來(lái)說(shuō),除了搜索的線程池,都是線程數(shù)量設(shè)置的跟cpu core一樣多的。
如果我們有8個(gè)cpu core,那么就可以并行運(yùn)行8個(gè)線程。那么對(duì)于大部分的線程池來(lái)說(shuō),分配8個(gè)線程就是最合理的數(shù)量。
不過(guò)搜索會(huì)有一個(gè)更加大的threadpool,一般被配置為:cpu core * 3 / 2 + 1。
也許我們會(huì)覺(jué)得有些線程可能會(huì)因?yàn)榇疟PIO等操作block住,所以我們需要更多的線程。但是在es中這并不是一個(gè)問(wèn)題,大多數(shù)的磁盤IO操作都是由lucene的線程管理的,而不是由es管理的,因此es的線程不需要關(guān)心這個(gè)問(wèn)題。
此外,threadpool還會(huì)通過(guò)在彼此之間傳遞任務(wù)來(lái)協(xié)作執(zhí)行,我們不需要擔(dān)心某一個(gè)網(wǎng)絡(luò)線程會(huì)因?yàn)榈却淮未疟P寫操作,而導(dǎo)致自己被block住,無(wú)法處理網(wǎng)絡(luò)請(qǐng)求。
網(wǎng)絡(luò)線程可以將那個(gè)磁盤寫操作交給其他線程池去執(zhí)行,然后自己接著回來(lái)處理網(wǎng)絡(luò)請(qǐng)求。
其實(shí)我們的進(jìn)程的計(jì)算能力是有限的,分配更多的線程只會(huì)強(qiáng)迫cpu在多個(gè)線程上下文之間頻繁來(lái)回切換。
一個(gè)cpu core在同一時(shí)間只能運(yùn)行一條線程,所以如果cpu要切換到另外一個(gè)線程去執(zhí)行,需要將當(dāng)前的state保存起來(lái),然后加載其他的線程進(jìn)來(lái)執(zhí)行。如果線程上下文切換發(fā)生在一個(gè)cpu core內(nèi),那么還好一些,但是如果在多個(gè)cpu core之間發(fā)生線程上下文切換,那么還需要走一個(gè)cpu core內(nèi)部的通信。這種線程上下文切換會(huì)消耗掉很多的cpu資源,對(duì)于現(xiàn)在的cpu來(lái)說(shuō),每次線程上下文切換,都會(huì)導(dǎo)致30微秒的時(shí)間開銷,所以寧愿將這些時(shí)間花費(fèi)在任務(wù)的處理上。
很多人會(huì)將threadpool大小設(shè)置為一些很愚蠢的數(shù)值,在一個(gè)8核的機(jī)器上,可能運(yùn)行了超過(guò)60,100,甚至1000個(gè)線程。這么多的線程會(huì)導(dǎo)致cpu資源利用率很低。所以下次如果我們要調(diào)節(jié)線程池的話,記住,千萬(wàn)別這么干。如果一定要調(diào)節(jié)線程數(shù)量,也得記住要根據(jù)你的cpu core數(shù)量來(lái)調(diào)節(jié),比如設(shè)置為cpu core的兩倍,如果設(shè)置的再多,那么就是一種浪費(fèi)了。
jvm和服務(wù)器內(nèi)存分配的最佳實(shí)踐
除了之前講解的一些配置,根據(jù)你的集群環(huán)境特殊的配置,我們這一講來(lái)講解最重要的內(nèi)存的分配,提出一些問(wèn)題,生產(chǎn)環(huán)境部署es,不可避免要回答一個(gè)問(wèn)題,比如我的機(jī)器上有64G的內(nèi)存,或者32G的內(nèi)存,那么一般來(lái)說(shuō)我應(yīng)該分配多少個(gè)G的內(nèi)存給es的jvm heap
jvm heap分配
如果用es默認(rèn)的heap size,那么生產(chǎn)環(huán)境的集群肯定表現(xiàn)不會(huì)太好。
有兩個(gè)方式來(lái)調(diào)節(jié)es中的jvm heap size。最簡(jiǎn)單的就是設(shè)置環(huán)境變量,ES_HEAP_SIZE。當(dāng)es進(jìn)程啟動(dòng)的時(shí)候,會(huì)讀取這個(gè)環(huán)境變量的值,然后設(shè)置為jvm的heap size。舉例來(lái)說(shuō),可以這樣來(lái)設(shè)置:export ES_HEAP_SIZE=10g。此外,還可以在啟動(dòng)es進(jìn)程的時(shí)候,傳遞一個(gè)jvm的option,比如:ES_JAVA_OPTS="-Xms10g -Xmx10g" ./bin/elasticsearch,但是要注意-Xms和-Xmx最小和最大堆內(nèi)存一定設(shè)置的一樣,避免運(yùn)行過(guò)程中的jvm heap resize,那會(huì)是一個(gè)非常耗時(shí)的過(guò)程。
在老版本的es中,比如es 2.x里面,一般推薦用ES_HEAP_SIZE環(huán)境變量的方式來(lái)設(shè)置jvm heap size。
在新版本的es中,比如es 5.x里面,一般推薦在jvm.options文件里面去設(shè)置jvm相關(guān)的參數(shù)。
將機(jī)器上少于一半的內(nèi)存分配給es
一個(gè)常見的問(wèn)題就是將es進(jìn)程的jvm heap size設(shè)置的過(guò)于大了。比如我們有一臺(tái)64G的機(jī)器,可能我們甚至想要給es jvm size設(shè)置64G內(nèi)存。但是這是錯(cuò)誤的。大家可能會(huì)覺(jué)得說(shuō),直接將機(jī)器上的可用的內(nèi)存都分配給es jvm heap,性能是絕對(duì)高的,因?yàn)榇罅康臄?shù)據(jù)都可以緩存在內(nèi)存里面。
雖然heap對(duì)于es來(lái)說(shuō)是非常重要的,jvm heap被es用來(lái)存放很多內(nèi)存中的數(shù)據(jù)結(jié)構(gòu)來(lái)提供更快的操作性能。
但是還有另外一個(gè)內(nèi)存的用戶,那就是lucene。lucene的設(shè)計(jì)就是要使用底層的os filesystem cache來(lái)緩存數(shù)據(jù)結(jié)構(gòu)。lucene的segment是保存在單獨(dú)的文件中的。因?yàn)檫@些segment是不可變的,所以這些文件實(shí)際上也從來(lái)不會(huì)改變。這樣的話,就可以更好的緩存這些文件,底層的os cache會(huì)將hot segment駐留在內(nèi)存中以供更快的訪問(wèn)。這些segment包括了倒排索引(為了全文檢索)以及正排索引(為了聚合操作)。
lucene的性能是嚴(yán)重依賴于底層的os的,但是如果我們給了過(guò)多的內(nèi)存到es的jvm heap,那么就沒(méi)有足夠的內(nèi)存留給lucene。這會(huì)極大的影響性能。
這里想告訴大家的是,就是說(shuō),es的性能很大的一塊,其實(shí)是由有多少內(nèi)存留給操作系統(tǒng)的os cache,供lucene去緩存索引文件,來(lái)決定的。所以說(shuō)lucene的os cache有多少是非常重要的。
一般建議的是,將50%的內(nèi)存分配給es jvm heap,然后留50%的內(nèi)存給os cache。留給os cache的內(nèi)存是不會(huì)不使用的,lucene會(huì)將剩下的內(nèi)存全部用光,用來(lái)cache segment file。
如果我們沒(méi)有對(duì)任何分詞的text field進(jìn)行聚合操作,那么我們就不需要使用fielddata,我們甚至可以考慮給os cache更多的內(nèi)存,因?yàn)閒ielddata是要用jvm heap。如果我們給jvm heap更少的內(nèi)存,那么實(shí)際上es的性能反而會(huì)更好,因?yàn)楦嗟膬?nèi)存留給了lucene用os cache提升索引讀寫性能,同時(shí)es的jvm heap的gc耗時(shí)會(huì)更少。
es部署的機(jī)器上,內(nèi)存是如何分配的,如何使用的,如何決定我們的操作系統(tǒng)的,我們?cè)撊绾谓ojvm和os cache分配內(nèi)存
為什么不要給jvm分配超過(guò)32G內(nèi)存?
還有另外一個(gè)原因不要將過(guò)多的內(nèi)存分配給es的jvm heap。如果heap小于32G的化,jvm會(huì)用一種技術(shù)來(lái)壓縮對(duì)象的指針,object pointer。在java中,所有的對(duì)象都會(huì)被分配到heap中,然后被一個(gè)pointer給引用。object pointer會(huì)指向heap中的對(duì)象,引用的是二進(jìn)制格式的地址。
對(duì)于32位的系統(tǒng)來(lái)說(shuō),jvm最大的heap size就是4G,解釋一下,32位,0和1值,0和1在32位的組合是2^32次方的字節(jié),除以1024就是多少k,再除以1024就是多少mb,再除以1024就是多少gb,最后算下來(lái)就是4G。
對(duì)于64位的系統(tǒng)來(lái)說(shuō),heap size可以更大,但是64位的object pointer會(huì)耗費(fèi)更多的空間,因?yàn)閛bject pointer更大了。比浪費(fèi)更多內(nèi)存空間更惡劣的是,過(guò)大的object pointer會(huì)在cpu,main memory和LLC、L1等多級(jí)緩存間移動(dòng)數(shù)據(jù)的時(shí)候,吃掉更多的帶寬。
所以jvm用了一種技術(shù),叫做compressed oops來(lái)解決object pointer耗費(fèi)過(guò)大空間的問(wèn)題。這個(gè)技術(shù)的核心思想是,不要讓object pointer引用內(nèi)存中的二進(jìn)制地址,而是讓object pointer引用object offset。
這就意味著32位的pointer可以引用400萬(wàn)個(gè)對(duì)象,而不是400萬(wàn)字節(jié)。這也意味著,使用32位的pointer,最大的heap大小可以到32G。此時(shí)只要heap size在32G以內(nèi),jvm就會(huì)自動(dòng)啟用32位的object pointer,因?yàn)?2位的對(duì)象指針,足夠引用32G的內(nèi)存了,就可以用32位的pointer替代64位的pointer。但是32位的pointer比64位的pointer可以耗費(fèi)更少的內(nèi)存耗費(fèi)。
如果你給jvm heap分配的內(nèi)存小于32G,此時(shí)jvm會(huì)自動(dòng)使用32位的object pointer,同時(shí)是讓pointer指向?qū)ο蟮膐ffset,32位的object pointer就足以引用32G的內(nèi)存,同時(shí)32位的pointer占用的內(nèi)存空間很少,對(duì)cpu和memory之間移動(dòng)數(shù)據(jù)的帶寬開銷也很少。這個(gè)過(guò)程就叫做compressed oops。
但是一旦我們?cè)竭^(guò)了32G這個(gè)界限,就是給jvm heap分配了超過(guò)32G的內(nèi)存,比較坑了。就沒(méi)有辦法用32位的pointer+引用object offset的模式了,因?yàn)?2位的pointer最多引用32G的內(nèi)存,超過(guò)了32G,就沒(méi)法用32位pointer。不用32位pointer,就只能用64位pointer,才能引用超過(guò)32G的內(nèi)存空間。此時(shí)pointer就會(huì)退回到傳統(tǒng)的object pointer引用對(duì)象的二進(jìn)制地址的模式,此時(shí)object pinter的大小會(huì)急劇增長(zhǎng),更多的cpu到內(nèi)存的帶寬會(huì)被占據(jù),更多的內(nèi)存被耗費(fèi)。實(shí)際上,不用compressed oops時(shí),你如果給jvm heap分配了一個(gè)40~50G的內(nèi)存的可用空間,實(shí)際上被object pointer可能都要占據(jù)十幾G的內(nèi)存空間,可用的空間量,可能跟使用了compressed oops時(shí)的32GB內(nèi)存的可用空間,20多個(gè)G,幾乎是一樣的。
因此,即使我們有很多內(nèi)存,但是還是要分配給heap在32GB以內(nèi),否則的話浪費(fèi)更多的內(nèi)存,降低cpu性能,而且會(huì)讓jvm回收更大的heap。
綜上所述,如果你給jvm heap分配超過(guò)32G的內(nèi)存,實(shí)際上是沒(méi)有什么意義的,因?yàn)橛?4位的pointer,1/3的內(nèi)存都給object pointer給占據(jù)了,這段內(nèi)存空間就浪費(fèi)掉了。還不如分配32G以內(nèi),啟用compressed oops,可用空間跟你分配50個(gè)G的內(nèi)存,是一樣的。
所以也正是因?yàn)?2G的限制,一般來(lái)說(shuō),都是建議說(shuō),如果你的es要處理的數(shù)據(jù)量上億的話,幾億,或者十億以內(nèi)的規(guī)模的話,建議,就是用64G的內(nèi)存的機(jī)器比較合適,有個(gè)5臺(tái),差不多也夠了。給jvm heap分配32G,留下32G給os cache。
在32G以內(nèi)的話具體應(yīng)該設(shè)置heap為多大?
這個(gè)是根據(jù)具體情況而定的,不是固定死的,根據(jù)不同的jvm和平臺(tái)而變。一般而言,將jvm heap size設(shè)置為31G比較安全一些。主要是要確保說(shuō),你設(shè)置的這個(gè)jvm heap大小,可以讓es啟用compressed oops這種優(yōu)化機(jī)制。此外,可以給jvm option加入-XX:+PrintFlagsFinal,然后可以打印出來(lái)UseCompressedOops是否為true。這就可以讓我們找到最佳的內(nèi)存設(shè)置。因?yàn)榭梢圆粩嗾{(diào)節(jié)內(nèi)存大小,然后觀察是否啟用compressed oops。
舉例來(lái)說(shuō),如果在mac os上啟動(dòng)一個(gè)java 1.7,同時(shí)將heap size設(shè)置為32600mb,那么compressed oops是會(huì)開啟的;但是如果設(shè)置為32766m,compressed oops就不會(huì)開啟。相反的是,使用jdk 1.8的化,分配32766m,compressed oops是會(huì)開啟的,設(shè)置為32767m,就不會(huì)開啟。所以說(shuō),這個(gè)東西不是固定的。根據(jù)不同的操作系統(tǒng)以及jvm版本而定。
在es啟動(dòng)日志中,我們可以查看compressed oops是否開啟,比如下面的字樣:[2015-12-16 13:53:33,417][INFO ][env] [Illyana Rasputin] heap size [989.8mb], compressed ordinary object pointers [true]。
對(duì)于有1TB內(nèi)存的超大內(nèi)存機(jī)器該如何分配?
如果我們的機(jī)器是一臺(tái)超級(jí)服務(wù)器,內(nèi)存資源甚至達(dá)到了1TB,或者512G,128G,該怎么辦?首先es官方是建議避免用這種超級(jí)服務(wù)器來(lái)部署es集群的,但是如果我們只有這種機(jī)器可以用的話,我們要考慮以下幾點(diǎn):
(1)我們是否在做大量的全文檢索?
考慮一下分配4~32G的內(nèi)存給es進(jìn)程,同時(shí)給lucene留下其余所有的內(nèi)存用來(lái)做os filesystem cache。所有的剩余的內(nèi)存都會(huì)用來(lái)cache segment file,而且可以提供非常高性能的搜索,幾乎所有的數(shù)據(jù)都是可以在內(nèi)存中緩存的,es集群的性能會(huì)非常高
(2)是否在做大量的排序或者聚合操作?
聚合操作是不是針對(duì)數(shù)字、日期或者未分詞的string?如果是的話,那么還是給es 4~32G的內(nèi)存即可,其他的留給es filesystem cache,可以將聚合好用的正排索引,doc values放在os cache中
(3)是否針對(duì)分詞的string做大量的排序或聚合操作?
如果是的話,那么就需要使用fielddata,這就得給jvm heap分配更大的內(nèi)存空間。此時(shí)不建議運(yùn)行一個(gè)節(jié)點(diǎn)在機(jī)器上,而是運(yùn)行多個(gè)節(jié)點(diǎn)在一臺(tái)機(jī)器上,那么如果我們的服務(wù)器有128G的內(nèi)存,可以運(yùn)行兩個(gè)es節(jié)點(diǎn),然后每個(gè)節(jié)點(diǎn)分配32G的內(nèi)存,剩下64G留給os cache。如果在一臺(tái)機(jī)器上運(yùn)行多個(gè)es node,建議設(shè)置:cluster.routing.allocation.same_shard.host: true。這會(huì)避免在同一臺(tái)物理機(jī)上分配一個(gè)primary shard和它的replica shard。
swapping
如果頻繁的將es進(jìn)程的內(nèi)存swap到磁盤上,絕對(duì)會(huì)是一個(gè)服務(wù)器的性能殺手。想象一下,內(nèi)存中的操作都是要求快速完成的,如果需要將內(nèi)存頁(yè)的數(shù)據(jù)從磁盤swap回main memory的化,性能會(huì)有多差。如果內(nèi)存被swap到了磁盤,那么100微秒的操作會(huì)瞬間變成10毫秒,那么如果是大量的這種內(nèi)存操作呢?這會(huì)導(dǎo)致性能急劇下降。
因此通常建議徹底關(guān)閉機(jī)器上的swap,swapoff -a,如果要永久性關(guān)閉,需要在/etc/fstab中配置
如果沒(méi)法完全關(guān)閉swap,那么可以嘗試調(diào)低swappiness至,這個(gè)值是控制os會(huì)如何將內(nèi)存swap到磁盤的。這會(huì)在正常情況下阻止swap,但是在緊急情況下,還是會(huì)swap。一般用sysctl來(lái)設(shè)置,vm.swappiness = 1。如果swappiness也不能設(shè)置,那么就需要啟用mlockall,這就可以讓我們的jvm lock住自己的內(nèi)存不被swap到磁盤上去,在elasticsearch.yml中可以設(shè)置:bootstrap.mlockall: true。
總結(jié)
以上是生活随笔為你收集整理的白话Elasticsearch67-不随意调节jvm和thread pool的原因jvm和服务器内存分配的最佳实践的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 白话Elasticsearch66-针对
- 下一篇: 白话Elasticsearch68-ES