日均5億查詢量,京東到家訂單中心的ES架構(gòu)演進
京東到家訂單中心系統(tǒng)業(yè)務(wù)中,無論是外部商家的訂單生產(chǎn),或是內(nèi)部上下游系統(tǒng)的依賴,訂單查詢的調(diào)用量都非常大,造成了訂單數(shù)據(jù)讀多寫少的情況。
我們把訂單數(shù)據(jù)存儲在 MySQL 中,但顯然只通過 DB 來支撐大量的查詢是不可取的。
同時對于一些復雜的查詢,MySQL 支持得不夠友好,所以訂單中心系統(tǒng)使用了 Elasticsearch 來承載訂單查詢的主要壓力。
Elasticsearch 作為一款功能強大的分布式搜索引擎,支持近實時的存儲、搜索數(shù)據(jù),在京東到家訂單系統(tǒng)中發(fā)揮著巨大作用,目前訂單中心 ES 集群存儲數(shù)據(jù)量達到 10 億個文檔,日均查詢量達到 5 億。
隨著京東到家近幾年業(yè)務(wù)的快速發(fā)展,訂單中心 ES 架設(shè)方案也不斷演進,發(fā)展至今 ES 集群架設(shè)是一套實時互備方案,很好地保障了 ES 集群讀寫的穩(wěn)定性,下面就給大家介紹一下這個歷程以及發(fā)展過程中遇到的一些坑。
ES 集群架設(shè)演進歷程
初始階段
訂單中心 ES 初始階段如一張白紙,架設(shè)方案基本沒有,很多配置都是保持集群默認配置。整個集群部署在集團的彈性云上,ES 集群的節(jié)點以及機器部署都比較混亂。
同時按照集群維度來看,一個 ES 集群會有單點問題,顯然對于訂單中心業(yè)務(wù)來說也是不被允許的。
集群隔離階段
和很多業(yè)務(wù)一樣,ES 集群采用的混布的方式。但由于訂單中心 ES 存儲的是線上訂單數(shù)據(jù),偶爾會發(fā)生混布集群搶占系統(tǒng)大量資源,導致整個訂單中心 ES 服務(wù)異常。
顯然任何影響到訂單查詢穩(wěn)定性的情況都是無法容忍的,所以針對這個情況,先是對訂單中心 ES 所在的彈性云,遷出那些系統(tǒng)資源搶占很高的集群節(jié)點,ES 集群狀況稍有好轉(zhuǎn)。
但隨著集群數(shù)據(jù)不斷增加,彈性云配置已經(jīng)不太能滿足 ES 集群,且為了完全的物理隔離,最終干脆將訂單中心 ES 集群部署到高配置的物理機上,ES 集群性能又得到提升。
節(jié)點副本調(diào)優(yōu)階段
ES 的性能跟硬件資源有很大關(guān)系,當 ES 集群單獨部署到物理機器上時,集群內(nèi)部的節(jié)點并不是獨占整臺物理機資源,在集群運行的時候同一物理機上的節(jié)點仍會出現(xiàn)資源搶占的問題。
所以在這種情況下,為了讓 ES 單個節(jié)點能夠使用最大程度的機器資源,采用每個 ES 節(jié)點部署在單獨一臺物理機上的方式。
但緊接著,問題又來了,如果單個節(jié)點出現(xiàn)瓶頸了呢?我們應(yīng)該怎么再優(yōu)化呢?
ES 查詢的原理,當請求打到某號分片的時候,如果沒有指定分片類型(Preference 參數(shù))查詢,請求會負載到對應(yīng)分片號的各個節(jié)點上。
而集群默認副本配置是一主一副,針對此情況,我們想到了擴容副本的方式,由默認的一主一副變?yōu)橐恢鞫保瑫r增加相應(yīng)物理機。
訂單中心 ES 集群架設(shè)示意圖
如圖,整個架設(shè)方式通過 VIP 來負載均衡外部請求:
- 第一層 Gateway 節(jié)點實質(zhì)為 ES 中 Client Node,相當于一個智能負載均衡器,充當著分發(fā)請求的角色。
- 第二層為 Data Node,負責存儲數(shù)據(jù)以及執(zhí)行數(shù)據(jù)的相關(guān)操作。
整個集群有一套主分片,二套副分片(一主二副),從網(wǎng)關(guān)節(jié)點轉(zhuǎn)發(fā)過來的請求,會在打到數(shù)據(jù)節(jié)點之前通過輪詢的方式進行均衡。
集群增加一套副本并擴容機器的方式,增加了集群吞吐量,從而提升了整個集群查詢性能。
下圖為訂單中心 ES 集群各階段性能示意圖,直觀地展示了各階段優(yōu)化后 ES 集群性能的顯著提升:
當然分片數(shù)量和分片副本數(shù)量并不是越多越好,在此階段,我們對選擇適當?shù)姆制瑪?shù)量做了進一步探索。
分片數(shù)可以理解為 MySQL 中的分庫分表,而當前訂單中心 ES 查詢主要分為兩類:
- 單 ID 查詢
- 分頁查詢
分片數(shù)越大,集群橫向擴容規(guī)模也更大,根據(jù)分片路由的單 ID 查詢吞吐量也能大大提升,但聚合的分頁查詢性能則將降低。
分片數(shù)越小,集群橫向擴容規(guī)模也更小,單 ID 的查詢性能也會下降,但分頁查詢的性能將會提升。
所以如何均衡分片數(shù)量和現(xiàn)有查詢業(yè)務(wù),我們做了很多次調(diào)整壓測,最終選擇了集群性能較好的分片數(shù)。
主從集群調(diào)整階段
到此,訂單中心的 ES 集群已經(jīng)初具規(guī)模,但由于訂單中心業(yè)務(wù)時效性要求高,對 ES 查詢穩(wěn)定性要求也高,如果集群中有節(jié)點發(fā)生異常,查詢服務(wù)會受到影響,從而影響到整個訂單生產(chǎn)流程。
很明顯這種異常情況是致命的,所以為了應(yīng)對這種情況,我們初步設(shè)想是增加一個備用集群,當主集群發(fā)生異常時,可以實時的將查詢流量降級到備用集群。
那備用集群應(yīng)該怎么來搭?主備之間數(shù)據(jù)如何同步?備用集群應(yīng)該存儲什么樣的數(shù)據(jù)?
考慮到 ES 集群暫時沒有很好的主備方案,同時為了更好地控制 ES 數(shù)據(jù)寫入,我們采用業(yè)務(wù)雙寫的方式來搭設(shè)主備集群。
每次業(yè)務(wù)操作需要寫入 ES 數(shù)據(jù)時,同步寫入主集群數(shù)據(jù),然后異步寫入備集群數(shù)據(jù)。
同時由于大部分 ES 查詢的流量都來源于近幾天的訂單,且訂單中心數(shù)據(jù)庫數(shù)據(jù)已有一套歸檔機制,將指定天數(shù)之前已經(jīng)關(guān)閉的訂單轉(zhuǎn)移到歷史訂單庫。
所以歸檔機制中增加刪除備集群文檔的邏輯,讓新搭建的備集群存儲的訂單數(shù)據(jù)與訂單中心線上數(shù)據(jù)庫中的數(shù)據(jù)量保持一致。
同時使用 ZK 在查詢服務(wù)中做了流量控制開關(guān),保證查詢流量能夠?qū)崟r降級到備集群。在此,訂單中心主從集群完成,ES 查詢服務(wù)穩(wěn)定性大大提升。

現(xiàn)今:實時互備雙集群階段
期間由于主集群 ES 版本是較低的 1.7,而現(xiàn)今 ES 穩(wěn)定版本都已經(jīng)迭代到 6.x,新版本的 ES 不僅性能方面優(yōu)化很大,更提供了一些新的好用的功能。
所以我們對主集群進行了一次版本升級,直接從原來的 1.7 升級到 6.x 版本。
集群升級的過程繁瑣而漫長,不但需要保證線上業(yè)務(wù)無任何影響,平滑無感知升級。
同時由于 ES 集群暫不支持從 1.7 到 6.x 跨越多個版本的數(shù)據(jù)遷移,所以需要通過重建索引的方式來升級主集群,具體升級過程就不在此贅述了。
主集群升級的時候必不可免地會發(fā)生不可用的情況,但對于訂單中心 ES 查詢服務(wù),這種情況是不允許的。
所以在升級的階段中,備集群暫時頂上充當主集群,來支撐所有的線上 ES 查詢,保證升級過程不影響正常線上服務(wù)。
同時針對于線上業(yè)務(wù),我們對兩個集群做了重新的規(guī)劃定義,承擔的線上查詢流量也做了重新的劃分。
備集群存儲的是線上近幾天的熱點數(shù)據(jù),數(shù)據(jù)規(guī)模遠小于主集群,大約是主集群文檔數(shù)的十分之一。集群數(shù)據(jù)量小,在相同的集群部署規(guī)模下,備集群的性能要優(yōu)于主集群。
然而在線上真實場景中,線上大部分查詢流量也來源于熱點數(shù)據(jù),所以用備集群來承載這些熱點數(shù)據(jù)的查詢,而備集群也慢慢演變成一個熱數(shù)據(jù)集群。
之前的主集群存儲的是全量數(shù)據(jù),用該集群來支撐剩余較小部分的查詢流量,這部分查詢主要是需要搜索全量訂單的特殊場景查詢以及訂單中心系統(tǒng)內(nèi)部查詢等,而主集群也慢慢演變成一個冷數(shù)據(jù)集群。
同時備集群增加一鍵降級到主集群的功能,兩個集群地位同等重要,但都可以各自降級到另一個集群。
雙寫策略也優(yōu)化為:假設(shè)有 AB 集群,正常同步方式寫主(A 集群),異步方式寫備(B 集群)。A 集群發(fā)生異常時,同步寫 B 集群(主),異步寫 A 集群(備)。
ES 訂單數(shù)據(jù)的同步方案
MySQL 數(shù)據(jù)同步到 ES 中,大致總結(jié)可以分為兩種方案。
方案 1
監(jiān)聽 MySQL 的 Binlog,分析 Binlog 將數(shù)據(jù)同步到 ES 集群中:
優(yōu)點:業(yè)務(wù)與 ES 數(shù)據(jù)耦合度低,業(yè)務(wù)邏輯中不需要關(guān)心 ES 數(shù)據(jù)的寫入。
缺點:Binlog 模式只能使用 ROW 模式,且引入了新的同步服務(wù),增加了開發(fā)量以及維護成本,也增大了 ES 同步的風險。
方案 2
直接通過 ES API 將數(shù)據(jù)寫入到 ES 集群中:
- 優(yōu)點:簡潔明了,能夠靈活的控制數(shù)據(jù)的寫入。
- 缺點:與業(yè)務(wù)耦合嚴重,強依賴于業(yè)務(wù)系統(tǒng)的寫入方式。
考慮到訂單系統(tǒng) ES 服務(wù)的業(yè)務(wù)特殊性,對于訂單數(shù)據(jù)的實時性較高,顯然監(jiān)聽 Binlog 的方式相當于異步同步,有可能會產(chǎn)生較大的延時性。且方案 1 實質(zhì)上跟方案 2 類似,但又引入了新的系統(tǒng),維護成本也增高。
所以訂單中心 ES 采用了直接通過 ES API 寫入訂單數(shù)據(jù)的方式,該方式簡潔靈活,能夠很好的滿足訂單中心數(shù)據(jù)同步到 ES 的需求。
由于 ES 訂單數(shù)據(jù)的同步采用的是在業(yè)務(wù)中寫入的方式,當新建或更新文檔發(fā)生異常時,如果重試勢必會影響業(yè)務(wù)正常操作的響應(yīng)時間。
所以每次業(yè)務(wù)操作只更新一次 ES,如果發(fā)生錯誤或者異常,在數(shù)據(jù)庫中插入一條補救任務(wù),有 Worker 任務(wù)會實時地掃這些數(shù)據(jù),以數(shù)據(jù)庫訂單數(shù)據(jù)為基準來再次更新 ES 數(shù)據(jù)。
通過此種補償機制,來保證 ES 數(shù)據(jù)與數(shù)據(jù)庫訂單數(shù)據(jù)的最終一致性。
遇到的一些坑
實時性要求高的查詢走 DB
對于 ES 的寫入機制有了解的同學可能會知道,新增的文檔會被收集到 Indexing Buffer,然后寫入到文件系統(tǒng)緩存中,到了文件系統(tǒng)緩存中就可以像其他的文件一樣被索引到。
然而默認情況文檔從 Indexing Buffer 到文件系統(tǒng)緩存(即 Refresh 操作)是每秒分片自動刷新。
所以這就是我們說 ES 是近實時搜索而非實時的原因:文檔的變化并不是立即對搜索可見,但會在一秒之內(nèi)變?yōu)榭梢姟?/p>
當前訂單系統(tǒng) ES 采用的是默認 Refresh 配置,故對于那些訂單數(shù)據(jù)實時性比較高的業(yè)務(wù),直接走數(shù)據(jù)庫查詢,保證數(shù)據(jù)的準確性。
避免深分頁查詢
ES 集群的分頁查詢支持 from 和 size 參數(shù),查詢的時候,每個分片必須構(gòu)造一個長度為 from+size 的優(yōu)先隊列,然后回傳到網(wǎng)關(guān)節(jié)點,網(wǎng)關(guān)節(jié)點再對這些優(yōu)先隊列進行排序找到正確的 size 個文檔。
假設(shè)在一個有 6 個主分片的索引中,from 為 10000,size 為 10,每個分片必須產(chǎn)生 10010 個結(jié)果,在網(wǎng)關(guān)節(jié)點中匯聚合并 60060 個結(jié)果,最終找到符合要求的 10 個文檔。
由此可見,當 from 足夠大的時候,就算不發(fā)生 OOM,也會影響到 CPU 和帶寬等,從而影響到整個集群的性能。所以應(yīng)該避免深分頁查詢,盡量不去使用。
FieldData 與 Doc Values
FieldData
線上查詢出現(xiàn)偶爾超時的情況,通過調(diào)試查詢語句,定位到是跟排序有關(guān)系。
排序在 es1.x 版本使用的是 FieldData 結(jié)構(gòu),F(xiàn)ieldData 占用的是 JVM Heap 內(nèi)存,JVM 內(nèi)存是有限,對于 FieldData Cache 會設(shè)定一個閾值。
如果空間不足時,使用最久未使用(LRU)算法移除 FieldData,同時加載新的 FieldData Cache,加載的過程需要消耗系統(tǒng)資源,且耗時很大。
所以導致這個查詢的響應(yīng)時間暴漲,甚至影響整個集群的性能。針對這種問題,解決方式是采用 Doc Values。
Doc Values
Doc Values 是一種列式的數(shù)據(jù)存儲結(jié)構(gòu),跟 FieldData 很類似,但其存儲位置是在 Lucene 文件中,即不會占用 JVM Heap。
隨著 ES 版本的迭代,Doc Values 比 FieldData 更加穩(wěn)定,Doc Values 在 2.x 起為默認設(shè)置。
總結(jié)
架構(gòu)的快速迭代源于業(yè)務(wù)的快速發(fā)展,正是由于近幾年到家業(yè)務(wù)的高速發(fā)展,訂單中心的架構(gòu)也不斷優(yōu)化升級。
而架構(gòu)方案沒有最好的,只有最合適的,相信再過幾年,訂單中心的架構(gòu)又將是另一個面貌,但吞吐量更大,性能更好,穩(wěn)定性更強,將是訂單中心系統(tǒng)永遠的追求。