記一次Elasticsearch優化總結
專案中的服務集成了springboot-admin做服務監控,最近一直收到郵件告警,提示es出錯。錯誤資訊如下:
org.elasticsearch.ElasticsearchTimeoutException: java.util.concurrent.TimeoutException: Timeout waiting for task. 複製程式碼
頻繁收到這個告警,所以決定花時間研究一下。從報錯資訊看,併發超時異常。ES作為java開發的中介軟體,我們沒有對任何程式碼做過修改,所以就從JVM開始著手嘗試解決,同時還涉及到部分ES知識和springboot的知識。
二. JVM知識回顧
可參考另一篇學習筆記:深入理解java虛擬機器
1. JVM記憶體模型
- JVM gc的物件:堆
2. 堆記憶體
2.1 堆記憶體劃分
- 堆區分為新生代和老年代
- 新生代又分為Eden區,from survivor區,to survivor區
- Eden區和兩塊較小的survivor空間。大小比例為8:1:1
- java8已經沒有持久代了,改為元資料區,主要存放元資料,例如Class、Method的元資訊,與垃圾回收要回收的Java物件關係不大
2.2 堆記憶體檢視
使用 jstat -gc(-gccapacity, -gcutil)命令檢視堆分配情況
- S0C:survivor0區總記憶體大小(Capacity)
- S1C: survivor0區總記憶體大小
- S0U: survivor0區當前記憶體大小(Used)
- S0U: survivor1區當前記憶體大小
- EC:Eden區總記憶體大小
- EU:survivor1區當前記憶體大小
- OC:老年代總記憶體大小
- OU:老年代當前記憶體大小
- MC:meta data區總記憶體大小
- MU:survivor1區當前記憶體大小
2.3 記憶體分配和回收策略
2.3.1 分配策略
- 大部分物件建立時,在eden區分配
- 大的物件直接進入老年代,比如很長的字串或陣列。這些物件對垃圾回收不友好。
- 長期存活的物件,將從新生代晉升到老年代
2.3.2 回收策略
- eden區滿:觸發一次minor gc,存活的物件複製到其中一個survivor。物件的年齡+1
- 一個survivor區滿:滿足晉升條件的,進入老年代。不滿足的,複製到另一個survivor區
2.3.3 晉升條件的判斷
- Serial和ParNew GC中通過MaxTenuringThreshold引數設定,預設為15
- Parallel收集器自動調整年齡:survivor空間中相同年齡所有物件大小大於空間的一半,大於等於該年齡的物件就直接進入老年代
2.3 關於堆劃分的思考
2.3.1 大堆和小堆堆程式的影響
- 堆太大:垃圾回收時STW的時間過長,影響程式響應時間。據說ZGC(java11釋出)回收器能解決這個問題。java11中ZGC的介紹
- 堆太小:垃圾回收太頻繁
2.3.2 為什麼要劃分為不同的年代
- 每個物件的生命週期是不一樣的,將不同存活時間的物件劃分到不同的區,然後採用不同的垃圾回收演算法
- java很多物件都是朝生夕死的,這些物件不會進入老年代。
2.3.3 為什麼要有survivor區
- 沒有survivor區,只有eden區的話,每進行一次minor gc,物件就被送入老年代。很容易觸發full gc,影響效能
- survivor存在的目的就是減少送入老年代的物件數量,減少full gc的發生
2.3.4 為什麼要設定兩個survivor區
每次minor gc,通過將eden和一個survivor的內容複製到另一個survivor, 避免碎片化問題
3. 垃圾回收演算法
3.1 標記-清除演算法
- 最基礎的收集演算法
- 分為標記和清除兩個階段
- 不足之處:
- 效率問題
- 產生大量不連續的記憶體碎片
3.2 複製演算法
- 將記憶體分為大小相等的兩塊,每次使用其中的一塊
- 一塊用完時,將存活的物件複製到另一塊
- 現代虛擬機器新生代都用該演算法
- 不足:
- 記憶體利用率不高
3.3 標記-整理演算法
- 物件存活率高時大量的複製會影響效率,老年代使用該演算法
- 標記過程與標記-清除演算法一樣
- 後續步驟並不是清理物件,而是讓所有存活的物件都向一段移動,清理邊界以外的記憶體
3.4 分代收集演算法
- 根據物件存活週期不同,採用不同的收集演算法
- 新生代大量物件死亡,少量存活,採用複製演算法
- 老年代物件存活率高,採用標記-清理或者標記-收集演算法
4. 垃圾回收器

4.1 年代劃分
- 新生代收集器有:Serial,ParNew,Paraller Scavenge
- 老年代收集器有:CMS Serial old,Parallel Old
- G1收集器可作用與新生代和老年代
- 沒有連線的兩個收集器不能共存,比如CMS和Paraller Scavenge
4.2 工作機制劃分
- 序列收集器:Serial,Serial Old,單執行緒的一個回收器,簡單、易實現、效率高
- 並行收集器:ParNew,Serial的多執行緒版,可以充分的利用CPU資源,減少回收的時間
- 吞吐量優先收集器:Parallel Scavenge
- 併發收集器:CMS(Concurrent Mark Sweep),停頓時間少優先,基於“標記-清除”演算法實現。
4.3 其他說明
- java11 新出了一款ZGC收集器,效能比G1更高效(還在實驗階段)
- java5預設採用CMS收集器,java9預設收集器被G1代替
- 使用者可自己指定使用哪種垃圾收集器
- 各個垃圾收集器詳細介紹參考深入理解java虛擬機器
4.4 CMS工作原理
- 不會等到老年代空間快滿了才回收(和使用者執行緒併發,留記憶體給使用者執行緒)。配置引數為-XX:CMSInitiazingOccupanyFraction。預設為75%
- 使用標記-清除演算法。整個過程分為四步:
- 初始標記:STW,標記GC Roots能關聯到的物件,速度很快
- 併發標記:GC Roots Tracing過程。耗時。和使用者執行緒一起執行(並行)
- 重新標記:STW,標記併發標記過程中程式執行導致標記變化的物件,時間比初始標記長,遠比並發標記短
- 併發清除:耗時。和使用者執行緒一起執行(並行)
三. ES配置說明回顧
可參考另外一篇筆記: ofollow,noindex">Elasticsearch學習筆記
主要介紹es官網手冊特別說明的一些注意點
1. 關於配置的說明
1.1 ES使用的垃圾回收器
- 預設為CMS,2.x版本官方推薦不要修改為G1,某些版本JAVA G1存在的Bug,會造成Lucene的段檔案損壞。
- 不過5.x以及之後版本,沒有明確說推薦或不推薦G1,預設還是用的CMS
1.2 ES記憶體分配要求
- 不超過32G。因為每個物件的指標都變長了,就會使用更多的 CPU 記憶體頻寬,也就是說你實際上失去了更多的記憶體。
- 不要超過記憶體的一半,因為Lucene也需要記憶體,且這些記憶體不被JVM管理
- 如果不需要對分詞做聚合運算,可降低堆記憶體。堆記憶體越小,Elasticsearch(更快的 GC)和 Lucene(更多的記憶體用於快取)的效能越好。
2. 關於滾動重啟的說明
- 保證不停叢集功能的情況下逐一對每個節點進行升級或維護
- 先停止索引新的資料
- 禁止分片分配。cluster.routing.allocation.enable" : "none"
curl -XPUT http://{ip}:9200/_cluster/settings -d' { "transient" : { "cluster.routing.allocation.enable" : "none" } }' 複製程式碼
- 關閉單個節點,並執行升級維護
- 啟動節點,並等待加入叢集
- 重啟分片分配。cluster.routing.allocation.enable" : "all"
curl -XPUT http://{ip}:9200/_cluster/settings -d' { "transient" : { "cluster.routing.allocation.enable" : "all" } }' 複製程式碼
- 對其他節點重複以上步驟
- 恢復索引更新資料
四. 現狀分析
1. 版本及硬體情況介紹
- java:1.8.0_131
- elasticsearch:5.5.1
- es叢集:4個數據節點
- os: centos7 24核 128G
- 垃圾回收器:老年代(CMS)+ 新生代(ParNew)
2. 目前堆分配情況
要針對jvm調優,必不可少的是先檢視堆記憶體狀況,有以下幾種檢視方法
2.1 jstat -gc命令檢視堆分配情況

2.2 統計ES各個節點堆分配資訊
節點 | 堆總大小 | 新生代 | survivor | eden | 老年代 | 元資料區 |
---|---|---|---|---|---|---|
節點A | 32G | 1.46G | 0.146G | 1.16G | 30.5G | 81M |
節點B | 32G | 1.46G | 0.146G | 1.16G | 30.5G | 85M |
節點C | 32G | 1.46G | 0.146G | 1.16G | 30.5G | 81M |
節點D | 20G | 1.46G | 0.146G | 1.16G | 18.5G | 76M |
3. 監控工具對比
工具名稱 | 各分割槽情況 | 資料是否直觀 | 是否可檢視歷史資料 | 是否免費 | 備註 |
---|---|---|---|---|---|
jstat | 是 | 否 | 否 | 是 | 主要用於檢視各分割槽大小 |
ElasticHQ | 否 | 是 | 否 | 是 | 主要用於瀏覽es整體資訊 |
cerebro | 否 | 是 | 否 | 是 | 主要用於瀏覽es整體資訊 |
x-pack | 否 | 是 | 是 | 試用期一年 | 試用期到相關功能不可用,不影響現有功能。6.3版本x-pack已經開源,後續版本可能會免費 |
- 由於線上報異常郵件的時間是不確定的,不可能隨時盯著監控面板看,所有必須有檢視歷史資料的功能,因此x-pack是我們監控的首選工具
- x-pack監控功能只是其中之一,但是真的非常強大,強烈推薦!!同時期待ES官方儘快使之免費
- 網上有破解x-pack的方法,將jar包反編譯之後修改程式碼,再打包回去,還沒做嘗試。