有贊搜索系統的技術內幕

上文說到有贊搜索系統的架構演進,爲了支撐不斷演進的技術架構,除了 Elasticsearch 的維護優化以外,咱們也開發了上層的中間件來應對不斷提升的穩定性和性能要求。網絡

Elasticsearch 的檢索執行效率能夠表示爲: O(num_of_files * logN)架構

其中 num_of_files 表示索引文件段的個數,N 表示須要遍歷的數據量,從這裏咱們能夠總結出提高查詢性能能夠考慮的兩點:框架

  1. 減小遍歷的索引文件數量
  2. 減小遍歷的索引文檔總數

從 Elasticsearch 自身來講,減小索引文件數量方面能夠參考幾點:異步

  1. 經過 optimize 接口強制合併段
  2. 增大 index buffer/refresh_interval,減小小段生成,控制相同數量的文檔生成的新段個數

不管是強制合併或者 index buffer/refresh_interval,都有其應用場景的限制,好比調整 index buffer / refresh_interval 會相應的延長數據可見時間;optimize 對冷數據集比較適用,若是數據在不斷變化過程當中,除了新段的生成,老數據可能由於舊段過大而得不到物理刪除,反而形成較大的負擔。性能

而減小文檔總數方面,也能夠作相應的優化:優化

  1. 減小文檔更新
  2. 指定 _routing 來路由查詢到指定的 shard
  3. 經過 rollover 接口進行冷熱隔離

這裏尤爲須要注意的是減小文檔更新,因爲 LSM 追加寫的數據組織方式,更新數據實際上是新增數據+標記老數據爲刪除狀態的組合,真實參與計算的數據量是有效數據和標記刪除的數據量之和,減小文檔更新次數除了減小標記刪除數據以外,還能夠下降段 merge 以及索引刷新的消耗。設計

考慮到實際的業務場景,若是將海量數據存儲於單個索引,因爲 shard 個數不可變,一方面會使得索引分配大量的 shard,數據量持續增加會逐漸拖慢索引訪問性能,另外一方面想要經過擴展 shard 提升讀寫性能須要重建海量數據,成本至關高昂。日誌

索引拆分

爲了加強索引的橫向擴容能力,咱們在中間件層面進行了索引拆分,參考實際的業務場景將大索引拆分爲若干個小索引。 在索引拆分前,首先須要檢查索引對應業務是否知足拆分的三個必要條件:cdn

  1. 讀寫操做一定會帶入固定條件
  2. 讀寫操做維度惟一
  3. 用戶不關心全局的搜索結果

比較典型的好比店鋪內商品搜索,不論買賣家都只關心固定店鋪內的商品檢索結果,沒有跨店鋪檢索需求,後臺店鋪與商品也有固定的映射關係,這樣就能夠在中間件層面對讀寫請求進行解析,並路由到對應的子索引中,減小遍歷的文檔總量,能夠在性能上得到明顯的提高。中間件

相對 Elasticsearch 自帶的 _routing,這個方案具有更加靈活的控制粒度,好比能夠配置白名單,將部分店鋪數據路由到其餘不一樣 SLA 級別的索引或集羣,固然能夠配合 _routing 以得到更好的表現。

索引拆分首先會帶來全局索引文件數據上升的問題,不過由於沒有全局搜索需求,因此不會帶來實質的影響;其次比較須要注意的是數據傾斜問題,在拆分前須要先經過離線計算模擬索引拆分效果,若是發現數據傾斜嚴重,就能夠考慮將子索引數據進行重平衡。

如圖所示,數據重平衡在原有的拆分基礎上加入一個邏輯拆分步驟:

  1. 數據首先拆分爲 5 個邏輯索引
  2. 設定重平衡因子,假設爲 N
  3. 根據重平衡因子將邏輯索引數據順序哈希到N個連續的物理索引中

如圖,按平衡因子3重平衡以後文檔數據量的最大差值從 510 降爲了 160,單索引佔全局數據量比例從以前的 53% 下降爲 26%,可以起到不錯的數據平衡效果。

冷熱隔離

在查詢維度不惟一的場景下,索引拆分就不適用了,爲了解決此類場景下的性能問題,能夠考慮對索引進行冷熱隔離。 好比日誌/訂單類型的數據,具有比較明顯的時間特徵,大量的操做都集中在近期的一段時間內,這時就能夠考慮依據時間字段對其拆分爲冷熱索引。

Elasticsearch 自帶有 rollover 接口供索引進行自動輪轉,經過索引存活時間和保存的文檔數量做爲輪轉條件,知足其中之一便可建立一個新索引並將其做爲當前的活躍索引。 在實際應用過程當中,rollover 接口須要用戶感知活躍索引的變動,且自行計算查詢須要訪問的索引範圍,爲了對用戶屏蔽底層這些複雜操做的細節,咱們在中間件封裝了索引的冷熱隔離特性,從用戶視角只須訪問固定索引並帶入固定字段便可。

首先配置路由表,根據不一樣的時間跨度劃定不一樣的路由規則,好比業務初期數據增量並不大,能夠用50的時間跨度建立子索引,後期業務增量變大後逐漸縮短期跨度至 10 來建立子索引。 經過查詢條件的起止時間點分別從路由表中計算對應的子索引偏移量,獲得起止範圍,以上圖爲例,連續的子索引範圍爲 index2~index14,也就是該條件將命中此區間內的所有子索引,寫操做也相似,區別在於寫數據只會落到一個子索引,通常是當前的活躍索引。 這樣冷熱隔離的方式拆分能夠兼容多維度的查詢需求,好比訂單的買賣家查詢維度,並且拆分規則比較靈活,能夠動態調整,另外刪除數據只須要刪除整個過時索引,而沒必要經過 delete_by_query 的方式緩慢刪除索引數據。 除此以外,爲了更好的配合用戶使用,咱們還對此開發了索引的自動輪轉/定時清理等輔助功能。

HA

隨着搜索系統的普遍使用,用戶對系統的穩定性也提出了更高的要求,好比在機房發生斷電等故障狀況下,依然可以保證服務可用,這就須要咱們可以將數據進行跨機房複製同步。 首先考慮的方案是跨機房組建 Elasticsearch 集羣,這樣實現很是簡單,可是問題是機房的網絡交互是走專線的,在集羣宕機恢復數據過程當中會有很大的帶寬消耗,可能引發機房間的網絡擁堵,所以這個方案並不可行。 Elasticsearch 自己也在開發 Changes API 特性,能夠用於跨集羣的數據同步,但惋惜的是該特性仍然在開發中,在參考了主流的數據同步方法後,咱們在中間件層開發了一套異步數據複製系統。

在確認索引開啓多機房複製後,首先在 proxy 側啓動增量同步,發送同步消息給 mq 做爲同步程序 clones 的增量數據源,而後經過 reindex 功能從主索引全量複製數據到從索引。

在跨機房同步過程當中,數據容易由於 MQ、proxy 異步發送等影響而亂序,Elasticsearch 能夠經過樂觀鎖來保證數據變動的一致性,避免亂序的影響,前提是 version 可以一直保持在索引中。 可是在物理刪除模式下,因爲數據被物理清理,沒法繼續保持版本號的延續,這就有可能致使跨機房數據同步的髒寫。

爲了不樂觀鎖失效,咱們的解決方法是軟刪除的方式:

  1. delete 操做在中間件轉換爲 index 操做,文檔內容僅包含一個特殊字段,不會命中正常的搜索條件,也就是正常狀況下沒法搜索獲得該文檔,達到實際的刪除效果
  2. 在中間件將 create/get/update/delete 等操做轉換爲 script 請求,保持原有語義不變
  3. 經過軟刪除文檔中特殊字段記錄的時間戳定時清理數據(可選)

爲了可以感知到主從索引間的數據一致性和同步延遲,還有一套輔助的數據對帳系統實時運行,能夠用於主從索引數據的校驗、修復並經過 MQ 消費延時計算主從數據的延遲。

小結

到這裏有贊搜索系統的大體框架已經介紹完畢,由於篇幅的緣由還有不少細節的功能設計並無完整表述,也歡迎有興趣的同窗聯繫咱們一塊兒探討,有表述錯誤的地方也歡迎你們聯繫咱們糾正。 關於 Elasticsearch 方面本次的兩篇文章都沒有太多涉及,後續待咱們整理完善以後會做爲一個擴展閱讀奉送給你們。

相關文章
相關標籤/搜索