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