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