架構(gòu):計(jì)算存儲(chǔ)分離解決集群擴(kuò)縮容難題)
手頭有個(gè)搜索和日志分析的場景數(shù)據(jù)量說大不大說小不小但業(yè)務(wù)波動(dòng)特別明顯——白天高峰和夜間低谷能差幾十倍。用傳統(tǒng) Elasticsearch 集群扛這種流量要么常年空轉(zhuǎn)浪費(fèi)資源要么擴(kuò)容速度跟不上突發(fā)流量。后來我把項(xiàng)目遷到了 Elasticsearch Serverless 架構(gòu)上核心思路就是無狀態(tài)化計(jì)算節(jié)點(diǎn)不保存任何持久數(shù)據(jù)所有數(shù)據(jù)都落到遠(yuǎn)程共享存儲(chǔ)這才算徹底解決了我的痛點(diǎn)。這篇文章不聊PPT式的架構(gòu)演進(jìn)就從我實(shí)際遷移和運(yùn)維的視角把 Elasticsearch Serverless 到底怎么做到無狀態(tài)、數(shù)據(jù)怎么流動(dòng)、部署時(shí)要注意什么、遇到問題怎么排查一條線講清楚。無論你是剛開始接觸 ES 的新手還是在為集群擴(kuò)縮容發(fā)愁的老手這篇應(yīng)該都能給你一些可以直接落地的參考。1. 傳統(tǒng)集群的痛點(diǎn)以及無狀態(tài)架構(gòu)的解題思路1.1 傳統(tǒng) Elasticsearch 集群為什么難伺候先說結(jié)論傳統(tǒng) ES 集群的痛點(diǎn)幾乎全來自狀態(tài)這兩個(gè)字。所謂狀態(tài)就是節(jié)點(diǎn)本地保存了數(shù)據(jù)、分片、副本、集群元數(shù)據(jù)。每個(gè)數(shù)據(jù)節(jié)點(diǎn)都要維護(hù)自己的分片副本又要把分片狀態(tài)和集群狀態(tài)保持一致這讓節(jié)點(diǎn)變得非常重。我舉個(gè)例子一個(gè) 3 節(jié)點(diǎn)的集群每新增一個(gè)節(jié)點(diǎn)新節(jié)點(diǎn)要先把分片數(shù)據(jù)拷貝過去集群要做數(shù)據(jù)再平衡rebalance這個(gè)過程對磁盤 IO 和網(wǎng)絡(luò)帶寬的消耗都很大。而且節(jié)點(diǎn)掛了之后其它節(jié)點(diǎn)要重新分配副本稍有疏忽就容易出現(xiàn) yellow 或 red 狀態(tài)。另一個(gè)繞不開的問題是擴(kuò)縮容。業(yè)務(wù)流量漲了你想把集群從 5 個(gè)節(jié)點(diǎn)擴(kuò)到 20 個(gè)節(jié)點(diǎn)正常情況下必須等待分片遷移完成這個(gè)等待時(shí)間往往以小時(shí)計(jì)。反過來流量降下來想縮容還得小心翼翼地把某個(gè)節(jié)點(diǎn)上的分片先移走否則又會(huì)出現(xiàn)數(shù)據(jù)丟失風(fēng)險(xiǎn)。這種操作非常依賴運(yùn)維經(jīng)驗(yàn)不敢輕易自動(dòng)化。還有單分片容量、副本數(shù)、磁盤空間、內(nèi)存分配這些規(guī)劃問題。我印象很深的一回集群里一個(gè)索引的分片計(jì)劃定得太粗結(jié)果半年后單分片撐到了接近 40GB查詢速度明顯下降但那時(shí)候再改分片數(shù)就得重建索引相當(dāng)痛苦。這些運(yùn)維負(fù)擔(dān)說到底是等價(jià)的你選擇了有狀態(tài)的數(shù)據(jù)持久化方式就要接受帶狀態(tài)的管理成本。1.2 無狀態(tài)架構(gòu)無掉的是數(shù)據(jù)節(jié)點(diǎn)里的持久數(shù)據(jù)Elasticsearch Serverless 的核心設(shè)計(jì)思路不是把 Elasticsearch 功能砍掉而是把數(shù)據(jù)存儲(chǔ)從計(jì)算節(jié)點(diǎn)上徹底剝離出來。傳統(tǒng)架構(gòu)里一個(gè)數(shù)據(jù)節(jié)點(diǎn)的本地磁盤就承擔(dān)著分片數(shù)據(jù)的持久化任務(wù)。無狀態(tài)架構(gòu)則完全不同數(shù)據(jù)節(jié)點(diǎn)本質(zhì)上只是一臺(tái)計(jì)算資源它本地不保存任何必須長期保留的數(shù)據(jù)所有分片數(shù)據(jù)都以 segment 文件的形式寫入遠(yuǎn)程共享存儲(chǔ)比如對象存儲(chǔ)或者共享文件系統(tǒng)。節(jié)點(diǎn)本地的磁盤只用來放緩存和臨時(shí)文件隨時(shí)可以被清空、被替換。這就像把一間小賣部改成了中央倉庫加臨時(shí)攤位。攤位只負(fù)責(zé)把商品擺出來賣商品本身都鎖在倉庫里。攤位燒了、搬了換一個(gè)攤位繼續(xù)賣就行不用一箱箱把貨搬走。落到 Elasticsearch 的實(shí)現(xiàn)上數(shù)據(jù)節(jié)點(diǎn)啟動(dòng)時(shí)不需要從其它數(shù)據(jù)節(jié)點(diǎn)復(fù)制數(shù)據(jù)只需要從遠(yuǎn)程存儲(chǔ)讀取分片元數(shù)據(jù)和 segment 文件。所以新節(jié)點(diǎn)加入集群幾乎秒級完成。節(jié)點(diǎn)異常退出也沒有影響——它本身沒有必須搶救的數(shù)據(jù)頂多是本地緩存丟失重新從遠(yuǎn)程存儲(chǔ)拉一遍就好。1.3 什么場景適合無狀態(tài)什么場景不建議我自己的判斷標(biāo)準(zhǔn)很簡單如果你的業(yè)務(wù)有明顯的流量波峰波谷或者你想讓一套搜索/日志系統(tǒng)能按需伸縮那 Serverless 無狀態(tài)架構(gòu)非常合適因?yàn)樗奄Y源成本和數(shù)據(jù)副本解耦了。反過來說如果你的數(shù)據(jù)量很小、流量常年平穩(wěn)、團(tuán)隊(duì)對傳統(tǒng) ES 運(yùn)維已經(jīng)很熟練那就沒必要引入這套復(fù)雜度。加一個(gè)遠(yuǎn)程存儲(chǔ)引入新的網(wǎng)絡(luò)開銷和配置項(xiàng)帶來的收益并不明顯。小集群上傳統(tǒng)架構(gòu)完全夠用硬上一個(gè)無狀態(tài)架構(gòu)反而增加故障點(diǎn)。另外要注意無狀態(tài)架構(gòu)對網(wǎng)絡(luò)帶寬和遠(yuǎn)程存儲(chǔ)的讀寫性能要求很高。如果你所在機(jī)房到對象存儲(chǔ)的延遲超過幾十毫秒查詢和寫入都會(huì)明顯變慢。這一點(diǎn)在規(guī)劃階段就得想清楚。2. 核心設(shè)計(jì)拆解計(jì)算存儲(chǔ)分離背后的三層角色2.1 三層角色分裂協(xié)調(diào)、數(shù)據(jù)與存儲(chǔ)Elasticsearch Serverless 的邏輯架構(gòu)我習(xí)慣分成三層來看。第一層是協(xié)調(diào)層Coordinator。它負(fù)責(zé)接收客戶端請求解析查詢做權(quán)限校驗(yàn)把請求路由到合適的數(shù)據(jù)節(jié)點(diǎn)然后匯總結(jié)果返回。協(xié)調(diào)層本身不存數(shù)據(jù)也不做數(shù)據(jù)持久化所以它可以非常輕量地做水平擴(kuò)展。第二層是數(shù)據(jù)節(jié)點(diǎn)層Data Node。這是干活的角色負(fù)責(zé)執(zhí)行寫入、查詢、聚合。每個(gè)數(shù)據(jù)節(jié)點(diǎn)在收到請求后會(huì)操作本地的緩存數(shù)據(jù)如果緩存未命中就去遠(yuǎn)程存儲(chǔ)拉取相應(yīng)的 segment。數(shù)據(jù)節(jié)點(diǎn)沒有任何持久化存儲(chǔ)的義務(wù)它可以隨時(shí)銷毀重建。第三層是存儲(chǔ)層Storage。也就是遠(yuǎn)程共享存儲(chǔ)保存了所有索引的 segment 文件、translog 以及分片元數(shù)據(jù)。這一層才是整個(gè)集群唯一的狀態(tài)持有者。存儲(chǔ)層通常由對象存儲(chǔ)承擔(dān)它天然具備高可用性、高持久性以及無限擴(kuò)展能力。傳統(tǒng) ES 的分片有主備副本概念在無狀態(tài)架構(gòu)里發(fā)生了變化。副本不再是完整的數(shù)據(jù)拷貝而是同一份 segment 文件在遠(yuǎn)程共享存儲(chǔ)上的不同引用方式。數(shù)據(jù)節(jié)點(diǎn)不需要各自持有一份完整副本避免了很多一致性問題。2.2 數(shù)據(jù)到底怎么進(jìn)入倉庫要理解數(shù)據(jù)寫入流程我們先說 Elasticsearch 中的一個(gè)核心概念segment段。ES 中每個(gè)分片的數(shù)據(jù)實(shí)際是由若干不可變的 segment 文件組成的新寫入的數(shù)據(jù)先進(jìn)入內(nèi)存 buffer等到滿足刷新條件默認(rèn) 1 秒后生成一個(gè)新 segment再經(jīng)過合并merge把多個(gè)小 segment 合并成一個(gè)大 segment。在有狀態(tài)的傳統(tǒng)架構(gòu)里這些 segment 直接寫進(jìn)節(jié)點(diǎn)本地磁盤。在無狀態(tài)架構(gòu)里segment 生成之后會(huì)被上傳到遠(yuǎn)程共享存儲(chǔ)節(jié)點(diǎn)本地只保留最近一小部分熱數(shù)據(jù)的緩存。至于 translog事務(wù)日志為了保證寫入不丟同樣會(huì)同步到遠(yuǎn)程存儲(chǔ)。所以數(shù)據(jù)寫入的鏈路大概是這樣客戶端請求到達(dá)協(xié)調(diào)層協(xié)調(diào)層路由給某個(gè)數(shù)據(jù)節(jié)點(diǎn)數(shù)據(jù)節(jié)點(diǎn)寫 translog 并寫入內(nèi)存 bufferperiodic flush 后生成 segment再把 segment 和 translog 上傳遠(yuǎn)程存儲(chǔ)最后返回客戶端寫入成功。這里實(shí)際上做了一個(gè)很重要的取舍用一次遠(yuǎn)程網(wǎng)絡(luò)寫入來換取數(shù)據(jù)節(jié)點(diǎn)的完全無狀態(tài)。如果你的業(yè)務(wù)寫入量特別大、對 p99 延遲要求又非??量踢@種設(shè)計(jì)相對傳統(tǒng)本地寫入確實(shí)多了網(wǎng)絡(luò)開銷這也是為什么無狀態(tài)架構(gòu)更適合流量可彈性伸縮 對延遲不是極度敏感的場景。2.3 為什么必須要一個(gè)遠(yuǎn)程共享存儲(chǔ)很多剛接觸無狀態(tài)架構(gòu)的同事會(huì)問我能不能用數(shù)據(jù)節(jié)點(diǎn)之間的復(fù)制來替代遠(yuǎn)程存儲(chǔ)答案是否定的。無狀態(tài)的前提是節(jié)點(diǎn)隨時(shí)可以被替換。如果數(shù)據(jù)還是放在某個(gè)節(jié)點(diǎn)的本地磁盤那節(jié)點(diǎn)掛了數(shù)據(jù)就持有狀態(tài)的那部分就丟了。遠(yuǎn)程共享存儲(chǔ)之所以必要是因?yàn)樗羌喝w節(jié)點(diǎn)都能訪問、且不依賴于某一個(gè)具體節(jié)點(diǎn)存活的公共底座。數(shù)據(jù)只要進(jìn)了遠(yuǎn)程存儲(chǔ)業(yè)務(wù)計(jì)算節(jié)點(diǎn)再怎么增減數(shù)據(jù)都在。從實(shí)現(xiàn)層面看遠(yuǎn)程共享存儲(chǔ)可以理解為一個(gè)大得多的分布式文件系統(tǒng)但它和本地文件系統(tǒng)有一個(gè)明顯的不同它不保證低延遲的隨機(jī)寫。所以 Elasticsearch 在無狀態(tài)架構(gòu)里會(huì)把寫操作盡量合并成較大的 segment 文件再上傳減少小文件頻繁上傳的開銷。這也是為什么無狀態(tài) ES 通常會(huì)調(diào)整合并策略和刷新頻率讓 segment 足夠大、數(shù)量足夠少。3. 無狀態(tài)架構(gòu)與傳統(tǒng)架構(gòu)的對比與選型判斷3.1 一張表看懂兩者的關(guān)鍵差異對比項(xiàng)傳統(tǒng) ES 架構(gòu)Serverless 無狀態(tài)架構(gòu)數(shù)據(jù)持久化位置節(jié)點(diǎn)本地磁盤遠(yuǎn)程共享存儲(chǔ)對象存儲(chǔ)等節(jié)點(diǎn)故障影響需要重新分配分片集群可能變紅節(jié)點(diǎn)被替換數(shù)據(jù)從遠(yuǎn)程恢復(fù)擴(kuò)容速度依賴分片遷移分鐘到小時(shí)級秒級拉起新節(jié)點(diǎn)縮容復(fù)雜度需遷移分片并規(guī)避數(shù)據(jù)丟失風(fēng)險(xiǎn)直接銷毀節(jié)點(diǎn)即可存儲(chǔ)彈性受限于節(jié)點(diǎn)磁盤配額獨(dú)立擴(kuò)展幾乎無限計(jì)算/存儲(chǔ)耦合高度耦合規(guī)劃困難完全解耦按需分配成本模型按節(jié)點(diǎn)固定成本付費(fèi)按實(shí)際計(jì)算資源和存儲(chǔ)用量付費(fèi)運(yùn)維深度需要專業(yè) ES 運(yùn)維經(jīng)驗(yàn)平臺(tái)托管為主運(yùn)維成本低延遲敏感度適合對延遲極度苛刻的場景多一層網(wǎng)絡(luò)開銷延遲略高3.2 從幾個(gè)核心指標(biāo)看架構(gòu)變化我最直觀的感受在三個(gè)方面第一擴(kuò)容時(shí)間從小時(shí)級變成分鐘級。以前加節(jié)點(diǎn)分片遷移要等磁盤 IO 一點(diǎn)點(diǎn)搬現(xiàn)在新節(jié)點(diǎn)啟動(dòng)后直接從遠(yuǎn)程存儲(chǔ)加載元數(shù)據(jù)只要計(jì)算資源到位基本上兩三分鐘就能接入集群服務(wù)查詢。第二CPU 和磁盤不再綁定。傳統(tǒng)架構(gòu)里磁盤滿了往往 CPU 還很閑想加計(jì)算能力就不得不擴(kuò)大磁盤造成浪費(fèi)。無狀態(tài)架構(gòu)下存儲(chǔ)獨(dú)立擴(kuò)展計(jì)算資源按需增減成本模型更精確。第三故障恢復(fù)的心態(tài)變了。以前晚上收到節(jié)點(diǎn)宕機(jī)告警第一反應(yīng)是分片有沒有損壞、副本能不能頂上。現(xiàn)在節(jié)點(diǎn)宕機(jī)對我而言只是少了一臺(tái)計(jì)算資源遠(yuǎn)程存儲(chǔ)里的數(shù)據(jù)還在我完全不慌。這種心態(tài)上的轉(zhuǎn)變實(shí)際上就是架構(gòu)穩(wěn)定性提升的體現(xiàn)。3.3 選型判斷什么情況下可以遷移我建議你按這三步來做判斷第一步評估流量特征。如果一天的流量波動(dòng)超過 3 倍或者未來有比較明顯的增長預(yù)期無狀態(tài)架構(gòu)的價(jià)值就很大。第二步評估延遲需求。你的業(yè)務(wù)查詢響應(yīng)要求是幾百毫秒級別還是秒級如果要求極端的低延遲且數(shù)據(jù)量不大傳統(tǒng)架構(gòu)反而更穩(wěn)。第三步評估團(tuán)隊(duì)能力。你們有沒有專門的 ES 運(yùn)維人力如果沒有選擇托管式的 Serverless 服務(wù)能節(jié)省很多精力如果有那也可以基于開源組件自建無狀態(tài)集群但那個(gè)復(fù)雜度和運(yùn)維成本就不低了。4. 實(shí)操記錄本地啟動(dòng)與服務(wù)部署的完整過程4.1 Windows 本地啟動(dòng) Elasticsearch 的注意點(diǎn)說實(shí)話網(wǎng)上說Windows 啟動(dòng) Elasticsearch的教程很多但踩坑的細(xì)節(jié)沒幾個(gè)人講清楚。第一步是準(zhǔn)備 JDK。Elasticsearch 7.17 之后的版本內(nèi)置了捆綁的 JDK但建議你還是單獨(dú)安裝一個(gè) JDK 并配置好環(huán)境變量方便后續(xù)操作和排查。這里有個(gè)我踩過幾次的坑如果你機(jī)器上裝過其它軟件環(huán)境變量里的 PATH 順序可能導(dǎo)致 ES 用了錯(cuò)誤版本的 Java建議在命令行里先執(zhí)行java -version確認(rèn)版本。第二步是下載對應(yīng)版本的安裝包。盡量在官方渠道下載不要用不明來源的整合包。下載 zip 包后解壓注意路徑不要包含中文和空格否則后續(xù)腳本執(zhí)行容易出問題。第三步是修改配置。在config/elasticsearch.yml里本地單機(jī)測試至少要把discovery.type設(shè)置為single-node否則默認(rèn)的zen發(fā)現(xiàn)機(jī)制會(huì)嘗試找其它節(jié)點(diǎn)一直在 bootstrap 階段卡住。內(nèi)存設(shè)置方面在config/jvm.options里把-Xms和-Xmx設(shè)為一樣大小避免運(yùn)行時(shí)堆擴(kuò)容的停頓建議設(shè)為本機(jī)物理內(nèi)存的一半左右比如 8GB 內(nèi)存的機(jī)器就設(shè) 4GB。第四步是啟動(dòng)。Windows 下直接運(yùn)行bin/elasticsearch.bat然后訪問http://localhost:9200能得到一個(gè) JSON 響應(yīng)就說明啟動(dòng)成功了。這里我可以給一段最小啟動(dòng)步驟清單下載 zip 包并解壓配置JAVA_HOME環(huán)境變量修改config/elasticsearch.yml設(shè)置cluster.name和discovery.type: single-node設(shè)置jvm.options里的堆內(nèi)存執(zhí)行bin\elasticsearch.bat啟動(dòng)訪問http://localhost:9200驗(yàn)證4.2 Serverless 部署的配置與驗(yàn)證流程當(dāng)你準(zhǔn)備部署到 Serverless 環(huán)境事情就從裝一個(gè)單機(jī)進(jìn)程變成了管理一套自動(dòng)伸縮的計(jì)算集群。我用過的 Serverless 部署方式主要面向云廠商托管服務(wù)或者基于容器平臺(tái)自建。先說托管方式。通常你只需要?jiǎng)?chuàng)建一個(gè) Serverless 項(xiàng)目填入要支持的地域、數(shù)據(jù)歸屬和網(wǎng)絡(luò)配置剩下的數(shù)據(jù)節(jié)點(diǎn)數(shù)量、分片分配都由平臺(tái)自動(dòng)搞定。部署完可以直接拿到一個(gè)專用的接入地址體驗(yàn)非常清爽。如果是自建我會(huì)建議把數(shù)據(jù)節(jié)點(diǎn)做成容器化工作負(fù)載讓它跑在支持橫向擴(kuò)容的容器平臺(tái)上。部署的關(guān)鍵點(diǎn)有三處第一處節(jié)點(diǎn)配置里要指定遠(yuǎn)程存儲(chǔ)的地址和認(rèn)證信息。比如在elasticsearch.yml里配置對象存儲(chǔ)的 endpoint、bucket 和密鑰這些是數(shù)據(jù)節(jié)點(diǎn)連接遠(yuǎn)程存儲(chǔ)的橋梁。第二處必須保證所有配置的不變性。節(jié)點(diǎn)鏡像一旦構(gòu)建好運(yùn)行參數(shù)不再修改要變就重新構(gòu)建鏡像。這樣節(jié)點(diǎn)掛了被替換時(shí)新起的節(jié)點(diǎn)就和舊的完全一樣這是無狀態(tài)部署的基礎(chǔ)。第三處要配置數(shù)據(jù)節(jié)點(diǎn)的存活探針。平臺(tái)應(yīng)該能夠定期檢查節(jié)點(diǎn)健康狀態(tài)一旦發(fā)現(xiàn)節(jié)點(diǎn)異常立刻拉一個(gè)新的節(jié)點(diǎn)替換而不需要人工介入。部署完成后的驗(yàn)證我建議做三步檢查第一步確認(rèn)所有節(jié)點(diǎn)都能從遠(yuǎn)程存儲(chǔ)讀取分片元數(shù)據(jù)可以在數(shù)據(jù)節(jié)點(diǎn)的啟動(dòng)日志里看到recovered相關(guān)記錄。第二步隨機(jī)殺掉一個(gè)節(jié)點(diǎn)確認(rèn)平臺(tái)自動(dòng)恢復(fù)后查詢服務(wù)沒有出現(xiàn)長時(shí)間中斷。第三步人為打入少量索引數(shù)據(jù)然后刪除一個(gè)節(jié)點(diǎn)再看數(shù)據(jù)是否能正常檢索以確認(rèn)數(shù)據(jù)確實(shí)持久化在遠(yuǎn)程存儲(chǔ)上。4.3 與 Spring Boot 集成時(shí)需要注意的幾個(gè)參數(shù)服務(wù)端部署好之后實(shí)際業(yè)務(wù)應(yīng)用接入時(shí)最常見的就是 Spring Boot 項(xiàng)目。我通常用spring-data-elasticsearch配合 REST High Level Client 或者新版的ElasticsearchClient。集成時(shí)我最推薦直接使用官方的 transport 客戶端或 REST 客戶端因?yàn)樗鼘?Spring Boot 生態(tài)兼容性好參數(shù)調(diào)整也方便。比較重要的配置項(xiàng)有這么幾個(gè)spring.elasticsearch.uris接入地址使用 Serverless 接入點(diǎn)時(shí)直接填為返回的 HTTPS URL。spring.elasticsearch.username/spring.elasticsearch.password認(rèn)證信息。spring.elasticsearch.connection-timeout和socket-timeout如果集群處在不同的地域網(wǎng)絡(luò)延遲偏高務(wù)必調(diào)大超時(shí)時(shí)間否則業(yè)務(wù)端很容易報(bào)ConnectionTimeOutException。集成時(shí)還要注意一個(gè)細(xì)節(jié)Serverless 環(huán)境通常會(huì)限制索引的自定義 mapping 不能隨意變更推薦把 mapping 和 setting 的模板提前定義好。如果直接在業(yè)務(wù)代碼里通過IndexRequest動(dòng)態(tài)建索引可能會(huì)出現(xiàn)字段類型和模板不一致的報(bào)錯(cuò)。我一般會(huì)在項(xiàng)目里放一份索引映射的初始化腳本部署新環(huán)境時(shí)先執(zhí)行一次避免奇奇怪怪的字段類型問題。5. 無狀態(tài)架構(gòu)下的讀寫流程與故障恢復(fù)邏輯5.1 寫入流程從協(xié)調(diào)層到遠(yuǎn)程存儲(chǔ)無狀態(tài)架構(gòu)下一次典型的寫入請求經(jīng)過的鏈路如下客戶端把寫入請求發(fā)到協(xié)調(diào)節(jié)點(diǎn)。協(xié)調(diào)節(jié)點(diǎn)對請求做解析、鑒權(quán)和路由找到合適的索引主分片所在的數(shù)據(jù)節(jié)點(diǎn)。數(shù)據(jù)節(jié)點(diǎn)接收請求后先寫 translog再寫入內(nèi)存 buffer。當(dāng) buffer 達(dá)到一定大小或時(shí)間達(dá)到刷新閾值觸發(fā) flush生成一個(gè)新的 segment。這個(gè) segment 緊接著被上傳到遠(yuǎn)程共享存儲(chǔ)。等遠(yuǎn)程存儲(chǔ)確認(rèn)接收成功寫入請求才會(huì)返回成功。這段鏈路里我最在意的是 translog 和 segment 的安全。translog 如果丟了內(nèi)存 buffer 里的數(shù)據(jù)就丟了segment 如果沒傳到遠(yuǎn)程存儲(chǔ)這個(gè)分片就等于沒有持久化成功。因此無狀態(tài)架構(gòu)對這兩個(gè)操作的同步要求比較嚴(yán)格通常會(huì)等遠(yuǎn)程存儲(chǔ)返回 ACK 之后再給客戶端發(fā)成功響應(yīng)。5.2 查詢流程緩存與遠(yuǎn)程存儲(chǔ)的取舍查詢請求的路徑和寫入略有不同??蛻舳苏埱蟮絽f(xié)調(diào)節(jié)點(diǎn)后協(xié)調(diào)節(jié)點(diǎn)把查詢廣播到所有相關(guān)分片的數(shù)據(jù)節(jié)點(diǎn)。每個(gè)數(shù)據(jù)節(jié)點(diǎn)先在本地緩存里查找相關(guān) segment本地沒有的再去遠(yuǎn)程存儲(chǔ)拉取。這里有一個(gè)取舍要注意如果本地緩存總是未命中幾次查詢之后緩存會(huì)逐漸暖起來后續(xù)延遲會(huì)下降但如果查詢范圍跨越大量 segment遠(yuǎn)程存儲(chǔ)的隨機(jī)讀會(huì)被放大。所以我在配置無狀態(tài)集群時(shí)會(huì)優(yōu)先保證頻繁查詢的索引足夠小并且設(shè)置合理的 segment 合并策略減少落到遠(yuǎn)程讀的次數(shù)。5.3 故障恢復(fù)演示從節(jié)點(diǎn)消失到業(yè)務(wù)無感這是我覺得無狀態(tài)架構(gòu)最優(yōu)秀的地方。假設(shè)某個(gè)數(shù)據(jù)節(jié)點(diǎn)因?yàn)橛布收贤蝗诲礄C(jī)傳統(tǒng)架構(gòu)此時(shí)要經(jīng)歷主分片重選舉、副本重分配、數(shù)據(jù)重新復(fù)制無狀態(tài)架構(gòu)里協(xié)調(diào)層會(huì)把這個(gè)節(jié)點(diǎn)標(biāo)記為異常平臺(tái)自動(dòng)啟動(dòng)一臺(tái)新數(shù)據(jù)節(jié)點(diǎn)。新節(jié)點(diǎn)啟動(dòng)時(shí)不需要從其它節(jié)點(diǎn)復(fù)制數(shù)據(jù)而是先從遠(yuǎn)程存儲(chǔ)獲取這個(gè)分片的元信息再用遠(yuǎn)程讀取的方式直接對外提供服務(wù)。整個(gè)過程客戶端感知到的可能就是個(gè)別請求的響應(yīng)時(shí)間略微變長但不會(huì)出現(xiàn)整個(gè)索引不可查詢的情況。我開始做遷移實(shí)驗(yàn)時(shí)做過一次破壞性測試在一個(gè)正常運(yùn)行的無狀態(tài)集群里直接殺掉所有數(shù)據(jù)節(jié)點(diǎn)結(jié)果因?yàn)橄M(fèi)端有連接重試機(jī)制節(jié)點(diǎn)恢復(fù)后數(shù)據(jù)依舊可查。這正是計(jì)算無狀態(tài)、存儲(chǔ)有狀態(tài)的價(jià)值所在。6. 常見問題與排查技巧實(shí)錄6.1 啟動(dòng)失敗heap 設(shè)置和端口占用我碰到最多的啟動(dòng)失敗場景本地調(diào)試時(shí)八成是 heap 設(shè)置得不合理或者端口被占用。如果你看到native thread creation failed之類的錯(cuò)誤通常是線程數(shù)或內(nèi)存超了。換個(gè)測試環(huán)境時(shí)先把jvm.options里的堆內(nèi)存調(diào)小一點(diǎn)比如 512MB等啟動(dòng)成功再慢慢往上加。端口占用也很常見9200 和 9300 被占用時(shí)啟動(dòng)日志會(huì)直接報(bào)Address already in use。Windows 下用netstat -ano | findstr 9200找出占用進(jìn)程手動(dòng)處理掉就行。還有一點(diǎn)容易忽略ES 默認(rèn)不允許以 root 身份在 Linux 下運(yùn)行如果你正在容器里跑記得用非 root 用戶運(yùn)行。Windows 下沒有這個(gè)限制但配置文件的權(quán)限要開放否則可能啟動(dòng)時(shí)報(bào)access denied。6.2 數(shù)據(jù)節(jié)點(diǎn)無法連接遠(yuǎn)程存儲(chǔ)無狀態(tài)架構(gòu)里的一個(gè)高發(fā)問題數(shù)據(jù)節(jié)點(diǎn)配置了遠(yuǎn)程存儲(chǔ)但啟動(dòng)后大量刷報(bào)錯(cuò)檢查發(fā)現(xiàn)節(jié)點(diǎn)根本無法訪問對象存儲(chǔ)的 endpoint。排查思路我總結(jié)成一個(gè)順序確認(rèn)遠(yuǎn)程存儲(chǔ) endpoint 可以被數(shù)據(jù)節(jié)點(diǎn)的網(wǎng)絡(luò)解析并且可以連通。在容器環(huán)境里做ping或curl測試。確認(rèn)訪問憑證配置正確。密鑰信息如果放在環(huán)境變量里要檢查數(shù)據(jù)節(jié)點(diǎn)的環(huán)境變量是否真的注入進(jìn)去了。確認(rèn)訪問策略允許數(shù)據(jù)節(jié)點(diǎn)所在網(wǎng)絡(luò)的讀寫操作。很多云環(huán)境要通過 VPC 或安全組規(guī)則放行相應(yīng)端口和 IP。如果以上都沒問題但節(jié)點(diǎn)還是報(bào)錯(cuò)檢查一下本地時(shí)鐘和遠(yuǎn)程服務(wù)器時(shí)鐘的偏差時(shí)間偏差超過 5 分鐘往往會(huì)導(dǎo)致請求簽名校驗(yàn)失敗。6.3 查詢結(jié)果不全segment 與緩存機(jī)制有同事遇到過一個(gè)困惑數(shù)據(jù)寫入幾秒后查詢偶爾會(huì)漏掉一部分新數(shù)據(jù)。我第一反應(yīng)是查詢默認(rèn)的_refresh間隔。ES 默認(rèn) 1 秒刷新剛寫入的數(shù)據(jù)不會(huì)立刻對查詢可見這是正?,F(xiàn)象。如果你需要更強(qiáng)的實(shí)時(shí)性可以在寫入后強(qiáng)制refreshtrue但代價(jià)是頻繁刷新會(huì)生成大量小 segment影響后續(xù)查詢性能不建議在批量寫入場景使用。另一個(gè)點(diǎn)比較容易忽略無狀態(tài)架構(gòu)的本地緩存替換機(jī)制。當(dāng)本地緩存打滿節(jié)點(diǎn)會(huì)按策略淘汰部分 segment。如果查詢落在被淘汰的 segment 上節(jié)點(diǎn)需要重新從遠(yuǎn)程存儲(chǔ)加載這會(huì)表現(xiàn)為偶發(fā)的慢查詢。所以查詢耗時(shí)的曲線不應(yīng)該只看平均值一定要觀察 p99 分位值出現(xiàn)明顯抖動(dòng)時(shí)優(yōu)先排查緩存命中率。6.4 我的排查工具與日常體檢清單很多剛?cè)胄械呐笥严矚g遇到問題就上論壇搜索我的習(xí)慣是先自己盯一套指標(biāo)集群健康狀態(tài)GET _cluster/health看status是不是 green。節(jié)點(diǎn)熱點(diǎn)GET _nodes/hot_threads看數(shù)據(jù)節(jié)點(diǎn)的 CPU 熱點(diǎn)。分片分配GET _cat/shards看有沒有分片長時(shí)間處于 UNASSIGNED。緩存命中GET _nodes/stats重點(diǎn)看query_cache和request_cache的命中率。存儲(chǔ)寫入延遲針對遠(yuǎn)程存儲(chǔ)做一次讀寫壓測確認(rèn)延遲是否在合理范圍內(nèi)。日常我是按周做一次體檢查一遍集群健康、看看數(shù)據(jù)增長和分片數(shù)量、檢查有沒有頻繁刷新的索引。這套習(xí)慣看起來簡單但能幫我提前攔住不少問題。最后說點(diǎn)個(gè)人體會(huì)從傳統(tǒng)集群遷到無狀態(tài)架構(gòu)我最大的收獲不是省了多少運(yùn)維時(shí)間而是想清楚了一個(gè)問題在分布式系統(tǒng)里狀態(tài)放對位置比什么都重要。把數(shù)據(jù)狀態(tài)集中到遠(yuǎn)程共享存儲(chǔ)計(jì)算節(jié)點(diǎn)才能真正變成可丟棄的廉價(jià)資源。這個(gè)思路不只適用于 Elasticsearch很多大數(shù)據(jù)組件都在往這個(gè)方向走。如果你正準(zhǔn)備上手我個(gè)人建議先從一個(gè)小規(guī)模的非核心業(yè)務(wù)開始。先在本地按單節(jié)點(diǎn)模式跑通再部署到 Serverless 環(huán)境然后逐步加數(shù)據(jù)量、加查詢壓力。不要一上來就把核心業(yè)務(wù)遷過去無狀態(tài)架構(gòu)雖然穩(wěn)定但多了一層網(wǎng)絡(luò)依賴你得先摸清自己業(yè)務(wù)對延遲和成本的邊界在哪里。Windows 上啟動(dòng) Elasticsearch 的坑、Serverless 部署的配置、Spring Boot 集成的注意點(diǎn)這些具體操作我在前面已經(jīng)寫得很細(xì)了。你照著走一遍大概率能順利跑通。真碰到奇怪的問題再回頭看看第 6 部分的排查清單基本能定位個(gè)八九不離十。