目錄
背景介紹
JVM知識回顧
ES配置說明回顧
現狀分析
調優實戰
總結與展望
一. 背景介紹
項目中的服務集成了springboot-admin作服務監控,最近一直收到郵件告警,提示es出錯。錯誤信息以下:html
org.elasticsearch.ElasticsearchTimeoutException: java.util.concurrent.TimeoutException: Timeout waiting for task.
複製代碼
頻繁收到這個告警,因此決定花時間研究一下。從報錯信息看,併發超時異常。ES做爲java開發的中間件,咱們沒有對任何代碼作過修改,因此就從JVM開始着手嘗試解決,同時還涉及到部分ES知識和springboot的知識。java
二. 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)命令查看堆分配狀況spring
- S0C:survivor0區總內存大小(Capacity)
- S1C: survivor1區總內存大小
- S0U: survivor0區當前內存大小(Used)
- S1U: survivor1區當前內存大小
- EC:Eden區總內存大小
- EU:Eden區當前內存大小
- OC:老年代總內存大小
- OU:老年代當前內存大小
- MC:meta data區總內存大小
- MU:meta data區當前內存大小
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, 避免碎片化問題chrome
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配置說明回顧
可參考另一篇筆記:Elasticsearch學習筆記json
主要介紹es官網手冊特別說明的一些注意點centos
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包反編譯以後修改代碼,再打包回去,還沒作嘗試。
x-pack安裝過程的一些小問題總結
第一步:證書申請
curl -XPUT 'http://{ip}:9200/_xpack/license?acknowledge=true' -H "Content-Type: application/json" -d @sivabalan-nagarajan-2327c0fa-f56b-443a-a3d6-abef7ecf2220-v5.json
複製代碼
第二步:安裝x-pack插件, 包括es和kibana
./bin/kibana-plugin install x-pack 安裝很慢,先把文件下載下來,用下一個命令安裝
./bin/elasticsearch-plugin install file:///home/breakpad/softs/x-pack-5.5.1.zip
複製代碼
第三步:修改配置文件
es配置文件裏,只啓用監控功能瀏覽器
xpack.security.enabled: false
xpack.monitoring.enabled: true
xpack.graph.enabled: false
xpack.watcher.enabled: false
複製代碼
kibana配置文件裏,只啓用監控功能緩存
xpack.security.enabled: false
xpack.monitoring.enabled: true
xpack.graph.enabled: false
xpack.reporting.enabled: false
複製代碼
報錯問題解決
rpm安裝後,systemctl方式啓動kibana報權限不足的問題?
- 卸載x-pack,以kibana用戶去安裝 sudo -u kibana bin/kibana-plugin install file:///usr/share/kibana/x-pack-5.5.1.zip
- 仍是報權限錯誤,修改報錯的文件權限都爲kibana
- 安裝包權限報錯,修改安裝包權限爲kibana
kibana啓動後網頁打不開怎麼解決?
- 在config/kibana.yml裏配置日誌路徑:logging.dest: /var/log/kibana.log
- 修改日誌權限 touch /var/log/kibana.log chown kibana:kibana /var/log/kibana.log
- 日誌也沒有錯,可是瀏覽器就是打不開。最後無心間換了個瀏覽器居然正常了,再從新把以前打不開的chrome瀏覽器升級以後,也能正常打開了!!
4. x-pack監控狀況分析(以節點B,週期爲7天爲例)
曲線中每一天大概24個點,即計算的是每一個小時的數據
4.1 gc次數:平均值爲250次/h 左右
4.2 minor gc耗時:平均值爲10000ms/h
4.3 full gc後:剩餘堆大小:4.2G,兩次full gc的時間分別爲2.224s,2.438s
5. 觀察到的現象
- 新生代和老年代分配比例不合理,新生代過小,老年代太大
- 網上不少文章指出新生代和老年代的默認比例爲1:2,可是經過觀察發現並非這樣(咱們的機器上約是1:20)。
- 具體緣由在網上目前只找到這樣一篇文章有過說明。CMS默認新生代是多大?
- 大體就是:取默認NewRatio計算出來的值和另一個公式計算出來的值對比,取小的那一個
- 計算公式爲:計算機核數*某個參數(64M)*13/10。咱們的機器算出來的值爲2G,勉強符合這個說法。
- 新生代垃圾回收頻繁
- 老年代收集後:有效內存只達4G左右(活躍數據)
6. 針對觀察到現象的初步分析和解決
- 新生代過小:致使minior gc回收頻繁,可適當加大新生代大小
- 老年代太大:致使major gc或full gc回收時間過長,可適當減小老年代大小
- 如何肯定新生代老年代大小:根據美團gc優化實戰文章所述:
- 總大小:3-4倍活躍數據大小:
- 節點B爲4.2G*4,咱們設置爲20G。
- 其餘機器大概爲3.6G*4,咱們設置爲16G
- 新生代:1-1.5倍活躍數據大小
- 老年代:總大小-新生代。綜上,咱們設置新生代:老年代=1:4
五. 調優實戰
經過以上分析發現的問題,而後嘗試調整參數,而且觀察調整後的監控結果,驗證咱們的推測是否正確。
1. 修改配置參數
- 文件爲{es_home}/config/jvm.options
2. 修改後的堆配置參數
節點 |
堆總大小 |
新生代 |
survivor |
eden |
老年代 |
元數據區 |
節點A |
16G |
3.2G |
320M |
2.56G |
12.8G |
77M |
節點B |
20G |
4G |
400M |
3.2G |
16G |
66M |
節點C |
16G |
3.2G |
320M |
2.56G |
12.8G |
77M |
節點D |
16G |
3.2G |
320M |
2.56G |
12.8G |
77M |
3. 修改後的監控信息
3.1 gc次數總體呈降低趨勢
3.2 gc耗時總體呈降低趨勢,full gc時間大體在900ms左右
4. springboot參數的調整
六. 總結與展望
1. 最終優化總結
1.1 關於JVM的調優
- 減小ES節點分配給JVM的堆大小
- 調整新生代和老年代的比例
1.2 關於springboot的調優
- 加大springboot-actuator針對Elasticsearch健康檢查時的響應時間(默認爲100ms)
2. 展望
- 業務優化:這次優化僅僅從JVM的角度作了參數調整,es官方文檔其實給出不少高效使用es的方法。後續優化能夠從業務的角度去分析,包括:
- 不少不須要分詞的字段,都沒有作配置,默認都分詞了。特別影響性能。
- 不少查詢沒有用filter,用了query。沒法緩存。
- 同時,期待未來ZGC投入使用,同時ES很好的兼容ZGC,或許那時就不須要任何調優了(無論多大的堆,gc時間在10ms內)。
七. 參考
- 《深刻理解java虛擬機》
- 《elasticsearch權威指南》
- 美團gc優化實戰