滴滴 Elasticsearch 多叢集架構實踐

java06051515發表於2019-01-10

出品 | 滴滴技術
作者 |魏子珺

圖片描述

Elasticsearch 是基於 Lucene 實現的分散式搜尋引擎,提供了海量資料實時檢索和分析能力。Elastic 公司開源的一系列產品組成的Elastic Stack,可以為日誌服務、搜尋引擎、系統監控等提供簡單、易用的解決方案。

滴滴 Elasticsearch 簡介
滴滴2016年初開始構建Elasticsearch平臺,如今已經發展到超過3500+Elasticsearch例項,超過5PB的資料儲存,峰值寫入tps超過了2000w/s的超大規模。

Elasticsearch在滴滴有著非常豐富的使用場景,例如線上核心的叫車地圖搜尋,客服、運營的多維度查詢,滴滴日誌服務等近千個平臺使用者。

超大的規模和豐富的場景給滴滴Elasticsearch平臺帶來了極大的挑戰,我們在這期間積累了豐富經驗,也取得了一些成果。本文給大家分享下滴滴在Elasticsearch多叢集架構的實踐。

單叢集架構瓶頸
介紹單叢集架構瓶頸前,先來看下滴滴Elasticsearch單叢集的架構。

滴滴Elasticsearch單叢集架構

圖片描述

滴滴在單叢集架構的時候,寫入和查詢就已經通過Sink服務和Gateway服務管控起來。

| Sink服務
滴滴幾乎所有寫入Elasticsearch的資料都是經由kafka消費入到Elasticsearch。kafka的資料包括業務log資料、mysql binlog資料和業務自主上報的資料,Sink服務將這些資料實時消費入到Elasticsearch。

最初設計Sink服務是想對寫入Elasticsearch叢集進行管控,保護Elasticsearch叢集,防止海量的資料寫入拖垮Elasticsearch,之後我們也一直沿用了Sink服務,並將該服務從Elasticsearch平臺分離出去,成立滴滴Sink資料投遞平臺,可以從kafka或者MQ實時同步資料到Elasticsearch、HDFS、Ceph等多個儲存服務。

有了多叢集架構後,Elasticsearch平臺可以消費一份MQ資料寫入多個Elasticsearch叢集,做到叢集級別的容災,還能通過MQ回溯資料進行故障恢復。

| Gateway服務
所有業務的查詢都是經過Gateway服務,Gateway服務實現了Elasticsearch的http restful和tcp協議,業務方可以通過Elasticsearch各語言版本的sdk直接訪問Gateway服務,Gateway服務還實現了SQL介面,業務方可以直接使用SQL訪問Elasticsearch平臺。

Gateway服務最初提供了應用許可權的管控,訪問記錄,限流、降級等基本能力,後面隨著平臺演進,Gateway服務還提供了索引儲存分離、DSL級別的限流、多叢集災備等能力。

| Admin服務
整個Elasticsearch平臺由Admin服務統一管控起來。Admin服務提供了索引的生命週期管理,索引容量自動規劃,索引健康分,叢集監控等豐富的平臺能力,以及為Sink、Gateway服務提供索引、許可權等後設資料資訊。

Elasticsearch單叢集瓶頸
隨著滴滴Elasticsearch平臺規模的快速發展,Elasticsearch叢集越來越大,最大的時候,是由幾百臺物理機組成叢集,當時叢集共 3000+ 的索引,超過了 50000 個 shard,叢集總容量達到了PB級別。超大的Elasticsearch叢集面臨了很大的穩定性風險,這些風險主要來自於以下三個方面:

  • Elasticsearch架構瓶頸
  • 索引資源共享風險
  • 業務場景差異大

Elasticsearch架構瓶頸
Elasticsearch架構在叢集變大到一定的規模會遇到瓶頸,瓶頸主要跟Elasticsearch任務處理模型有關。

Elasticsearch看起來是p2p架構,但實際上,仍然是中心化的分散式架構。整個叢集只有一個active master。master負責整個叢集的後設資料管理。叢集的所有後設資料儲存在ClusterState物件中,主要包括全域性的配置資訊、索引資訊和節點資訊。只要後設資料發生修改,都得由master完成。

Elasticsearchmaster的任務處理是單執行緒完成的,每次處理任務,涉及到ClusterState的改動,都會將最新的ClusterState物件publish給叢集的全部節點,並阻塞等待全部節點接受到變更訊息,處理完變更任務後,才完成本次任務。

這樣的架構模型導致在叢集規模變大的時候出現很嚴重的穩定性風險。

  • 如果有節點假死,比如jvm記憶體被打滿,程式還存活著,響應master任務時間會很長,影響單個任務的完成時間。
  • 有大量恢復任務的時候,由於master是單執行緒處理的,所有任務需要排隊處理,產生大量的pending_tasks。恢復時間變得很長。
  • Elasticsearch的任務分了優先順序,例如put-mapping任務優先順序低於建立、恢復索引,如果一些業務上低優先順序索引在恢復,正常索引有新欄位寫入時會被阻塞。
  • master任務處理模型,在任務執行完成後,會回撥大量listener處理後設資料變更。其中有些回撥邏輯在索引、shard膨脹後,會出現處理緩慢的問題,當shard膨脹到5-6w時,一些任務處理需要8-9s的時間,嚴重影響了叢集的恢復能力。

針對這些問題,Elasticsearch也在不斷優化,針對相同型別的任務,比如put-mapping任務,master會一次性處理所有堆積在佇列裡的相同任務。ClusterState物件只傳遞diff內容,優化回撥listener模組的處理耗時環節等等。

但是由於整個叢集的任務都集中在一個master的一個執行緒中處理,線上程中需要同步後設資料變更給叢集的每個節點,並阻塞等待全部節點同步完成。這個模型在叢集規模不斷膨脹時,穩定性會不斷下降。

| 索引資源共享風險
Elasticsearch索引是由多個shard組成,master會動態給這些shard分配節點資源。不同的索引會存在資源混部的情況。
圖片描述

Elasticsearch通過Shard Allocation Awareness的設計,可以將叢集的節點按集合劃分成不同的rack。在分配索引時可以指定rack列表,這樣索引就只會分配在指定rack對應的節點列表中,從而做到物理資源的隔離。

但是實際使用中,很多容量小的索引由於佔用資源有限,會混部在一些節點中。這種情況下,會因為個別索引的查詢、寫入量飆升,而影響到其他索引的穩定性。如果出現了節點故障,就會影響到整個叢集的穩定性。

整個叢集master、clientnode資源是共享的,master風險前面已經單獨提及,clientnode共享帶來的gc、抖動、異常問題都會影響到叢集內的全部索引。

| 業務場景差異大
Elasticsearch適用的業務場景差異特別大。

  • 針對線上核心的入口搜尋,一般按城市劃分索引後,索引容量不大,資料沒有實時寫入或者實時寫入tps很小,比如地圖poi資料採用離線更新的方式,外賣商家、菜品寫入量也很小。但是查詢的qps很高,查詢對rt的平均時間和抖動情況要求很高。
  • 針對日誌檢索的的場景,實時寫入量特別大,有些索引甚至超過了100w/s的tps,該場景對吞吐量要求很高,但對查詢qps和查詢rt要求不高。
  • 針對binlog資料的檢索,寫入量相比日誌會小很多,但是對查詢的複雜度、qps和rt有一定的要求。
  • 針對監控、分析類的場景,聚合查詢需求會比較多,對Elasticsearch記憶體壓力較大,容易引起節點的抖動和gc。

這些場景各異,穩定性、效能要求各不相同的場景,一個Elasticsearch叢集即使使用各種優化手段,很難全部滿足需求,最好的方式還是按業務場景劃分Elasticsearch叢集。

多叢集挑戰
正是單叢集面臨了非常大的穩定性風險,我們開始規劃多叢集的架構。我們在設計多叢集方案的時候,期望對業務方是零感知的。

寫入還是經過kafka,Sink服務可以將不同topic的資料入到不同的Elasticsearch叢集。查詢繼續通過Gateway服務,而且業務方仍然像之前一樣傳遞索引名稱,而無需感知到平臺內部的索引分佈。所有的索引在不同叢集的分佈細節,均由Gateway服務遮蔽。

整個改造最大的挑戰在於查詢方式的相容。Elasticsearch查詢索引的方式非常靈活,可以支援*號作為萬用字元匹配。這樣一個索引query可能查詢的是多個索引,比如有如下3個索引:

  • index_a
  • index_b
  • index_c

使用index*查詢的時候,可以同時查詢到index_a、index_b、index_c三個索引。 Elasticsearch這種實現方式非常簡單,由於一次query最終查詢的是多個shard的資料,所以無論對於具體的索引,還是模糊的索引,都是先根據索引名稱得到shard列表,再將多個shard的query結果merge到一起返回。

這樣的使用方式,對於多叢集方案就會遇到問題,比如index_a在A叢集,index_b在B叢集、index_c在C叢集,對於index*的query,就無法在一個叢集上完成。

tribenode介紹

經過調研,我們發現Elasticsearchtribenode特性可以很好的滿足多叢集查詢的特性。tribenode的實現非常巧妙。org.elasticsearch.tribe包下只有三個檔案,核心類是TribeService。tribenode的核心原理就是merge每個叢集的ClusterState物件成一個公共的ClusterState物件,ClusterState包含了索引、shard和節點資料分佈表。而Elasticsearch的工作邏輯都是基於ClusterState後設資料驅動的,所以對外看起來就是一個包含全部索引的的clientnode。

圖片描述

tribenode通過配置多個Elasticsearch叢集地址,然後以clientnode角色分別連線每個叢集,每個叢集看起來會多了一個clientnode。tribenode通過該clientnode角色獲取到叢集的ClusterState資訊,並繫結listener監聽ClusterState變化。tribenode將獲取的所有叢集的ClusterState資訊merge到一起,形成一個對外部訪問使用的ClusterState物件,對外提供服務。tribenode除了註冊listener和merge ClusterState,其他的所有邏輯都是複用了clientnode的程式碼。

可以看到tribenode的優點:

  • 能夠滿足多叢集訪問的需求,對外使用是透明的。
  • 實現的簡單、優雅,可靠性有保證。

同時tribenode有些不足的地方:

  • tribenode必須以clientnode加入到每個Elasticsearch叢集,master的變更任務必須等待tribenode的回應才能繼續,可能影響到原叢集的穩定性。
  • tribenode不會持久化ClusterState物件,重啟時需要從每個Elasticsearch叢集獲取後設資料。而在獲取後設資料期間,tribenode就已經能夠提供訪問,會導致查詢到還在初始化中的叢集索引訪問失敗。
  • tribenode連線的叢集多了,初始化會變得很慢。針對該缺陷,我們平臺在重啟某個tribenode叢集時,將Gateway訪問該叢集的全部流量切到備份tribenode叢集解決。

如果多個叢集有相同的索引名稱,tribenode只能設定一種perfer規則:隨機、丟棄、prefer指定叢集。這可能帶來查到不符合預期的異常。滴滴Elasticsearch平臺通過統一管控索引,避免了同一個索引名稱出現在tribenode連線的多個叢集中。

正是tribenode有了這些瑕疵,Elasticsearch在高版本引入了Cross ClusterSearch的設計,Cross Cluster不會以節點的形式連線到其他叢集,只是將請求代理。目前我們還在評估Cross Cluster的方案,這裡不展開介紹。

多叢集架構拓撲
最終改造後,我們的叢集架構拓撲如下:

圖片描述

按照不同的應用場景,平臺將Elasticsearch叢集劃分成四種型別,Log叢集、Binlog叢集、文件資料叢集、獨立叢集。公共叢集一般最多100臺datanode為基準組成一個叢集。我們利用滴滴雲實現了叢集的自動化部署和彈性擴縮容,可以很方便的水平擴充套件叢集。

Elasticsearch叢集前面是多組tribenode叢集,主要是為了解決tribenode的穩定性問題。

Gateway會同時連線tribenode叢集和Elasticsearch叢集,根據應用訪問的索引列表,配置應用訪問的叢集名稱,Gateway根據叢集名稱,將請求代理到指定叢集訪問,如果訪問的是tribenode叢集,則該應用可以訪問到多個叢集的索引。

Admin服務則管控了所有的Elasticsearch叢集,以及索引和叢集的對應關係。一系列功能都針對多叢集做了改造。

Sink服務已經從Elasticsearch平臺分離出去,成立DSink資料投遞平臺,DSink Manager負責管理DSink節點,DSink Manager從Elasticsearch Admin服務獲取索引的後設資料資訊,下發給對應的DSink節點。

多叢集架構實踐總結

| 多叢集架構收益
Elasticsearch多叢集架構改造給Elasticsearch平臺帶來了如下收益:

  • Elasticsearch平臺的隔離性可以從物理節點級別上升到Elasticsearch叢集級別。對於核心的線上應用,可以使用獨立的Elasticsearch叢集支援。
  • 不同型別的資料按叢集劃分,避免相互影響,減小了故障的影響面,對平臺穩定性帶來極大的提升。
  • Elasticsearch平臺的擴充套件能力進一步提升,通過新增叢集可以很好的做到水平擴充套件。
  • 多叢集架構最終做到了對業務方無感知,業務看起來,Elasticsearch平臺就像一個無限大的Elasticsearch叢集,而無需感知索引真實的叢集分佈。

| 多叢集架構實踐經驗
滴滴Elasticsearch平臺多叢集的架構已經演進了一年半時間,這期間也遇到一些多叢集架構帶來的挑戰。
tribenode穩定性挑戰:

  • 隨著叢集數量越來越多,前面提到的tribenode不足越來越明顯,比如初始化的時間越來越長等等。我們採取的應對策略是部署多組tribenode叢集,有幾組連線全量的叢集,互為災備,有幾組只連線核心的一些叢集,用作更為重要的跨叢集訪問場景。
  • tribenode的ClusterState後設資料包含了太多的索引和shard,Elasticsearch的search邏輯在有些case處理下容易出現耗時過長的情況。Elasticsearch在client接收到search請求時,是在netty的io執行緒中完成請求轉發給每個shard的,低版本的Elasticsearch還沒有限制一次query的shard數量,在一些複雜的模糊索引匹配shard的邏輯中,以及給每個shard傳送query請求時,會出現較高的耗時,可能有超過1-2s的case,這會影響到該netty worker上的其他的請求,造成部分響應飆高的情況。我們優化了tribenode search流程中一些索引、shard膨脹之後的耗時邏輯,解決了該問題。

多叢集配置、版本統一的挑戰:

  • 在只有一個叢集的時候,平臺只用維護一份叢集的配置和版本。當叢集數量增多後,不同叢集間的_cluster
    settings資訊會出現部分差異,這些差異,可能會導致叢集間的負載不均,恢復速度過快或者過慢等問題,每個叢集還有一份基礎的索引模板配置,這裡面也出現了部分差異。這個問題目前我們還在解決中,我們計劃將Admin服務分離成索引管理服務和叢集管理服務,叢集管理會專注於叢集版本、配置、部署、擴容、監控等方面對Elasticsearch叢集進行更全面的管控。
  • 我們做的一些Elasticsearch原始碼優化,會先後在部分叢集上線,這樣導致了叢集間的版本混亂的問題。我們的解決方案是在Elasticsearch和Lucene內增加內部的版本號,通過公司內部的釋出系統,釋出Elasticsearch的更新,後續叢集管理服務會將叢集的版本管理起來。

多叢集間容量均衡的挑戰:

  • 我們主要從跨叢集索引遷移和容量規劃解決叢集間容量均衡的挑戰,在單Elasticsearch叢集的時候,資料遷移可以依賴Elasticsearch的rebalance能力完成。在使用多叢集架構後,平臺內部的Elasticsearch叢集會出現資源分配不均的問題,例如有些索引容量增長的很快,導致所在叢集的資源緊張,有些索引資料減少,不需要佔用太多資源,導致叢集資源空閒。於是產生了索引跨叢集遷移的需求。針對這個需求,我們通過給索引新增版本號,解決了索引跨叢集遷移問題。之後我們有文章會詳細的介紹該方案。
  • 滴滴Elasticsearch平臺實現了索引容量的自動規劃,解決了叢集間的容量均衡。Elasticsearch平臺可以動態的規劃索引的容量。當一個叢集容量規劃不足時,平臺可以動態的遷移一部分索引到空閒的叢集中。新的索引接入需求會優先接入在空閒的叢集資源中。滴滴Elasticsearch平臺是如何實現索引容量的自動規劃,也請期待後續的分享。

總結
滴滴的多叢集架構,最初是為了解決Elasticsearch單叢集架構的瓶頸。為了支援多叢集架構,後面的很多元件都需要考慮連線多個叢集的場景,給平臺架構帶來了一定的複雜性。但是多Elasticsearch叢集帶來的穩定性和隔離性的提升,它所帶來的收益遠遠大於架構的複雜性。改造成多叢集架構後,我們扛住了Elasticsearch平臺規模爆炸式增長,Elasticsearch平臺的規模翻了5倍多,多叢集架構很好的支撐了業務的快速發展。

相關文章