記一次Elasticsearch優化總結

kinnylee發表於2018-10-11

目錄

背景介紹

JVM知識回顧

ES配置說明回顧

現狀分析

調優實戰

總結與展望

一. 背景介紹

專案中的服務整合了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的物件:堆
    記一次Elasticsearch優化總結

2. 堆記憶體

2.1 堆記憶體劃分

  • 堆區分為新生代和老年代
  • 新生代又分為Eden區,from survivor區,to survivor區
  • Eden區和兩塊較小的survivor空間。大小比例為8:1:1
  • java8已經沒有持久代了,改為後設資料區,主要存放後設資料,例如Class、Method的元資訊,與垃圾回收要回收的Java物件關係不大
    記一次Elasticsearch優化總結

2.2 堆記憶體檢視

使用 jstat -gc(-gccapacity, -gcutil)命令檢視堆分配情況

  • S0C:survivor0區總記憶體大小(Capacity)
  • S1C: survivor1區總記憶體大小
  • S0U: survivor0區當前記憶體大小(Used)
  • S1U: survivor1區當前記憶體大小
  • EC:Eden區總記憶體大小
  • EU:Eden區當前記憶體大小
  • OC:老年代總記憶體大小
  • OU:老年代當前記憶體大小
  • MC:meta data區總記憶體大小
  • MU:meta data區當前記憶體大小
    記一次Elasticsearch優化總結

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. 垃圾回收器

記一次Elasticsearch優化總結

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,標記併發標記過程中程式執行導致標記變化的物件,時間比初始標記長,遠比並發標記短
    • 併發清除:耗時。和使用者執行緒一起執行(並行)
      記一次Elasticsearch優化總結

三. ES配置說明回顧

可參考另外一篇筆記: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命令檢視堆分配情況

記一次Elasticsearch優化總結

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 左右

記一次Elasticsearch優化總結

4.2 minor gc耗時:平均值為10000ms/h

記一次Elasticsearch優化總結

4.3 full gc後:剩餘堆大小:4.2G,兩次full gc的時間分別為2.224s,2.438s

記一次Elasticsearch優化總結

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
    記一次Elasticsearch優化總結

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次數整體呈下降趨勢

記一次Elasticsearch優化總結

3.2 gc耗時整體呈下降趨勢,full gc時間大致在900ms左右

記一次Elasticsearch優化總結

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內)。

七. 參考

相關文章