PB 級大規模 Elasticsearch 叢集運維與調優實踐

騰訊技術工程發表於2020-08-12

騰訊雲 Elasticsearch 被廣泛應用於日誌實時分析、結構化資料分析、全文檢索等場景中,本文將以情景植入的方式,向大家介紹與騰訊雲客戶合作過程中遇到的各種典型問題,以及相應的解決思路與方法,希望與大家一同交流。

背景

某中型網際網路公司的遊戲業務,使用了騰訊雲的 Elasticsearch 產品,採用 ELK 架構儲存業務日誌。

因為遊戲業務本身的日誌資料量非常大(寫入峰值在 100w qps),在服務客戶的幾個月中,踩了不少坑,經過數次最佳化與調整,把客戶的 ES 叢集調整得比較穩定,避免了在業務高峰時客戶叢集的讀寫異常,並且降低了客戶的資金成本和使用成本。

場景 1:與客戶的初次交鋒

解決方案架構師 A: bellen, XX 要上線一款新遊戲,日誌儲存決定用 ELK 架構,他們決定在 XX 雲和我們之間二選一,我們首先去他們公司和他們交流一下,爭取拿下!

bellen: 好,隨時有空!

和架構師一起前往該公司,跟負責底層元件的運維部門的負責人進行溝通。

XX 公司運維老大:不要講你們的 PPT 了,先告訴我你們能給我們帶來什麼!

bellen:呃,我們有很多優勢,比如可以靈活擴縮容叢集,還可以一鍵平滑升級叢集版本,並且提供有跨機房容災的叢集從而實現高可用……

XX 公司運維老大:你說的這些別的廠商也有,我就問一個問題,我們現在要儲存一年的遊戲日誌,不能刪除資料,每天就按 10TB 的資料量算,一年也得有個 3PB 多的資料,這麼大的數量,都放在 SSD 雲盤上,成本太高了。你們有什麼方案既能夠滿足我們儲存這麼大資料量的需求,同時能夠降低我們的成本嗎?

bellen: 我們本身提供的有冷熱模式的叢集,熱節點採用 SSD 雲硬碟,冷節點採用 SATA 盤,採用 ES 自帶的 ILM 索引生命週期管理功能,定期把較老的索引從熱節點遷移到冷節點上,這樣從整體上可以降低成本。另外,也可以定期把更老的索引透過 snapshot 快照備份到 COS 物件儲存中,然後刪除索引,這樣成本就更低了。

XX 公司運維老大:儲存到 COS 就是冷儲存唄,我們需要查詢 COS 裡的資料時,還得再把資料恢復到 ES 裡?這樣不行,速度太慢了,業務等不了那麼長時間,我們的資料不能刪除,只能放在 ES 裡!你們能不能給我們提供一個 API, 讓老的索引資料雖然儲存在 COS 裡,但是透過這個 API 依然可以查詢到資料,而不是先恢復到 ES, 再進行查詢

bellen:呃,這個可以做,但是需要時間。是否可以採用 hadoop on COS 的架構,把存量的老的索引資料透過工具匯入到 COS,透過 hive 去查詢,這樣成本會非常低,資料依然是隨時可查的。

XX 公司運維老大:那不行,我們只想用成熟的 ELK 架構來做,再增加 hadoop 那一套東西,我們沒那麼多人力搞這個事!

bellen: 好吧,那可以先搞一個叢集測試起來,看看效能怎麼樣。關於存量資料放在 COS 裡但是也需要查詢的問題,我們可以先制定方案,儘快實施起來。

XX 公司運維老大:行吧,我們現在按每天 10TB 資料量預估,先購買一個叢集,能撐 3 個月的資料量就行,能給一個叢集配置的建議嗎?

bellen: 目前支援單節點磁碟最大 6TB, cpu 和記憶體的話可以放到 8 核 32G 單節點,單節點跑 2w qps 寫入沒有問題,後面也可以進行縱向擴容和橫向擴容。

XX 公司運維老大:好,我們先測試一下。

場景 2:叢集扛不住壓力了

N 天后,架構師 A 直接在微信群裡反饋:bellen, 客戶反饋這邊的 ES 叢集效能不行啊,使用 logstash 消費 kafka 中的日誌資料,跑了快一天了資料還沒追平,這是線上的叢集,麻煩緊急看一下吧。

我一看,一臉懵, 什麼時候已經上線了啊,不是還在測試中嗎?

XX 公司運維 B: 我們購買了 8 核 32G*10 節點的叢集,單節點磁碟 6TB, 索引設定的 10 分片 1 副本,現在使用 logstash 消費 kafka 中的資料,一直沒有追平,kafka 中還有很多資料積壓,感覺是 ES 的寫入效能有問題。

隨後我立即檢視了叢集的監控資料,發現 cpu 和 load 都很高,jvm 堆記憶體使用率平均都到了 90%,節點 jvm gc 非常頻繁了,部分節點因為響應緩慢,不停的離線又上線。

經過溝通,發現使用者的使用姿勢是 filebeat+kafka+logstash+elasticsearch, 當前已經在 kafka 中儲存了有 10 天的日誌資料,啟動了 20 臺 logstash 進行消費,logstash 的 batch size 也調到了 5000,效能瓶頸是在 ES 這一側。客戶 8 核 32G*10 節點的叢集,理論上跑 10w qps 沒有問題,但是 logstash 消費積壓的資料往 ES 寫入的 qps 遠不止 10w,所以是 ES 扛不住寫入壓力了,只能對 ES 叢集進行擴容,為了加快存量資料的消費速度,先縱向擴容單節點的配置到 32 核 64GB,之後再橫向增加節點,以保證 ES 叢集能夠最大支援 100w qps 的寫入(這裡需要注意的是,增加節點後索引的分片數量也需要調整)。

所以一般新客戶接入使用 ES 時,必須要事先評估好節點配置和叢集規模,可以從以下幾個方面進行評估:

  • 儲存容量:要考慮索引副本數量、資料膨脹、ES 內部任務額外佔用的磁碟空間(比如 segment merge)以及作業系統佔用的磁碟空間等因素,如果再需要預留 50%的空閒磁碟空間,那麼叢集總的儲存容量大約為源資料量的 4 倍
  • 計算資源:主要考慮寫入,2 核 8GB 的節點可以支援 5000qps 的寫入,隨著節點數量和節點規格的提升,寫入能力基本呈線性增長
  • 索引和分片數量評估:一般一個 shard 的資料量在 30-50GB 為宜,可以以此確定索引的分片數量以及確定按天還是按月建索引。需要控制單節點總的分片數量,1GB 堆記憶體支援 20-30 個分片為宜;另外需要控制叢集整體的分片數量,叢集總體的分片數量一般不要超過 3w。

場景 3:logstash 消費 kafka 效能調優

上面遇到的問題是業務上線前沒有對叢集配置和規模進行合理的評估,導致上線後 ES 叢集扛不住了。透過合理的擴容處理,叢集最終抗住了寫入壓力,但是新的問題又隨之出現了。

因為 kafka 積壓的資料比較多,客戶使用 logstash 消費 kafka 資料時,反饋有兩個問題:

  1. 增加多臺 logstash 消費 kafka 資料,消費速度沒有線性提升
  2. kafka 的不同 topic 消費速度不均勻、topic 內不同 partition 消費的速度也不均勻

經過分析客戶 logstash 的配置檔案,發現問題出現的原因主要是:

  1. topic 的 partition 數量少,雖然 logstash 機器數量多,但是卻沒有充分利用機器資源並行消費資料,導致消費速度一直上不去
  2. 所有 logstash 的配置檔案都相同,使用一個 group 同時消費所有的 topic,存在資源競爭的問題

分析後,對 kafka 和 logstash 進行了如下最佳化:

  1. 提高 kafka topic 的分割槽數量
  2. 對 logstash 進行分組;對於資料量較大的 topic,可以單獨設定一個消費組進行消費,有一組 logstash 單獨使用這個消費組對該 topic 進行消費;其它的資料量較小的 topic,可以共用一個消費組和一組 logstash
  3. 每組 logstash 中總的 consumer_threads 數量和消費組總的 partion 數量保持一致,比如有 3 個 logstash 程式,消費的 topic 的 partition 數量為 24, 那麼每個 logstash 配置檔案中的 consumer_threads 就設定為 8

透過上述最佳化,最終使得 logstash 機器資源都被充分利用上,很快消費完堆積的 kafka 資料,待消費速度追平生成速度後,logstash 消費 kafka 一直穩定執行,沒有出現積壓。

另外,客戶一開始使用的是 5.6.4 版本的 logstash,版本較老,使用過程中出現因為單個訊息體過長導致 logstash 拋異常後直接退出的問題:

whose size is larger than the fetch size 4194304 and hence cannot be ever returned. Increase the fetch size on the client (using max.partition.fetch.bytes), or decrease the maximum message size the broker will allow (using message.max.bytes)

透過把 logstash 升級至高版本 6.8 避免了這個問題(6.x 版本的 logstash 修復了這個問題,避免了 crash)。

場景 4:磁碟要滿了,緊急擴容?

客戶的遊戲上線有一個月了,原先預估每天最多有 10TB 的資料量,實際則是在運營活動期間每天產生 20TB 的資料,原先 6TB*60=360TB 總量的資料盤使用率也達到了 80%。針對這種情況,我們建議客戶使用冷熱分離的叢集架構,在原先 60 個熱節點的基礎上,增加一批 warm 節點儲存冷資料,利用 ILM(索引生命週期管理)功能定期遷移熱節點上的索引到 warm 節點上。

透過增加 warm 節點的方式,客戶的叢集磁碟總量達到了 780TB, 可以滿足最多三個月的儲存需求。但是客戶的需求還沒有滿足:

XX 公司運維老大:給我們一個能存放一年資料的方案吧,總是透過加節點擴容磁碟的方式不是長久之計,我們得天天盯著這個叢集,運維成本很高!並且一直加節點,ES 會扛不住吧?

bellen: 可以嘗試使用我們新上線的支援本地盤的機型,熱節點最大支援 7.2TB 的本地 SSD 盤,warm 節點最大支援 48TB 的本地 SATA 盤。一方面熱節點的效能相比雲盤提高了,另外 warm 節點可以支援更大的磁碟容量。單節點可以支援的磁碟容量增大了,節點數量就不用太多了,可以避免踩到因為節點數量太多而觸發的坑。

XX 公司運維老大:現在用的是雲盤,能替換成本地盤嗎,怎麼替換?

bellen: 不能直接替換,需要在叢集中新加入帶本地盤的節點,把資料從老的雲盤節點遷移到新的節點上,遷移完成後再剔除掉舊的節點,這樣可以保證服務不會中斷,讀寫都可以正常進行。

XX 公司運維老大:好,可以實施,儘快搞起來!

雲盤切換為本地盤,是透過呼叫雲服務後臺的 API 自動實施的。在實施之後,觸發了資料從舊節點遷移到新節點的流程,但是大約半個小時候,問題又出現了:

XX 公司運維小: bellen, 快看一下,ES 的寫入快掉 0 了。

PB 級大規模 Elasticsearch 叢集運維與調優實踐

透過檢視叢集監控,發現寫入 qps 直接由 50w 降到 1w,寫入拒絕率猛增,透過檢視叢集日誌,發現是因為當前小時的索引沒有建立成功導致寫入失敗。

緊急情況下,執行了以下操作定位到了原因:

  1. GET _cluster/health

發現叢集健康狀態是 green,但是有大約 6500 個 relocating_shards, number_of_pending_tasks 數量達到了數萬。

  1. GET _cat/pending_tasks?v

發現大量的"shard-started"任務在執行中,任務優先順序是"URGENT", 以及大量的排在後面的"put mapping"的任務,任務優先順序是"HIGH";"URGENT"優先順序比"HIGH"優先順序要高,因為大量的分片從舊的節點遷移到新的節點上,造成了索引建立的任務被阻塞,從而導致寫入資料失敗。

  1. GET _cluster/settings

為什麼會有這麼多的分片在遷移中?透過 GET _cluster/settings 發現"cluster.routing.allocation.node_concurrent_recoveries"的值為 50,而目前有 130 箇舊節點在把分片遷移到 130 個新節點中,所以有 130*50=6500 個遷移中的分片。而"cluster.routing.allocation.node_concurrent_recoveries"引數的值預設為 2,應該是之前在執行縱向擴容叢集時,為了加快分片遷移速度人為修改了這個值(因為叢集一開始節點數量沒有很多,索引同時遷移中的分片也不會太多,所以建立新索引不會被阻塞)。

  1. PUT _cluster/settings

現在透過 PUT _cluster/settings 把"cluster.routing.allocation.node_concurrent_recoveries"引數修改為 2。但是因為"put settings"任務的優先順序也是"HIGH", 低於"shard-started"任務的優先順序,所以更新該引數的操作還是會被阻塞,ES 報錯執行任務超時。此時,進行了多次重試,最終成功把"cluster.routing.allocation.node_concurrent_recoveries"引數修改為了 2。

  1. 取消 exclude 配置

現在透過 GET _cluster/health 看到遷移中的分片數量在逐漸減少,為了不增加新的遷移任務,把執行資料遷移的 exclude 配置取消掉:

PUT _cluster/settings
{
  "transient": {
    "cluster.routing.allocation.exclude._name": ""
  }
}
  1. 加速分片遷移

同時調大分片恢復時節點進行資料傳輸的每秒最大位元組數(預設為 40MB),加速存量的分片遷移任務的執行:

PUT _cluster/settings
{
  "transient": {
    "indices": {
      "recovery": {
        "max_bytes_per_sec": "200mb"
      }
    }
  }
}
  1. 提前建立索引

現在看到遷移中的分片數量慢慢減少,新索引已經建立成功了,寫入恢復正常了。到下個整點時,發現新建索引還是比較慢,因為還有幾百個分片在遷移中,建立新索引大概耗時 5 分鐘,這 5 分鐘內寫入也是失敗的。

PB 級大規模 Elasticsearch 叢集運維與調優實踐

等幾百個遷移中的分片都執行完畢後,新建索引就比較快了,也不會再寫入失敗了。但是問題是當前正在執行雲盤節點切換為本地盤的流程,需要把資料從舊的130個節點上遷移到新的130個節點上,資料遷移的任務不能停,那該怎麼辦?既然新建立索引比較慢,那就只好提前把索引都建立好,避免了在每個整點資料寫入失敗的情況。透過編寫python指令碼,每天執行一次,提前把第二天的每個小時的索引建立好,建立完成了再把"cluster.routing.allocation.exclude._name"更改為所有的舊節點,保證資料遷移任務能夠正常執行。

  1. 結果展示

總量 400TB 的資料,大約經過 10 天左右,終於完成遷移了。配合提前新建索引的 python 指令碼,這 10 天內也沒有出現寫入失敗的情況。

經過了這次擴容操作,總結了如下經驗:

  1. 分片數量過多時,如果同時進行遷移的分片數量過多,會阻塞索引建立和其它配置更新操作,所以在進行資料遷移時,要保證"cluster.routing.allocation.node_concurrent_recoveries"引數和"cluster.routing.allocation.cluster_concurrent_rebalance"為較小的值。
  2. 如果必須要進行資料遷移,則可以提前建立好索引,避免 ES 自動建立索引時耗時較久,從而導致寫入失敗。

場景 5:10 萬個分片?

在穩定執行了一陣後,叢集又出問題了。

XX 公司運維 B: bellen, 昨晚凌晨 1 點鐘之後,叢集就沒有寫入了,現在 kafka 裡有大量的資料堆積,麻煩儘快看一下?

透過 cerebro 檢視叢集,發現叢集處於 yellow 狀態,然後發現叢集有大量的錯誤日誌:

{"message":"blocked by: [SERVICE_UNAVAILABLE/1/state not recovered / initialized];: [cluster_block_exception] blocked by: [SERVICE_UNAVAILABLE/1/state not recovered / initialized];","statusCode":503,"error":"Service Unavailable"}

然後再進一步檢視叢集日誌,發現有"master not discovered yet..."之類的錯誤日誌,檢查三個 master 節點,發現有兩個 master 掛掉,只剩一個了,叢集無法選主。

登陸到掛了的 master 節點機器上,發現保活程式無法啟動 es 程式,第一直覺是 es 程式 oom 了;此時也發現 master 節點磁碟使用率 100%, 檢查了 JVM 堆記憶體快照檔案目錄,發現有大量的快照檔案,於是刪除了一部分檔案,重啟 es 程式,程式正常啟動了;但是問題是堆記憶體使用率太高,gc 非常頻繁,master 節點響應非常慢,大量的建立索引的任務都超時,阻塞在任務佇列中,叢集還是無法恢復正常。

看到叢集 master 節點的配置是 16 核 32GB 記憶體,JVM 實際只分配了 16GB 記憶體,此時只好透過對 master 節點原地增加記憶體到 64GB(虛擬機器,使用的騰訊雲 CVM, 可以調整機器規格,需要重啟),master 節點機器重啟之後,修改了 es 目錄 jvm.options 檔案,調整了堆記憶體大小,重新啟動了 es 程式。

3 個 master 節點都恢復正常了,但是分片還需要進行恢復,透過 GET _cluster/health 看到叢集當前有超過 10w 個分片,而這些分片恢復還需要一段時間,透過調大"cluster.routing.allocation.node_concurrent_recoveries", 增大分片恢復的併發數量。實際上 5w 個主分片恢復得是比較快的了,但是副本分片的恢復就相對慢很多,因為部分副本分片需要從主分片上同步資料才能恢復。此時可以採取的方式是把部分舊的索引副本數量調為 0, 讓大量副本分片恢復的任務儘快結束,保證新索引能夠正常建立,從而使得叢集能夠正常寫入。

總結這次故障的根本原因是:叢集的索引和分片數量太多,叢集後設資料佔用了大量的堆記憶體,而 master 節點本身的 JVM 記憶體只有 16GB(資料節點有 32GB), master 節點頻繁 full gc 導致 master 節點異常,從而最終導致整個叢集異常。所以要解決這個問題,還是得從根本上解決叢集的分片數量過多的問題。

目前日誌索引是按照小時建立,60 分片 1 副本,每天有 24*60*2=2880 個分片,每個月就產生 86400 個分片,這麼多的分片可能會帶來嚴重的問題。有以下幾種方式解決分片數量過多的問題:

  1. 可以在 ILM 的 warm phase 中開啟 shrink 功能,對老的索引從 60 分片 shrink 到 5 分片,分片數量可以降低 12 倍;
  2. 業務可以把每小時建立索引修改為每兩個小時或者更長,可以根據每個分片數量最多支援 50GB 的資料推算多長時間建立新索引合適;
  3. 對老的索引設定副本為 0,只保留主分片,分片數量能夠再下降近一倍,儲存量也下降近一倍;
  4. 定期關閉最老的索引,執行{index}/_close。

和客戶溝透過後,客戶表示可以接受方式 1 和方式 2,但是方式 3 和 4 不能接受,因為考慮到存在磁碟故障的可能性,必須保留一個副本來保證資料的可靠性;另外還必須保證所有資料都是隨時可查詢的,不能關閉。

場景 6:有點坑的 ILM

在上文中,雖然透過臨時給 master 節點增加記憶體,抗住了 10w 分片,但是不能從根本上解決問題。客戶的資料是計劃保留一年的,如果不進行最佳化,叢集必然扛不住數十萬個分片。所以接下來需要著重解決叢集整體分片數量過多的問題。前文也提到了,客戶可以接受開啟 shrink 以及降低索引建立粒度(經過調整後,每兩個小時建立一個索引),這在一定程度上減少了分片的數量,能夠使叢集暫時穩定一陣。

輔助客戶在 kibana 上配置瞭如下的 ILM 策略:

PB 級大規模 Elasticsearch 叢集運維與調優實踐

在 warm phase, 把建立時間超過 360 小時的索引從 hot 節點遷移到 warm 節點上,保持索引的副本數量為 1。之所以使用 360 小時作為條件,而不是 15 天作為條件,是因為客戶的索引是按小時建立的,如果以 15 天作為遷移條件,則在每天凌晨都會同時觸發 15 天前的 24 個索引一共 24*120=2880 個分片同時開始遷移索引,容易引發前文介紹的由於遷移分片數量過多導致建立索引被阻塞的問題。所以以 360 小時作為條件,則在每個小時只會執行一個索引的遷移,這樣把 24 個索引的遷移任務打平,避免其它任務被阻塞的情況發生。

PB 級大規模 Elasticsearch 叢集運維與調優實踐

同時,也在 warm phase 階段,設定索引 shrink,把索引的分片數縮成 5 個,因為老的索引已經不執行寫入了,所以也可以執行 force merge, 強制把 segment 檔案合併為 1 個,可以獲得更好的查詢效能。

另外,設定了 ILM 策略後,可以在索引模板裡增加 index.lifecycle.name 配置,使得所有新建立的索引都可以和新新增的 ILM 策略關聯,從而使得 ILM 能夠正常執行。

客戶使用的 ES 版本是 6.8.2, 在執行 ILM 的過程中, 也發現一些問題:

  1. 新新增的策略只能對新建立的索引生效,存量的索引只能透過批次修改索引 settings 裡的 index.lifecycle.name 執行策略;

  2. 如果一個策略進行了修改,那麼所有存量的索引,不管是有沒有執行過該策略,都不會執行修改後的策略,也即修改後的策略只對修改成功後新建立的索引生效。比如一開始的策略沒有開啟 shrink, 現在修改策略內容新增了 shrink 操作,那麼只有之後新建立的索引在達到策略觸發條件(比如索引已經建立超過 360 個小時)後才會執行 shrink, 而之前的所有索引都不會執行 shrink,此時若想對存量的索引也執行 shrink,只能夠透過指令碼批次執行了;

  3. 在 warm phase 同時執行索引遷移和 shrink 會觸發 es 的 bug, 如上圖中的 ILM 策略,索引本身包含 60 分片 1 副本,初始時都在 hot 節點上,在建立完成 360 小時之後,會執行遷移,把索引都遷移到 warm 節點上,同時又需要把分片 shrink 到 5,在實際執行中,發現一段時間後有大量的 unassigned shards,分片無法分配的原因如下:

 "deciders" : [
         {
           "decider" : "same_shard",
           "decision" : "NO",
           "explanation" : "the shard cannot be allocated to the same node on which a copy of the shard already exists [[x-2020.06.19-13][58], node[LKsSwrDsSrSPRZa-EPBJPg], [P], s[STARTED], a[id=iRiG6mZsQUm5Z_xLiEtKqg]]"
         },
         {
           "decider" : "awareness",
           "decision" : "NO",
           "explanation" : "there are too many copies of the shard allocated to nodes with attribute [ip], there are [2] total configured shard copies for this shard id and [130] total attribute values, expected the allocated shard count per attribute [2] to be less than or equal to the upper bound of the required number of shards per attribute [1]"
         }

這是因為 shrink 操作需要新把索引完整的一份資料都遷移到一個節點上,然後在記憶體中構建新的分片後設資料,把新的分片透過軟連結指向到幾個老的分片的資料,在 ILM 中執行 shrink 時,ILM 會對索引進行如下配置:

 {
   "index.routing" : {
           "allocation" : {
             "require" : {
               "temperature" : "warm",
               "_id" : "LKsSwrDsSrSPRZa-EPBJPg"
             }
           }
         }
 }

問題是索引包含副本,而主分片和副本分片又不能在同一個節點上,所以會出現部分分片無法分配的情況(不是全部,只有一部分)。這裡應該是觸發了 6.8 版本的 ILM 的 bug,需要檢視原始碼才能定位解決這個 bug,目前還在研究中。當前的 workaround 是透過指令碼定期掃描出現 unassigned shards 的索引,修改其 settings:

 {
   "index.routing" : {
           "allocation" : {
             "require" : {
               "temperature" : "warm",
               "_id" : "LKsSwrDsSrSPRZa-EPBJPg"
             }
           }
         }
 }

優先保證分片先從 hot 節點遷移到 warm 節點,這樣後續的 shrink 才能順利執行(也可能執行失敗,因為 60 個分片都在一個節點上,可能會觸發 rebalance, 導致分片遷移走,shrink 的前置條件又不滿足,導致執行失敗)。要完全規避這個問題,還得在 ILM 策略中設定,滿足建立時間超過 360 個小時的索引,副本直接調整為 0,但是客戶又不接受,沒辦法。

場景 7:自己實現 SLM

上文介紹了 10w 個分片會給叢集帶來的影響和透過開啟 shrink 來降低分片數量,但是仍然有兩個需要重點解決的問題:

  1. 索引不斷新建,如何保證一年內,叢集總的分片數量不高於 10w,穩定在一個較低的水位?
  2. ILM 中執行 shrink 可能會導致部分分片未分配以及 shrink 執行失敗,怎麼徹底解決呢?

可以估算一下,按小時建索引,60 分片 1 副本,一年的分片數為 24*120*365=1051200 個分片,執行 shrink 後分片數量 24*10*350 + 24*120*15 = 127200(15 天內的新索引為了保障寫入效能和資料可靠性,仍然保持 60 分片 1 副本,舊的索引 shrink 為 5 分片 1 副本), 仍然有超過 10w 個分片。結合叢集一年總的儲存量和單個分片可以支援的資料量大小進行評估,我們期望叢集總體的分片數量可以穩定為 6w~8w,怎麼最佳化?

可以想到的方案是執行資料冷備份,把比較老的索引都冷備到其它的儲存介質上比如 HDFS、S3、騰訊雲的 COS 物件儲存等。但是問題是這些冷備的資料如果也要查詢,需要先恢復到 ES 中才可查,恢復速度比較慢,客戶無法接受。由此也產生了新的想法,目前老的索引仍然是 1 副本,可以把老索引先進行冷備份,再把副本調為 0,這樣做有以下幾點好處:

  1. 叢集整體分片數量能降低一半;
  2. 資料儲存量也能降低一半,叢集可以儲存更多資料;
  3. 老的索引仍然隨時可查;
  4. 極端情況下,磁碟故障引起只有一個副本的索引資料無法恢復時,可以從冷備介質中進行恢復。

經過和客戶溝通,客戶接受了上述方案,計劃把老索引冷備到騰訊雲的物件儲存 COS 中,實施步驟為:

  1. 所有存量的老索引,需要批次處理,儘快地備份到 COS 中,然後批次修改副本數量為 0;
  2. 最近新建的索引,採用按天備份的策略,結合 ILM, 修改策略,在 ILM 執行過程中修改索引副本數為 0(ILM 的 warm phase 和 cold phase 都支援設定副本數量)。

其中第一個步驟的實施可以透過指令碼實現,本案例中就採用了騰訊雲 SCF 雲函式進行實施,方便快捷可監控。實施要點有:

  1. 按天建立 snapshot,批次備份每天產生的 24 個索引,如果是按月或者更大粒度建立快照,因資料量太大如果執行快照過程中出現中斷,則必須全部重來,耗時耗力;按小時建立快照也不適用,會造成快照數量太多,可能會踩到坑。
  2. 每建立一個快照,後續需要輪詢快照的狀態,保證前一個快照 state 為"SUCCESS"之後,再建立下一個快照;因為快照是按天建立的,快照名字可以為 snapshot-2020.06.01, 該快照只備份 6 月 1 號的所有索引。而在檢查到 snapshot-2020.06.01 快照執行成功後,然後新建下一個快照時,需要知道要對哪天的索引打快照,因此需要記錄當前正在執行哪一個快照。有兩種方式記錄,一是把當前正在執行的快照日期字尾"2020.06.01"寫入到檔案中, 指令碼透過定時任務輪詢時,每次都讀檔案;另外一種方式是建立一個臨時的索引,把"2020.06.01"寫入到這個臨時索引的一個 doc 中,之後對該 doc 進行查詢或者更新。
  3. 建立快照時,可以把"include_global_state"置為 false, 不對叢集的全域性狀態資訊進行備份。

在實施完第一個步驟之後,就可以批次把對索引進行過備份的索引副本數都調為 0, 這樣一次性釋放了很多磁碟空間,並且顯著降低了叢集整體的分片數量。

接下來實施第二個步驟,需要每天執行一次快照,多建立時間較久的索引進行備份。實施比較簡單,可以透過 crontab 定時執行指令碼或者使用騰訊雲 SCF 執行。

之後,就可以修改 ILM 策略,開啟 cold phase, 修改索引副本數量為 0:

PB 級大規模 Elasticsearch 叢集運維與調優實踐

此處的 timing 是建立時間 20 天后,需要保證在第二步驟中,對過去老索引資料備份先執行完成後才可以進入到 cold phase.

透過老索引資料冷備並且降低索引副本,我們可以把叢集整體的分片數量維持在一個較低的水位。但是還有另外一個問題待解決,也即 shrink 失敗的問題。剛好,我們可以利用對老索引資料冷備並且降低索引副本的方案,來徹底解決 shrink 失敗的問題。

在前文有提到,shrink 失敗歸根結底是因為索引的副本數量為 1, 現在我們可以把資料備份和降低副本提前,讓老索引進入到 ILM 的 warm phase 中時已經是 0 副本,之後再執行 shrink 操作就不會有問題了;同時,因為副本降低了,索引從 hot 節點遷移到 warm 節點遷移的資料量也減少了一半,從而降低了叢集負載,一舉兩得。

因此,我們需要修改 ILM 策略,在 warm phase 就把索引的副本數量調整為 0, 然後去除 cold phase。

另外一個可選的最佳化項是:對老的索引進行凍結,凍結索引是指把索引常駐記憶體的一些資料從記憶體中清理掉(比如 FST, 後設資料等), 從而降低記憶體使用量,而在查詢已經凍結的索引時,會重新構建出臨時的索引資料結構存放在記憶體中,查詢完畢再清理掉;需要注意的是,預設情況下是無法查詢已經凍結的索引的,需要在查詢時顯式的增加"ignore_throttled=false"引數

經過上述最佳化,我們最終解決了叢集整體分片數量過多和 shrink 失敗的問題。在實施過程中引入了額外的定時任務指令碼實施自動化快照,實際上在 7.4 版本的 ES 中,已經有這個功能了,特性名稱為SLM(快照生命週期管理),並且可以結合 ILM 使用,在 ILM 中增加了"wait_for_snapshot"的 ACTION, 但是卻只能在 delete phase 中使用,不滿足我們的場景。

場景 8:客戶十分喜歡的 Searchable Snapshots!

在上述的場景中,我們花費大量的精力去解決問題和最佳化使用方式,保證 ES 叢集能夠穩定執行,支援 PB 級別的儲存。溯本回原,如果我們能有一個方案使得客戶只需要把熱資料放在 SSD 盤上,然後冷資料儲存到 COS/S3 上,但同時又使冷資料能夠支援按需隨時可查,那我們前面碰到的所有問題都迎刃而解了。可以想象得到的好處有:

  1. 只需要更小規模的叢集和非常廉價的 COS/S3 物件儲存就可以支援 PB 級別的資料量,客戶的資金成本非常低;
  2. 小規模的叢集只需要能夠支撐熱索引的寫入和查詢即可,叢集整體的分片數不會太多,從而避免了叢集不穩定現象的發生。

而這正是目前 es 開源社群正在開發中的 Searchable Snapshots 功能,從Searchable Snapshots API的官方文件上可以看到,我們可以建立一個索引,將其掛載到一個指定的快照中,這個新的索引是可查詢的,雖然查詢時間可能會慢點,但是在日誌場景中,對一些較老的索引進行查詢時,延遲大點一般都是可以接受的。

所以我認為,Searchable Snapshots 解決了很多痛點,將會給 ES 帶來新的繁榮!

總結

經歷過上述運維和最佳化 ES 叢集的實踐,我們總結到的經驗有:

  1. 新叢集上線前務必做好叢集規模和節點規格的評估
  2. 叢集整體的分片數量不能太多,可以透過調整使用方式並且藉助 ES 本身的能力不斷進行最佳化,使得叢集總體的分片數維持在一個較低的水位,保證叢集的穩定性
  3. Searchable Snapshots 利器會給 ES 帶來新的生命力,需要重點關注並研究其實現原理

從一開始和客戶進行接觸,瞭解客戶訴求,逐步解決 ES 叢集的問題,最終使得 ES 叢集能夠保持穩定,這中間的經歷讓我真真正正地領悟到"實踐出真知"這句話的真諦,只有不斷實踐,才能對異常情況迅速做出反應,以及對客戶提的最佳化需求迅速反饋。

相關文章