Elasticsearch讀寫中間件的設計
- 2019 年 11 月 11 日
- 筆記
之前負責搜索系統的時候花了些時間在搜索中間件上,沉澱了一下拿出來跟大家分享。
一、背景
相比之下大家對數據庫中間件的項目背景會比較熟悉,其實搜索中間件的項目背景也類似,搜索系統總的來說可以分兩種,一種是業務為主的搜索推薦系統,另一種是以提供基礎搜索服務能力為主的泛化的數據檢索系統。
搜索中間件的服務目標就是這種泛化的平台化數據檢索系統。
二、總體目標
根據前期的搜索平台運營經驗,中間件的核心目標主要有三個:
- 引擎虛擬化:業務線不再需要感知具體的引擎地址(如具體的elasticsearch的ip/port),不需要了解集群的版本、配置、部署、高可用等。
- 支持索引拆分:讓索引可以支持類似於db分庫分表的操作,且具體的分庫邏輯和規則對業務透明。
- 流量可控:支持限流、熔斷。
三、需要實現的功能
制定了大目標之後,剩下就是任務拆解,具體來說,搜索中間件需要實現的功能如下:
1. 統一接入
搜索發展初期存在各種域名,對應不同的集群,也對應不同的業務:
- wsc.search.yz.com:9200
- wxd.search.yz.com:9200
- fans.search.yz.com:9200
- msg.search.yz.com:9200
- fx.search.yz.com:9200
在統一接入之後,將會只有:
- search.yz.com
所有的業務線,對搜索的訪問,只有一個入口,由搜索中間件進行鑒權和路由。

業務到索引的訪問,統一到一個入口,通過中間件進行路由控制,這樣對後端集群進行擴容、遷移、拆分等操作對業務來說透明,免去了很大的運維成本,也讓我們在控制流量上有了更大的空間。
這裡也可以根據實際需求分步實現,比如先統一單業務的訪問域名,再統一全部業務的訪問域名。
2. 保持訪問
因為歷史原因,訪問搜索集群的方式多種多樣,有通過原生DSL訪問的,也有通過內部封裝的業務協議請求的,甚至還有不同版本的協議樣式。
保持訪問,就是指讓上游業務對搜索服務的訪問接口盡量不用改變,中間件對上游業務訪問的引擎進行無縫替。
3. 屏蔽多索引
根據業務需要或者性能上的考慮,後端集群會將流量導入到多個索引中,比如冷熱隔離、或者類目/關鍵字檢索隔離,中間件需要對業務屏蔽此類細節,由自己來對不同請求進行路由。
4. 支持索引拆分
我們支持容量伸縮的方式基本是通過索引拆分實現的(比通過索引內部的 _routing 容易控制,比如重建和遷移),db 的分庫方式是在不同的實例中存在同樣的表,db => db1.table / db2.table,索引拆分目前是分表的方式 es.index => es.index1 / es.index2。

這樣對業務層來說訪問的始終是同一個索引,而不必感知實際細節,方便許多。
按照現有的業務使用方式,我們需要支持的拆分規則只需要下述四項即可:
- 按partition key取模拆分(比如 mod 拆分個數 => index${餘數})
- 按partition key劃段拆分(比如 0~100 / 100~500 / 500~)
- 按partition key取白名單拆分(比如 1、3、5 => index1 / 其餘 =>index2)
- 按多個partition key組合拆分(比如白名單+取模,1、2 => index1 / 其餘取模)
5. 熔斷限流
為了保護搜索集群,在遇到流量抖動或者流量洪峰情況下,中間件需要對其進行必要的熔斷或者限流處理,將問題的影響面控制到最低。
熔斷和限流是不同的兩種保護機制,熔斷:保護集群高負載情況下在中間層將流量導流或者拒絕,可行的比如通過監測後端請求失敗率,在失敗率高時觸發熔斷。
限流是限制不同請求能使用的資源,避免流量之間的相互影響,具體的又可以分為兩個方面:
- 能夠自動限制某索引的某項操作,比如 A 索引的寫操作
- 在資源空閑期自動調度流量分配,提高搜索集群的資源利用率
6. 高可用
高可用需要考慮中間件自身和搜索集群兩部分。
中間件部分:
- 故障時流量自動遷移:可以通過client探測故障,如果非client方式,則通過nginx代理能探測故障並摘除故障節點。
- 故障處理:探測搜索集群失敗率,統計自身的運行異常,並在數據異常時發出告警(也要搭配外部的保活工具)
搜索集群部分:
- 數據多活:將寫入 A 集群的數據同步至 B 集群,補全引擎功能。
- 故障流量遷移:中間件探測搜索集群失敗率,異常時自動切換到從集群(初期可以在失敗率異常時發出告警,通過人工干預切換)
7. 可運維
這裡目標是能夠讓開發和運維感知到中間件的運行數據,能夠通過管理命令在運行時進行變更,當然能夠提供界面化的操作最佳。
四、模塊設計
1. 總體架構
為了透明切換業務訪問到中間件,必須在中間件支持原生的server協議,並轉換query到目標dsl發送到集群,之所以不是透傳,主要是考慮到之後引擎升級可能產生與現有query不兼容的語法,總體架構如下:

es對外提供transport和rest兩種協議的服務接口,transport協議私有,並沒有對外公開,如果要兼容這個協議,只能通過查找源代碼中的序列化/反序列化模塊,考慮到大部分場景下對協議的敏感度並沒有我們想像的那麼大,這裡建議是捨棄transport訪問協議,只支持rest部分,在server部分的處理上會簡化不少。
2. 架構細節和組件劃分
架構細節和內部的組件劃分大致如下:

總的分為業務層、中間層、數據層三部分,其中中間層又可以分為管理子系統和業務子系統兩部分。
- 業務層
上游業務可以使用rest風格的http協議、原生es http client或者通過我們封裝的search-client調用搜索中間件,當然也可以支持監控任務或者開發、運維通過管理接口管理集群配置或者採集統計數據。
- 數據層
Es集群,作為承載數據查詢和存儲的介質,位於整個系統的最下游。
- 中間層-config mgr
配置管理初期可以通過本地的配置文件,但是當中間件集群擴大時,仍然使用本地文件的方式帶來的維護成本太大,通常會考慮通過配置中心的方式,可以自行實現,也可以集成現有的如Apollo等開源配置管理中間件。
配置形式上不再繼續展開,可以自行設計合適的組織形式。
- 中間層- httpserver
這部分負責對客戶端的交互,back queue 作為請求隊列除了擴大系統吞吐量,也是削峰限流的一部分,protocol analyzer用於解析客戶端的http request為結構化的查詢請求,這裡還有幾個問題需要處理:
- 異步io,在高吞吐的中間件支持異步調用是必要的,可以使用netty或者akka等異步通信框架
- 並發模型,io和業務處理異步化
- 上下文管理,多線程處理之後需要考慮請求中的特殊標記在線程間傳遞(可以通過改造線程池實現,參考es的設計)
- 連接管理,服務端超時、客戶端超時
- 負載均衡,上游業務流量的負載均衡
- 失敗探測,平滑上下線、失敗節點探測摘除、自動發現新節點
- 權限驗證,不同業務操作資源隔離
- 中間層- esclient
負責與 es 集群交互的部分,初期可以使用原生的low level http client,如果有多個版本 es 集群存在,可以考慮直接通過http client封裝,可以更加方便的控制DSL拼接,也適合對 client 連接池的管理和監控。
- 中間層- query parser
解析查詢,分解為原子條件,比如 term / terms / range / match 等,用來進行查詢校驗,可以分為非拆分索引和拆分索引兩種:
- 非拆分索引:檢查查詢的window size是否過大,是否包含敏感詞等,校驗不通過的查詢拒絕訪問,比如:{'query': {『match_all』:{}}, 『start』: 100000, 』size』:10000} 屬於非法查詢,window size過大
- 拆分索引:除了上述檢查項外,還需要檢查查詢中是否帶有partition key,比如:{'query': {『match_all』:{}}} 也屬於非法查詢,未帶有partition key
- 中間層- query optimize
優化查詢,是查詢過程中比較重要的一環,可以分為兩類:
- 具體的優化類型來源於日常運維過程中的慢查詢分析,以一個實際場景來分析:某外部業務通過scroll低頻查詢es,遍歷近5min內的客戶數據,{'query': {『bool』:{『must』:[{』term』:{』shop_id』:1}},{『range』:{『follow_time』:{『gt』:now-5min,』lt』:now}}}]}}, 『start』: 0, 』size』:10}。此類查詢存在大量佔用filter cache的風險,這裡就可以將其range優化為一個script查詢,避免其被cache住(es禁用filter cache只能全局生效,不能單請求設置)。
- 另一類需要優化的查詢是關鍵字檢索相關的,比如將單個 50% match 條件優化為 match_phrase 和 50% match 的組合,用以提高匹配度,並保證召回。
查詢優化器最後還需要將條件組合為具體的DSL。
- 中間層- query router
這部分負責路由轉發,非拆分索引和只有單路由規則的拆分索引可以根據索引名選擇對應的客戶端連接發送請求即可。
比較複雜的是組合路由規則的拆分索引,為了儘可能提高查詢執行效率,假設查詢命中多個子索引,需要考慮將查詢並行化,並通過 result merger 模塊合併結果返回,這裡的處理規則比較複雜,後續單獨開文展開討論。
- 中間層- query cache
為了提高集群性能考慮,這裡會加入一個短時間的result cache,用於補齊引擎不提供的功能。
- 中間層其他功能模塊
- admin server:監聽管理端口,接收運維操作指令
- config mgr:配置中心,可以獨立為一個服務或者集成第三方配置中心
- monitor mgr:監控告警
- statistic mgr:數據統計,可以通過管理端口對外保護統計數據採集接口