記一次Elasticsearch優化總結

目錄

背景介紹

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報權限不足的問題?

  1. 卸載x-pack,以kibana用戶去安裝 sudo -u kibana bin/kibana-plugin install file:///usr/share/kibana/x-pack-5.5.1.zip
  2. 仍是報權限錯誤,修改報錯的文件權限都爲kibana
  3. 安裝包權限報錯,修改安裝包權限爲kibana

kibana啓動後網頁打不開怎麼解決?

  1. 在config/kibana.yml裏配置日誌路徑:logging.dest: /var/log/kibana.log
  2. 修改日誌權限 touch /var/log/kibana.log chown kibana:kibana /var/log/kibana.log
  3. 日誌也沒有錯,可是瀏覽器就是打不開。最後無心間換了個瀏覽器居然正常了,再從新把以前打不開的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參數的調整

  • 經過對JVM參數調整後,發現仍是有不定時報警的狀況。因而研究了一下springboot-admin的原理,它是基於springboot-actuator作了封裝,而後看了一下springboot-actuator的原理,找到了其中一個很重要的參數:
    management.health.elasticsearch.response-timeout
    複製代碼
  • 該參數表示監控程序向es集羣發送心跳,容許最大的響應時間的多少? 聰明的你應該明白了,若是發送心跳的時候,es的JVM正在執行垃圾回收,STW致使響應遲遲得不到回覆,就會收到郵件告警。它的默認值是100ms,因此必須將該值設置爲至少超過minior gc的時間。
  • 可是,若是發送心跳的時候,剛好JVM正在執行full gc,由於STW的時間通常比較長,因此你必然會收到告警郵件,除非你把response-timeout的值設置爲比full gc的時間還長
  • 綜上分析,須要根據垃圾回收的時間,給該值設置合理的值。

六. 總結與展望

1. 最終優化總結

1.1 關於JVM的調優

  • 減小ES節點分配給JVM的堆大小
  • 調整新生代和老年代的比例

1.2 關於springboot的調優

  • 加大springboot-actuator針對Elasticsearch健康檢查時的響應時間(默認爲100ms)

2. 展望

  • 業務優化:這次優化僅僅從JVM的角度作了參數調整,es官方文檔其實給出不少高效使用es的方法。後續優化能夠從業務的角度去分析,包括:
    • 不少不須要分詞的字段,都沒有作配置,默認都分詞了。特別影響性能。
    • 不少查詢沒有用filter,用了query。沒法緩存。
  • 同時,期待未來ZGC投入使用,同時ES很好的兼容ZGC,或許那時就不須要任何調優了(無論多大的堆,gc時間在10ms內)。

七. 參考

相關文章
相關標籤/搜索