PB 級(jí)大規(guī)模 Elasticsearch 集群運(yùn)維與調(diào)優(yōu)實(shí)踐
作者:bellen,騰訊云大數(shù)據(jù)研發(fā)工程師。
騰訊云 Elasticsearch 被廣泛應(yīng)用于日志實(shí)時(shí)分析、結(jié)構(gòu)化數(shù)據(jù)分析、全文檢索等場景中,本文將以情景植入的方式,向大家介紹與騰訊云客戶合作過程中遇到的各種典型問題,以及相應(yīng)的解決思路與方法,希望與大家一同交流。
背景
因?yàn)橛螒驑I(yè)務(wù)本身的日志數(shù)據(jù)量非常大(寫入峰值在 100w qps),在服務(wù)客戶的幾個(gè)月中,踩了不少坑,經(jīng)過數(shù)次優(yōu)化與調(diào)整,把客戶的 ES 集群調(diào)整得比較穩(wěn)定,避免了在業(yè)務(wù)高峰時(shí)客戶集群的讀寫異常,并且降低了客戶的資金成本和使用成本。
場景 1:與客戶的初次交鋒
解決方案架構(gòu)師 A: bellen, XX 要上線一款新游戲,日志存儲(chǔ)決定用 ELK 架構(gòu),他們決定在 XX 云和我們之間二選一,我們首先去他們公司和他們交流一下,爭取拿下!
bellen: 好,隨時(shí)有空!
和架構(gòu)師一起前往該公司,跟負(fù)責(zé)底層組件的運(yùn)維部門的負(fù)責(zé)人進(jìn)行溝通。
XX 公司運(yùn)維老大:不要講你們的 PPT 了,先告訴我你們能給我們帶來什么!
bellen:呃,我們有很多優(yōu)勢,比如可以靈活擴(kuò)縮容集群,還可以一鍵平滑升級(jí)集群版本,并且提供有跨機(jī)房容災(zāi)的集群從而實(shí)現(xiàn)高可用……
XX 公司運(yùn)維老大:你說的這些別的廠商也有,我就問一個(gè)問題,我們現(xiàn)在要存儲(chǔ)一年的游戲日志,不能刪除數(shù)據(jù),每天就按 10TB 的數(shù)據(jù)量算,一年也得有個(gè) 3PB 多的數(shù)據(jù),這么大的數(shù)量,都放在 SSD 云盤上,成本太高了。你們有什么方案既能夠滿足我們存儲(chǔ)這么大數(shù)據(jù)量的需求,同時(shí)能夠降低我們的成本嗎?
bellen: 我們本身提供的有冷熱模式的集群,熱節(jié)點(diǎn)采用 SSD 云硬盤,冷節(jié)點(diǎn)采用 SATA 盤,采用 ES 自帶的 ILM 索引生命周期管理功能,定期把較老的索引從熱節(jié)點(diǎn)遷移到冷節(jié)點(diǎn)上,這樣從整體上可以降低成本。另外,也可以定期把更老的索引通過 snapshot 快照備份到 COS 對(duì)象存儲(chǔ)中,然后刪除索引,這樣成本就更低了。
XX 公司運(yùn)維老大:存儲(chǔ)到 COS 就是冷存儲(chǔ)唄,我們需要查詢 COS 里的數(shù)據(jù)時(shí),還得再把數(shù)據(jù)恢復(fù)到 ES 里?這樣不行,速度太慢了,業(yè)務(wù)等不了那么長時(shí)間,我們的數(shù)據(jù)不能刪除,只能放在 ES 里!你們能不能給我們提供一個(gè) API, 讓老的索引數(shù)據(jù)雖然存儲(chǔ)在 COS 里,但是通過這個(gè) API 依然可以查詢到數(shù)據(jù),而不是先恢復(fù)到 ES, 再進(jìn)行查詢?
bellen:呃,這個(gè)可以做,但是需要時(shí)間。是否可以采用 hadoop on COS 的架構(gòu),把存量的老的索引數(shù)據(jù)通過工具導(dǎo)入到 COS,通過 hive 去查詢,這樣成本會(huì)非常低,數(shù)據(jù)依然是隨時(shí)可查的。
XX 公司運(yùn)維老大:那不行,我們只想用成熟的 ELK 架構(gòu)來做,再增加 hadoop 那一套東西,我們沒那么多人力搞這個(gè)事!
bellen: 好吧,那可以先搞一個(gè)集群測試起來,看看性能怎么樣。關(guān)于存量數(shù)據(jù)放在 COS 里但是也需要查詢的問題,我們可以先制定方案,盡快實(shí)施起來。
XX 公司運(yùn)維老大:行吧,我們現(xiàn)在按每天 10TB 數(shù)據(jù)量預(yù)估,先購買一個(gè)集群,能撐 3 個(gè)月的數(shù)據(jù)量就行,能給一個(gè)集群配置的建議嗎?
bellen: 目前支持單節(jié)點(diǎn)磁盤最大 6TB, cpu 和內(nèi)存的話可以放到 8 核 32G 單節(jié)點(diǎn),單節(jié)點(diǎn)跑 2w qps 寫入沒有問題,后面也可以進(jìn)行縱向擴(kuò)容和橫向擴(kuò)容。
XX 公司運(yùn)維老大:好,我們先測試一下。
場景 2:集群扛不住壓力了
N 天后,架構(gòu)師 A 直接在微信群里反饋:bellen, 客戶反饋這邊的 ES 集群性能不行啊,使用 logstash 消費(fèi) kafka 中的日志數(shù)據(jù),跑了快一天了數(shù)據(jù)還沒追平,這是線上的集群,麻煩緊急看一下吧。
我一看,一臉懵, 什么時(shí)候已經(jīng)上線了啊,不是還在測試中嗎?
XX 公司運(yùn)維 B: 我們購買了 8 核 32G*10 節(jié)點(diǎn)的集群,單節(jié)點(diǎn)磁盤 6TB, 索引設(shè)置的 10 分片 1 副本,現(xiàn)在使用 logstash 消費(fèi) kafka 中的數(shù)據(jù),一直沒有追平,kafka 中還有很多數(shù)據(jù)積壓,感覺是 ES 的寫入性能有問題。
隨后我立即查看了集群的監(jiān)控?cái)?shù)據(jù),發(fā)現(xiàn) cpu 和 load 都很高,jvm 堆內(nèi)存使用率平均都到了 90%,節(jié)點(diǎn) jvm gc 非常頻繁了,部分節(jié)點(diǎn)因?yàn)轫憫?yīng)緩慢,不停的離線又上線。
經(jīng)過溝通,發(fā)現(xiàn)用戶的使用姿勢是 filebeat+kafka+logstash+elasticsearch, 當(dāng)前已經(jīng)在 kafka 中存儲(chǔ)了有 10 天的日志數(shù)據(jù),啟動(dòng)了 20 臺(tái) logstash 進(jìn)行消費(fèi),logstash 的 batch size 也調(diào)到了 5000,性能瓶頸是在 ES 這一側(cè)??蛻?8 核 32G*10 節(jié)點(diǎn)的集群,理論上跑 10w qps 沒有問題,但是 logstash 消費(fèi)積壓的數(shù)據(jù)往 ES 寫入的 qps 遠(yuǎn)不止 10w,所以是 ES 扛不住寫入壓力了,只能對(duì) ES 集群進(jìn)行擴(kuò)容,為了加快存量數(shù)據(jù)的消費(fèi)速度,先縱向擴(kuò)容單節(jié)點(diǎn)的配置到 32 核 64GB,之后再橫向增加節(jié)點(diǎn),以保證 ES 集群能夠最大支持 100w qps 的寫入(這里需要注意的是,增加節(jié)點(diǎn)后索引的分片數(shù)量也需要調(diào)整)。
所以一般新客戶接入使用 ES 時(shí),必須要事先評(píng)估好節(jié)點(diǎn)配置和集群規(guī)模,可以從以下幾個(gè)方面進(jìn)行評(píng)估:
- 存儲(chǔ)容量:要考慮索引副本數(shù)量、數(shù)據(jù)膨脹、ES 內(nèi)部任務(wù)額外占用的磁盤空間(比如 segment merge)以及操作系統(tǒng)占用的磁盤空間等因素,如果再需要預(yù)留 50%的空閑磁盤空間,那么集群總的存儲(chǔ)容量大約為源數(shù)據(jù)量的 4 倍
- 計(jì)算資源:主要考慮寫入,2 核 8GB 的節(jié)點(diǎn)可以支持 5000qps 的寫入,隨著節(jié)點(diǎn)數(shù)量和節(jié)點(diǎn)規(guī)格的提升,寫入能力基本呈線性增長
- 索引和分片數(shù)量評(píng)估:一般一個(gè) shard 的數(shù)據(jù)量在 30-50GB 為宜,可以以此確定索引的分片數(shù)量以及確定按天還是按月建索引。需要控制單節(jié)點(diǎn)總的分片數(shù)量,1GB 堆內(nèi)存支持 20-30 個(gè)分片為宜;另外需要控制集群整體的分片數(shù)量,集群總體的分片數(shù)量一般不要超過 3w。
場景 3:logstash 消費(fèi) kafka 性能調(diào)優(yōu)
上面遇到的問題是業(yè)務(wù)上線前沒有對(duì)集群配置和規(guī)模進(jìn)行合理的評(píng)估,導(dǎo)致上線后 ES 集群扛不住了。通過合理的擴(kuò)容處理,集群最終抗住了寫入壓力,但是新的問題又隨之出現(xiàn)了。
因?yàn)?kafka 積壓的數(shù)據(jù)比較多,客戶使用 logstash 消費(fèi) kafka 數(shù)據(jù)時(shí),反饋有兩個(gè)問題:
- 增加多臺(tái) logstash 消費(fèi) kafka 數(shù)據(jù),消費(fèi)速度沒有線性提升
- kafka 的不同 topic 消費(fèi)速度不均勻、topic 內(nèi)不同 partition 消費(fèi)的速度也不均勻
經(jīng)過分析客戶 logstash 的配置文件,發(fā)現(xiàn)問題出現(xiàn)的原因主要是:
- topic 的 partition 數(shù)量少,雖然 logstash 機(jī)器數(shù)量多,但是卻沒有充分利用機(jī)器資源并行消費(fèi)數(shù)據(jù),導(dǎo)致消費(fèi)速度一直上不去
- 所有 logstash 的配置文件都相同,使用一個(gè) group 同時(shí)消費(fèi)所有的 topic,存在資源競爭的問題
分析后,對(duì) kafka 和 logstash 進(jìn)行了如下優(yōu)化:
- 提高 kafka topic 的分區(qū)數(shù)量
- 對(duì) logstash 進(jìn)行分組;對(duì)于數(shù)據(jù)量較大的 topic,可以單獨(dú)設(shè)置一個(gè)消費(fèi)組進(jìn)行消費(fèi),有一組 logstash 單獨(dú)使用這個(gè)消費(fèi)組對(duì)該 topic 進(jìn)行消費(fèi);其它的數(shù)據(jù)量較小的 topic,可以共用一個(gè)消費(fèi)組和一組 logstash
- 每組 logstash 中總的 consumer_threads 數(shù)量和消費(fèi)組總的 partion 數(shù)量保持一致,比如有 3 個(gè) logstash 進(jìn)程,消費(fèi)的 topic 的 partition 數(shù)量為 24, 那么每個(gè) logstash 配置文件中的 consumer_threads 就設(shè)置為 8
通過上述優(yōu)化,最終使得 logstash 機(jī)器資源都被充分利用上,很快消費(fèi)完堆積的 kafka 數(shù)據(jù),待消費(fèi)速度追平生成速度后,logstash 消費(fèi) kafka 一直穩(wěn)定運(yùn)行,沒有出現(xiàn)積壓。
另外,客戶一開始使用的是 5.6.4 版本的 logstash,版本較老,使用過程中出現(xiàn)因?yàn)閱蝹€(gè)消息體過長導(dǎo)致 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 升級(jí)至高版本 6.8 避免了這個(gè)問題(6.x 版本的 logstash 修復(fù)了這個(gè)問題,避免了 crash)。
場景 4:磁盤要滿了,緊急擴(kuò)容?
客戶的游戲上線有一個(gè)月了,原先預(yù)估每天最多有 10TB 的數(shù)據(jù)量,實(shí)際則是在運(yùn)營活動(dòng)期間每天產(chǎn)生 20TB 的數(shù)據(jù),原先 6TB*60=360TB 總量的數(shù)據(jù)盤使用率也達(dá)到了 80%。針對(duì)這種情況,我們建議客戶使用冷熱分離的集群架構(gòu),在原先 60 個(gè)熱節(jié)點(diǎn)的基礎(chǔ)上,增加一批 warm 節(jié)點(diǎn)存儲(chǔ)冷數(shù)據(jù),利用 ILM(索引生命周期管理)功能定期遷移熱節(jié)點(diǎn)上的索引到 warm 節(jié)點(diǎn)上。
通過增加 warm 節(jié)點(diǎn)的方式,客戶的集群磁盤總量達(dá)到了 780TB, 可以滿足最多三個(gè)月的存儲(chǔ)需求。但是客戶的需求還沒有滿足:
XX 公司運(yùn)維老大:給我們一個(gè)能存放一年數(shù)據(jù)的方案吧,總是通過加節(jié)點(diǎn)擴(kuò)容磁盤的方式不是長久之計(jì),我們得天天盯著這個(gè)集群,運(yùn)維成本很高!并且一直加節(jié)點(diǎn),ES 會(huì)扛不住吧?
bellen: 可以嘗試使用我們新上線的支持本地盤的機(jī)型,熱節(jié)點(diǎn)最大支持 7.2TB 的本地 SSD 盤,warm 節(jié)點(diǎn)最大支持 48TB 的本地 SATA 盤。一方面熱節(jié)點(diǎn)的性能相比云盤提高了,另外 warm 節(jié)點(diǎn)可以支持更大的磁盤容量。單節(jié)點(diǎn)可以支持的磁盤容量增大了,節(jié)點(diǎn)數(shù)量就不用太多了,可以避免踩到因?yàn)楣?jié)點(diǎn)數(shù)量太多而觸發(fā)的坑。
XX 公司運(yùn)維老大:現(xiàn)在用的是云盤,能替換成本地盤嗎,怎么替換?
bellen: 不能直接替換,需要在集群中新加入帶本地盤的節(jié)點(diǎn),把數(shù)據(jù)從老的云盤節(jié)點(diǎn)遷移到新的節(jié)點(diǎn)上,遷移完成后再剔除掉舊的節(jié)點(diǎn),這樣可以保證服務(wù)不會(huì)中斷,讀寫都可以正常進(jìn)行。
XX 公司運(yùn)維老大:好,可以實(shí)施,盡快搞起來!
云盤切換為本地盤,是通過調(diào)用云服務(wù)后臺(tái)的 API 自動(dòng)實(shí)施的。在實(shí)施之后,觸發(fā)了數(shù)據(jù)從舊節(jié)點(diǎn)遷移到新節(jié)點(diǎn)的流程,但是大約半個(gè)小時(shí)候,問題又出現(xiàn)了:
XX 公司運(yùn)維小: bellen, 快看一下,ES 的寫入快掉 0 了。
通過查看集群監(jiān)控,發(fā)現(xiàn)寫入 qps 直接由 50w 降到 1w,寫入拒絕率猛增,通過查看集群日志,發(fā)現(xiàn)是因?yàn)楫?dāng)前小時(shí)的索引沒有創(chuàng)建成功導(dǎo)致寫入失敗。
緊急情況下,執(zhí)行了以下操作定位到了原因:
1GET _cluster/health
發(fā)現(xiàn)集群健康狀態(tài)是 green,但是有大約 6500 個(gè) relocating_shards, number_of_pending_tasks 數(shù)量達(dá)到了數(shù)萬。
2.GET _cat/pending_tasks?v
發(fā)現(xiàn)大量的"shard-started"任務(wù)在執(zhí)行中,任務(wù)優(yōu)先級(jí)是"URGENT", 以及大量的排在后面的"put mapping"的任務(wù),任務(wù)優(yōu)先級(jí)是"HIGH";"URGENT"優(yōu)先級(jí)比"HIGH"優(yōu)先級(jí)要高,因?yàn)榇罅康姆制瑥呐f的節(jié)點(diǎn)遷移到新的節(jié)點(diǎn)上,造成了索引創(chuàng)建的任務(wù)被阻塞,從而導(dǎo)致寫入數(shù)據(jù)失敗。
3.GET _cluster/settings
為什么會(huì)有這么多的分片在遷移中?通過 GET _cluster/settings 發(fā)現(xiàn)"cluster.routing.allocation.node_concurrent_recoveries"的值為 50,而目前有 130 個(gè)舊節(jié)點(diǎn)在把分片遷移到 130 個(gè)新節(jié)點(diǎn)中,所以有 130*50=6500 個(gè)遷移中的分片。而"cluster.routing.allocation.node_concurrent_recoveries"參數(shù)的值默認(rèn)為 2,應(yīng)該是之前在執(zhí)行縱向擴(kuò)容集群時(shí),為了加快分片遷移速度人為修改了這個(gè)值(因?yàn)榧阂婚_始節(jié)點(diǎn)數(shù)量沒有很多,索引同時(shí)遷移中的分片也不會(huì)太多,所以創(chuàng)建新索引不會(huì)被阻塞)。
4.PUT _cluster/settings
現(xiàn)在通過 PUT _cluster/settings 把"cluster.routing.allocation.node_concurrent_recoveries"參數(shù)修改為 2。但是因?yàn)?quot;put settings"任務(wù)的優(yōu)先級(jí)也是"HIGH", 低于"shard-started"任務(wù)的優(yōu)先級(jí),所以更新該參數(shù)的操作還是會(huì)被阻塞,ES 報(bào)錯(cuò)執(zhí)行任務(wù)超時(shí)。此時(shí),進(jìn)行了多次重試,最終成功把"cluster.routing.allocation.node_concurrent_recoveries"參數(shù)修改為了 2。
5.取消 exclude 配置
現(xiàn)在通過 GET _cluster/health 看到遷移中的分片數(shù)量在逐漸減少,為了不增加新的遷移任務(wù),把執(zhí)行數(shù)據(jù)遷移的 exclude 配置取消掉:
- PUT _cluster/settings
- {
- "transient": {
- "cluster.routing.allocation.exclude._name": ""
- }
- }
6.加速分片遷移
同時(shí)調(diào)大分片恢復(fù)時(shí)節(jié)點(diǎn)進(jìn)行數(shù)據(jù)傳輸?shù)拿棵胱畲笞止?jié)數(shù)(默認(rèn)為 40MB),加速存量的分片遷移任務(wù)的執(zhí)行:
- PUT _cluster/settings
- {
- "transient": {
- "indices": {
- "recovery": {
- "max_bytes_per_sec": "200mb"
- }
- }
- }
- }
7.提前創(chuàng)建索引
現(xiàn)在看到遷移中的分片數(shù)量慢慢減少,新索引已經(jīng)創(chuàng)建成功了,寫入恢復(fù)正常了。到下個(gè)整點(diǎn)時(shí),發(fā)現(xiàn)新建索引還是比較慢,因?yàn)檫€有幾百個(gè)分片在遷移中,創(chuàng)建新索引大概耗時(shí) 5 分鐘,這 5 分鐘內(nèi)寫入也是失敗的。
等幾百個(gè)遷移中的分片都執(zhí)行完畢后,新建索引就比較快了,也不會(huì)再寫入失敗了。但是問題是當(dāng)前正在執(zhí)行云盤節(jié)點(diǎn)切換為本地盤的流程,需要把數(shù)據(jù)從舊的130個(gè)節(jié)點(diǎn)上遷移到新的130個(gè)節(jié)點(diǎn)上,數(shù)據(jù)遷移的任務(wù)不能停,那該怎么辦?既然新創(chuàng)建索引比較慢,那就只好提前把索引都創(chuàng)建好,避免了在每個(gè)整點(diǎn)數(shù)據(jù)寫入失敗的情況。通過編寫python腳本,每天執(zhí)行一次,提前把第二天的每個(gè)小時(shí)的索引創(chuàng)建好,創(chuàng)建完成了再把"cluster.routing.allocation.exclude._name"更改為所有的舊節(jié)點(diǎn),保證數(shù)據(jù)遷移任務(wù)能夠正常執(zhí)行。
8.結(jié)果展示
總量 400TB 的數(shù)據(jù),大約經(jīng)過 10 天左右,終于完成遷移了。配合提前新建索引的 python 腳本,這 10 天內(nèi)也沒有出現(xiàn)寫入失敗的情況。
經(jīng)過了這次擴(kuò)容操作,總結(jié)了如下經(jīng)驗(yàn):
分片數(shù)量過多時(shí),如果同時(shí)進(jìn)行遷移的分片數(shù)量過多,會(huì)阻塞索引創(chuàng)建和其它配置更新操作,所以在進(jìn)行數(shù)據(jù)遷移時(shí),要保證"cluster.routing.allocation.node_concurrent_recoveries"參數(shù)和"cluster.routing.allocation.cluster_concurrent_rebalance"為較小的值。
如果必須要進(jìn)行數(shù)據(jù)遷移,則可以提前創(chuàng)建好索引,避免 ES 自動(dòng)創(chuàng)建索引時(shí)耗時(shí)較久,從而導(dǎo)致寫入失敗。
場景 5:10 萬個(gè)分片?
在穩(wěn)定運(yùn)行了一陣后,集群又出問題了。
XX 公司運(yùn)維 B: bellen, 昨晚凌晨 1 點(diǎn)鐘之后,集群就沒有寫入了,現(xiàn)在 kafka 里有大量的數(shù)據(jù)堆積,麻煩盡快看一下?
通過 cerebro 查看集群,發(fā)現(xiàn)集群處于 yellow 狀態(tài),然后發(fā)現(xiàn)集群有大量的錯(cuò)誤日志:
- {"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"}
然后再進(jìn)一步查看集群日志,發(fā)現(xiàn)有"master not discovered yet..."之類的錯(cuò)誤日志,檢查三個(gè) master 節(jié)點(diǎn),發(fā)現(xiàn)有兩個(gè) master 掛掉,只剩一個(gè)了,集群無法選主。
登陸到掛了的 master 節(jié)點(diǎn)機(jī)器上,發(fā)現(xiàn)?;畛绦驘o法啟動(dòng) es 進(jìn)程,第一直覺是 es 進(jìn)程 oom 了;此時(shí)也發(fā)現(xiàn) master 節(jié)點(diǎn)磁盤使用率 100%, 檢查了 JVM 堆內(nèi)存快照文件目錄,發(fā)現(xiàn)有大量的快照文件,于是刪除了一部分文件,重啟 es 進(jìn)程,進(jìn)程正常啟動(dòng)了;但是問題是堆內(nèi)存使用率太高,gc 非常頻繁,master 節(jié)點(diǎn)響應(yīng)非常慢,大量的創(chuàng)建索引的任務(wù)都超時(shí),阻塞在任務(wù)隊(duì)列中,集群還是無法恢復(fù)正常。
看到集群 master 節(jié)點(diǎn)的配置是 16 核 32GB 內(nèi)存,JVM 實(shí)際只分配了 16GB 內(nèi)存,此時(shí)只好通過對(duì) master 節(jié)點(diǎn)原地增加內(nèi)存到 64GB(虛擬機(jī),使用的騰訊云 CVM, 可以調(diào)整機(jī)器規(guī)格,需要重啟),master 節(jié)點(diǎn)機(jī)器重啟之后,修改了 es 目錄 jvm.options 文件,調(diào)整了堆內(nèi)存大小,重新啟動(dòng)了 es 進(jìn)程。
3 個(gè) master 節(jié)點(diǎn)都恢復(fù)正常了,但是分片還需要進(jìn)行恢復(fù),通過 GET _cluster/health 看到集群當(dāng)前有超過 10w 個(gè)分片,而這些分片恢復(fù)還需要一段時(shí)間,通過調(diào)大"cluster.routing.allocation.node_concurrent_recoveries", 增大分片恢復(fù)的并發(fā)數(shù)量。實(shí)際上 5w 個(gè)主分片恢復(fù)得是比較快的了,但是副本分片的恢復(fù)就相對(duì)慢很多,因?yàn)椴糠指北痉制枰獜闹鞣制贤綌?shù)據(jù)才能恢復(fù)。此時(shí)可以采取的方式是把部分舊的索引副本數(shù)量調(diào)為 0, 讓大量副本分片恢復(fù)的任務(wù)盡快結(jié)束,保證新索引能夠正常創(chuàng)建,從而使得集群能夠正常寫入。
總結(jié)這次故障的根本原因是:集群的索引和分片數(shù)量太多,集群元數(shù)據(jù)占用了大量的堆內(nèi)存,而 master 節(jié)點(diǎn)本身的 JVM 內(nèi)存只有 16GB(數(shù)據(jù)節(jié)點(diǎn)有 32GB), master 節(jié)點(diǎn)頻繁 full gc 導(dǎo)致 master 節(jié)點(diǎn)異常,從而最終導(dǎo)致整個(gè)集群異常。所以要解決這個(gè)問題,還是得從根本上解決集群的分片數(shù)量過多的問題。
目前日志索引是按照小時(shí)創(chuàng)建,60 分片 1 副本,每天有 24*60*2=2880 個(gè)分片,每個(gè)月就產(chǎn)生 86400 個(gè)分片,這么多的分片可能會(huì)帶來嚴(yán)重的問題。有以下幾種方式解決分片數(shù)量過多的問題:
- 可以在 ILM 的 warm phase 中開啟 shrink 功能,對(duì)老的索引從 60 分片 shrink 到 5 分片,分片數(shù)量可以降低 12 倍;
- 業(yè)務(wù)可以把每小時(shí)創(chuàng)建索引修改為每兩個(gè)小時(shí)或者更長,可以根據(jù)每個(gè)分片數(shù)量最多支持 50GB 的數(shù)據(jù)推算多長時(shí)間創(chuàng)建新索引合適;
- 對(duì)老的索引設(shè)置副本為 0,只保留主分片,分片數(shù)量能夠再下降近一倍,存儲(chǔ)量也下降近一倍;
- 定期關(guān)閉最老的索引,執(zhí)行{index}/_close。
和客戶溝通過后,客戶表示可以接受方式 1 和方式 2,但是方式 3 和 4 不能接受,因?yàn)榭紤]到存在磁盤故障的可能性,必須保留一個(gè)副本來保證數(shù)據(jù)的可靠性;另外還必須保證所有數(shù)據(jù)都是隨時(shí)可查詢的,不能關(guān)閉。
場景 6:有點(diǎn)坑的 ILM
在上文中,雖然通過臨時(shí)給 master 節(jié)點(diǎn)增加內(nèi)存,抗住了 10w 分片,但是不能從根本上解決問題??蛻舻臄?shù)據(jù)是計(jì)劃保留一年的,如果不進(jìn)行優(yōu)化,集群必然扛不住數(shù)十萬個(gè)分片。所以接下來需要著重解決集群整體分片數(shù)量過多的問題。前文也提到了,客戶可以接受開啟 shrink 以及降低索引創(chuàng)建粒度(經(jīng)過調(diào)整后,每兩個(gè)小時(shí)創(chuàng)建一個(gè)索引),這在一定程度上減少了分片的數(shù)量,能夠使集群暫時(shí)穩(wěn)定一陣。
輔助客戶在 kibana 上配置了如下的 ILM 策略:
在 warm phase, 把創(chuàng)建時(shí)間超過 360 小時(shí)的索引從 hot 節(jié)點(diǎn)遷移到 warm 節(jié)點(diǎn)上,保持索引的副本數(shù)量為 1。之所以使用 360 小時(shí)作為條件,而不是 15 天作為條件,是因?yàn)榭蛻舻乃饕前葱r(shí)創(chuàng)建的,如果以 15 天作為遷移條件,則在每天凌晨都會(huì)同時(shí)觸發(fā) 15 天前的 24 個(gè)索引一共 24*120=2880 個(gè)分片同時(shí)開始遷移索引,容易引發(fā)前文介紹的由于遷移分片數(shù)量過多導(dǎo)致創(chuàng)建索引被阻塞的問題。所以以 360 小時(shí)作為條件,則在每個(gè)小時(shí)只會(huì)執(zhí)行一個(gè)索引的遷移,這樣把 24 個(gè)索引的遷移任務(wù)打平,避免其它任務(wù)被阻塞的情況發(fā)生。
同時(shí),也在 warm phase 階段,設(shè)置索引 shrink,把索引的分片數(shù)縮成 5 個(gè),因?yàn)槔系乃饕呀?jīng)不執(zhí)行寫入了,所以也可以執(zhí)行 force merge, 強(qiáng)制把 segment 文件合并為 1 個(gè),可以獲得更好的查詢性能。
另外,設(shè)置了 ILM 策略后,可以在索引模板里增加 index.lifecycle.name 配置,使得所有新創(chuàng)建的索引都可以和新添加的 ILM 策略關(guān)聯(lián),從而使得 ILM 能夠正常運(yùn)行。
客戶使用的 ES 版本是 6.8.2, 在運(yùn)行 ILM 的過程中, 也發(fā)現(xiàn)一些問題:
- 新添加的策略只能對(duì)新創(chuàng)建的索引生效,存量的索引只能通過批量修改索引 settings 里的 index.lifecycle.name 執(zhí)行策略;
- 如果一個(gè)策略進(jìn)行了修改,那么所有存量的索引,不管是有沒有執(zhí)行過該策略,都不會(huì)執(zhí)行修改后的策略,也即修改后的策略只對(duì)修改成功后新創(chuàng)建的索引生效。比如一開始的策略沒有開啟 shrink, 現(xiàn)在修改策略內(nèi)容添加了 shrink 操作,那么只有之后新創(chuàng)建的索引在達(dá)到策略觸發(fā)條件(比如索引已經(jīng)創(chuàng)建超過 360 個(gè)小時(shí))后才會(huì)執(zhí)行 shrink, 而之前的所有索引都不會(huì)執(zhí)行 shrink,此時(shí)若想對(duì)存量的索引也執(zhí)行 shrink,只能夠通過腳本批量執(zhí)行了;
- 在 warm phase 同時(shí)執(zhí)行索引遷移和 shrink 會(huì)觸發(fā) es 的 bug, 如上圖中的 ILM 策略,索引本身包含 60 分片 1 副本,初始時(shí)都在 hot 節(jié)點(diǎn)上,在創(chuàng)建完成 360 小時(shí)之后,會(huì)執(zhí)行遷移,把索引都遷移到 warm 節(jié)點(diǎn)上,同時(shí)又需要把分片 shrink 到 5,在實(shí)際執(zhí)行中,發(fā)現(xiàn)一段時(shí)間后有大量的 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]"
- }
這是因?yàn)?shrink 操作需要新把索引完整的一份數(shù)據(jù)都遷移到一個(gè)節(jié)點(diǎn)上,然后在內(nèi)存中構(gòu)建新的分片元數(shù)據(jù),把新的分片通過軟鏈接指向到幾個(gè)老的分片的數(shù)據(jù),在 ILM 中執(zhí)行 shrink 時(shí),ILM 會(huì)對(duì)索引進(jìn)行如下配置:
- {
- "index.routing" : {
- "allocation" : {
- "require" : {
- "temperature" : "warm",
- "_id" : "LKsSwrDsSrSPRZa-EPBJPg"
- }
- }
- }
- }
問題是索引包含副本,而主分片和副本分片又不能在同一個(gè)節(jié)點(diǎn)上,所以會(huì)出現(xiàn)部分分片無法分配的情況(不是全部,只有一部分)。這里應(yīng)該是觸發(fā)了 6.8 版本的 ILM 的 bug,需要查看源碼才能定位解決這個(gè) bug,目前還在研究中。當(dāng)前的 workaround 是通過腳本定期掃描出現(xiàn) unassigned shards 的索引,修改其 settings:
- {
- "index.routing" : {
- "allocation" : {
- "require" : {
- "temperature" : "warm",
- "_id" : "LKsSwrDsSrSPRZa-EPBJPg"
- }
- }
- }
- }
優(yōu)先保證分片先從 hot 節(jié)點(diǎn)遷移到 warm 節(jié)點(diǎn),這樣后續(xù)的 shrink 才能順利執(zhí)行(也可能執(zhí)行失敗,因?yàn)?60 個(gè)分片都在一個(gè)節(jié)點(diǎn)上,可能會(huì)觸發(fā) rebalance, 導(dǎo)致分片遷移走,shrink 的前置條件又不滿足,導(dǎo)致執(zhí)行失敗)。要完全規(guī)避這個(gè)問題,還得在 ILM 策略中設(shè)置,滿足創(chuàng)建時(shí)間超過 360 個(gè)小時(shí)的索引,副本直接調(diào)整為 0,但是客戶又不接受,沒辦法。
場景 7:自己實(shí)現(xiàn) SLM
上文介紹了 10w 個(gè)分片會(huì)給集群帶來的影響和通過開啟 shrink 來降低分片數(shù)量,但是仍然有兩個(gè)需要重點(diǎn)解決的問題:
- 索引不斷新建,如何保證一年內(nèi),集群總的分片數(shù)量不高于 10w,穩(wěn)定在一個(gè)較低的水位?
- ILM 中執(zhí)行 shrink 可能會(huì)導(dǎo)致部分分片未分配以及 shrink 執(zhí)行失敗,怎么徹底解決呢?
可以估算一下,按小時(shí)建索引,60 分片 1 副本,一年的分片數(shù)為 24*120*365=1051200 個(gè)分片,執(zhí)行 shrink 后分片數(shù)量 24*10*350 + 24*120*15 = 127200(15 天內(nèi)的新索引為了保障寫入性能和數(shù)據(jù)可靠性,仍然保持 60 分片 1 副本,舊的索引 shrink 為 5 分片 1 副本), 仍然有超過 10w 個(gè)分片。結(jié)合集群一年總的存儲(chǔ)量和單個(gè)分片可以支持的數(shù)據(jù)量大小進(jìn)行評(píng)估,我們期望集群總體的分片數(shù)量可以穩(wěn)定為 6w~8w,怎么優(yōu)化?
可以想到的方案是執(zhí)行數(shù)據(jù)冷備份,把比較老的索引都冷備到其它的存儲(chǔ)介質(zhì)上比如 HDFS、S3、騰訊云的 COS 對(duì)象存儲(chǔ)等。但是問題是這些冷備的數(shù)據(jù)如果也要查詢,需要先恢復(fù)到 ES 中才可查,恢復(fù)速度比較慢,客戶無法接受。由此也產(chǎn)生了新的想法,目前老的索引仍然是 1 副本,可以把老索引先進(jìn)行冷備份,再把副本調(diào)為 0,這樣做有以下幾點(diǎn)好處:
- 集群整體分片數(shù)量能降低一半;
- 數(shù)據(jù)存儲(chǔ)量也能降低一半,集群可以存儲(chǔ)更多數(shù)據(jù);
- 老的索引仍然隨時(shí)可查;
- 極端情況下,磁盤故障引起只有一個(gè)副本的索引數(shù)據(jù)無法恢復(fù)時(shí),可以從冷備介質(zhì)中進(jìn)行恢復(fù)。
經(jīng)過和客戶溝通,客戶接受了上述方案,計(jì)劃把老索引冷備到騰訊云的對(duì)象存儲(chǔ) COS 中,實(shí)施步驟為:
- 所有存量的老索引,需要批量處理,盡快地備份到 COS 中,然后批量修改副本數(shù)量為 0;
- 最近新建的索引,采用按天備份的策略,結(jié)合 ILM, 修改策略,在 ILM 執(zhí)行過程中修改索引副本數(shù)為 0(ILM 的 warm phase 和 cold phase 都支持設(shè)置副本數(shù)量)。
其中第一個(gè)步驟的實(shí)施可以通過腳本實(shí)現(xiàn),本案例中就采用了騰訊云 SCF 云函數(shù)進(jìn)行實(shí)施,方便快捷可監(jiān)控。實(shí)施要點(diǎn)有:
- 按天創(chuàng)建 snapshot,批量備份每天產(chǎn)生的 24 個(gè)索引,如果是按月或者更大粒度創(chuàng)建快照,因數(shù)據(jù)量太大如果執(zhí)行快照過程中出現(xiàn)中斷,則必須全部重來,耗時(shí)耗力;按小時(shí)創(chuàng)建快照也不適用,會(huì)造成快照數(shù)量太多,可能會(huì)踩到坑。
- 每創(chuàng)建一個(gè)快照,后續(xù)需要輪詢快照的狀態(tài),保證前一個(gè)快照 state 為"SUCCESS"之后,再創(chuàng)建下一個(gè)快照;因?yàn)榭煺帐前刺靹?chuàng)建的,快照名字可以為 snapshot-2020.06.01, 該快照只備份 6 月 1 號(hào)的所有索引。而在檢查到 snapshot-2020.06.01 快照執(zhí)行成功后,然后新建下一個(gè)快照時(shí),需要知道要對(duì)哪天的索引打快照,因此需要記錄當(dāng)前正在執(zhí)行哪一個(gè)快照。有兩種方式記錄,一是把當(dāng)前正在執(zhí)行的快照日期后綴"2020.06.01"寫入到文件中, 腳本通過定時(shí)任務(wù)輪詢時(shí),每次都讀文件;另外一種方式是創(chuàng)建一個(gè)臨時(shí)的索引,把"2020.06.01"寫入到這個(gè)臨時(shí)索引的一個(gè) doc 中,之后對(duì)該 doc 進(jìn)行查詢或者更新。
- 創(chuàng)建快照時(shí),可以把"include_global_state"置為 false, 不對(duì)集群的全局狀態(tài)信息進(jìn)行備份。
在實(shí)施完第一個(gè)步驟之后,就可以批量把對(duì)索引進(jìn)行過備份的索引副本數(shù)都調(diào)為 0, 這樣一次性釋放了很多磁盤空間,并且顯著降低了集群整體的分片數(shù)量。
接下來實(shí)施第二個(gè)步驟,需要每天執(zhí)行一次快照,多創(chuàng)建時(shí)間較久的索引進(jìn)行備份。實(shí)施比較簡單,可以通過 crontab 定時(shí)執(zhí)行腳本或者使用騰訊云 SCF 執(zhí)行。
之后,就可以修改 ILM 策略,開啟 cold phase, 修改索引副本數(shù)量為 0:
此處的 timing 是創(chuàng)建時(shí)間 20 天后,需要保證在第二步驟中,對(duì)過去老索引數(shù)據(jù)備份先執(zhí)行完成后才可以進(jìn)入到 cold phase.
通過老索引數(shù)據(jù)冷備并且降低索引副本,我們可以把集群整體的分片數(shù)量維持在一個(gè)較低的水位。但是還有另外一個(gè)問題待解決,也即 shrink 失敗的問題。剛好,我們可以利用對(duì)老索引數(shù)據(jù)冷備并且降低索引副本的方案,來徹底解決 shrink 失敗的問題。
在前文有提到,shrink 失敗歸根結(jié)底是因?yàn)樗饕母北緮?shù)量為 1, 現(xiàn)在我們可以把數(shù)據(jù)備份和降低副本提前,讓老索引進(jìn)入到 ILM 的 warm phase 中時(shí)已經(jīng)是 0 副本,之后再執(zhí)行 shrink 操作就不會(huì)有問題了;同時(shí),因?yàn)楦北窘档土?,索引?hot 節(jié)點(diǎn)遷移到 warm 節(jié)點(diǎn)遷移的數(shù)據(jù)量也減少了一半,從而降低了集群負(fù)載,一舉兩得。
因此,我們需要修改 ILM 策略,在 warm phase 就把索引的副本數(shù)量調(diào)整為 0, 然后去除 cold phase。
另外一個(gè)可選的優(yōu)化項(xiàng)是:對(duì)老的索引進(jìn)行凍結(jié),凍結(jié)索引是指把索引常駐內(nèi)存的一些數(shù)據(jù)從內(nèi)存中清理掉(比如 FST, 元數(shù)據(jù)等), 從而降低內(nèi)存使用量,而在查詢已經(jīng)凍結(jié)的索引時(shí),會(huì)重新構(gòu)建出臨時(shí)的索引數(shù)據(jù)結(jié)構(gòu)存放在內(nèi)存中,查詢完畢再清理掉;需要注意的是,默認(rèn)情況下是無法查詢已經(jīng)凍結(jié)的索引的,需要在查詢時(shí)顯式的增加"ignore_throttled=false"參數(shù)。
經(jīng)過上述優(yōu)化,我們最終解決了集群整體分片數(shù)量過多和 shrink 失敗的問題。在實(shí)施過程中引入了額外的定時(shí)任務(wù)腳本實(shí)施自動(dòng)化快照,實(shí)際上在 7.4 版本的 ES 中,已經(jīng)有這個(gè)功能了,特性名稱為SLM(快照生命周期管理),并且可以結(jié)合 ILM 使用,在 ILM 中增加了"wait_for_snapshot"的 ACTION, 但是卻只能在 delete phase 中使用,不滿足我們的場景。
場景 8:客戶十分喜歡的 Searchable Snapshots!
在上述的場景中,我們花費(fèi)大量的精力去解決問題和優(yōu)化使用方式,保證 ES 集群能夠穩(wěn)定運(yùn)行,支持 PB 級(jí)別的存儲(chǔ)。溯本回原,如果我們能有一個(gè)方案使得客戶只需要把熱數(shù)據(jù)放在 SSD 盤上,然后冷數(shù)據(jù)存儲(chǔ)到 COS/S3 上,但同時(shí)又使冷數(shù)據(jù)能夠支持按需隨時(shí)可查,那我們前面碰到的所有問題都迎刃而解了??梢韵胂蟮玫降暮锰幱校?/p>
- 只需要更小規(guī)模的集群和非常廉價(jià)的 COS/S3 對(duì)象存儲(chǔ)就可以支持 PB 級(jí)別的數(shù)據(jù)量,客戶的資金成本非常低;
- 小規(guī)模的集群只需要能夠支撐熱索引的寫入和查詢即可,集群整體的分片數(shù)不會(huì)太多,從而避免了集群不穩(wěn)定現(xiàn)象的發(fā)生。
而這正是目前 es 開源社區(qū)正在開發(fā)中的 Searchable Snapshots 功能,從Searchable Snapshots API的官方文檔上可以看到,我們可以創(chuàng)建一個(gè)索引,將其掛載到一個(gè)指定的快照中,這個(gè)新的索引是可查詢的,雖然查詢時(shí)間可能會(huì)慢點(diǎn),但是在日志場景中,對(duì)一些較老的索引進(jìn)行查詢時(shí),延遲大點(diǎn)一般都是可以接受的。
所以我認(rèn)為,Searchable Snapshots 解決了很多痛點(diǎn),將會(huì)給 ES 帶來新的繁榮!
總結(jié)
經(jīng)歷過上述運(yùn)維和優(yōu)化 ES 集群的實(shí)踐,我們總結(jié)到的經(jīng)驗(yàn)有:
- 新集群上線前務(wù)必做好集群規(guī)模和節(jié)點(diǎn)規(guī)格的評(píng)估
- 集群整體的分片數(shù)量不能太多,可以通過調(diào)整使用方式并且借助 ES 本身的能力不斷進(jìn)行優(yōu)化,使得集群總體的分片數(shù)維持在一個(gè)較低的水位,保證集群的穩(wěn)定性
- Searchable Snapshots 利器會(huì)給 ES 帶來新的生命力,需要重點(diǎn)關(guān)注并研究其實(shí)現(xiàn)原理
從一開始和客戶進(jìn)行接觸,了解客戶訴求,逐步解決 ES 集群的問題,最終使得 ES 集群能夠保持穩(wěn)定,這中間的經(jīng)歷讓我真真正正地領(lǐng)悟到"實(shí)踐出真知"這句話的真諦,只有不斷實(shí)踐,才能對(duì)異常情況迅速做出反應(yīng),以及對(duì)客戶提的優(yōu)化需求迅速反饋。