有贊搜尋系統的架構演進

程式猿DD發表於2019-03-05

有贊搜尋平臺是一個面向公司內部各項搜尋應用以及部分 NoSQL 儲存應用的 PaaS 產品,幫助應用合理高效的支援檢索和多維過濾功能,有贊搜尋平臺目前支援了大大小小一百多個檢索業務,服務於近百億資料。

在為傳統的搜尋應用提供高階檢索和大資料互動能力的同時,有贊搜尋平臺還需要為其他比如商品管理、訂單檢索、粉絲篩選等海量資料過濾提供支援,從工程的角度看,如何擴充套件平臺以支援多樣的檢索需求是一個巨大的挑戰。

我是有贊搜尋團隊的第一位員工,也有幸負責設計開發了有贊搜尋平臺到目前為止的大部分功能特性,我們搜尋團隊目前主要負責平臺的效能、可擴充套件性和可靠性方面的問題,並儘可能降低平臺的運維成本以及業務的開發成本。

#Elasticsearch

Elasticsearch 是一個高可用分散式搜尋引擎,一方面技術相對成熟穩定,另一方面社群也比較活躍,因此我們在搭建搜尋系統過程中也是選擇了 Elasticsearch 作為我們的基礎引擎。

#架構1.0

時間回到 2015 年,彼時執行在生產環境的有贊搜尋系統是一個由幾臺高配虛擬機器組成的 Elasticsearch 叢集,主要執行商品和粉絲索引,資料通過 Canal 從 DB 同步到 Elasticsearch,大致架構如下:

有贊搜尋系統的架構演進

通過這種方式,在業務量較小時,可以低成本的快速為不同業務索引建立同步應用,適合業務快速發展時期,但相對的每個同步程式都是單體應用,不僅與業務庫地址耦合,需要適應業務庫快速的變化,如遷庫、分庫分表等,而且多個 canal 同時訂閱同一個庫,也會造成資料庫效能的下降。 另外 Elasticsearch 叢集也沒有做物理隔離,有一次促銷活動就因為粉絲資料量過於龐大導致 Elasticsearch 程式 heap 記憶體耗盡而 OOM,使得叢集內全部索引都無法正常工作,這給我上了深深的一課。

#架構 2.0

我們在解決以上問題的過程中,也自然的沉澱出了有贊搜尋的 2.0 版架構,大致架構如下:

有贊搜尋系統的架構演進

首先資料匯流排將資料變更訊息同步到 mq,同步應用通過消費 mq 訊息來同步業務庫資料,借資料匯流排實現與業務庫的解耦,引入資料匯流排也可以避免多個 canal 監聽消費同一張表 binlog 的虛耗。

#高階搜尋(Advanced Search)

隨著業務發展,我們也逐漸出現了一些比較中心化的流量入口,比如分銷、精選等,這時普通的 bool 查詢並不能滿足我們對搜尋結果的細粒率排序控制需求,將複雜的 function_score 之類專業性較強的高階查詢編寫和優化工作交給業務開發負責顯然是個不可取的選項,這裡我們考慮的是通過一個高階查詢中介軟體攔截業務查詢請求,在解析出必要的條件後重新組裝為高階查詢交給引擎執行,大致架構如下:

有贊搜尋系統的架構演進

這裡另外做的一點優化是加入了搜尋結果快取,常規的文字檢索查詢 match 每次執行都需要實時計算,在實際的應用場景中這並不是必須的,使用者在一定時間段內(比如 15 或 30 分鐘)通過同樣的請求訪問到同樣的搜尋結果是完全可以接受的,在中介軟體做一次結果快取可以避免重複查詢反覆執行的虛耗,同時提升中介軟體響應速度,對高階搜尋比較感興趣的同學可以閱讀另外一篇文章《有贊搜尋引擎實踐(工程篇)》,這裡不再細述。

#大資料整合

搜尋應用和大資料密不可分,除了通過日誌分析來挖掘使用者行為的更多價值之外,離線計算排序綜合得分也是優化搜尋應用體驗不可缺少的一環,在 2.0 階段我們通過開源的 es-hadoop 元件搭建 hive 與 Elasticsearch 之間的互動通道,大致架構如下:

有贊搜尋系統的架構演進

通過 flume 收集搜尋日誌儲存到 hdfs 供後續分析,也可以在通過 hive 分析後匯出作為搜尋提示詞,當然大資料為搜尋業務提供的遠不止於此,這裡只是簡單列舉了幾項功能。

#問題

這樣的架構支撐了搜尋系統一年多的執行,但是也暴露出了許多問題,首當其衝的是越發高昂的維護成本,除去 Elasticsearch 叢集維護和索引本身的配置、欄位變更,雖然已經通過資料匯流排與業務庫解耦,但是耦合在同步程式中的業務程式碼依舊為團隊帶來了極大的維護負擔。訊息佇列雖然一定程式上減輕了我們與業務的耦合,但是帶來的訊息順序問題也讓不熟悉業務資料狀態的我們很難處理。這些問題我總結在之前寫過的一篇文章。 除此之外,流經 Elasticsearch 叢集的業務流量對我們來說呈半黑盒狀態,可以感知,但不可預測,也因此出現過線上叢集被內部大流量錯誤呼叫壓到CPU佔滿不可服務的故障。

#目前的架構 3.0

針對 2.0 時代的問題,我們在 3.0 架構中做了一些針對性調整,列舉主要的幾點:

  1. 通過開放介面接收使用者呼叫,與業務程式碼完全解耦;
  2. 增加 proxy 用來對外服務,預處理使用者請求並執行必要的流控、快取等操作;
  3. 提供管理平臺簡化索引變更和叢集管理 這樣的演變讓有贊搜尋系統逐漸的平臺化,已經初具了一個搜尋平臺的架構:

有贊搜尋系統的架構演進

#Proxy

作為對外服務的出入口,proxy 除了通過 ESLoader 提供相容不同版本 Elasticsearch 呼叫的標準化介面之外,也內嵌了請求校驗、快取、模板查詢等功能模組。 請求校驗主要是對使用者的寫入、查詢請求進行預處理,如果發現欄位不符、型別錯誤、查詢語法錯誤、疑似慢查詢等操作後以 fast fail 的方式拒絕請求或者以較低的流控水平執行,避免無效或低效能操作對整個 Elasticsearch 叢集的影響。 快取和 ESLoader 主要是將原先高階搜尋中的通用功能整合進來,使得高階搜尋可以專注於搜尋自身的查詢分析和重寫排序功能,更加內聚。我們在快取上做了一點小小的優化,由於查詢結果快取通常來說帶有源文件內容會比較大,為了避免流量高峰頻繁訪問導致 codis 叢集網路擁堵,我們在 proxy 上實現了一個簡單的本地快取,在流量高峰時自動降級。

這裡提一下模板查詢,在查詢結構(DSL)相對固定又比較冗長的情況下,比如商品類目篩選、訂單篩選等,可以通過模板查詢(search template)來實現,一方面簡化業務編排DSL的負擔,另一方面還可以通過編輯查詢模板 template,利用預設值、可選條件等手段在服務端進行線上查詢效能調優。

#管理平臺

為了降低日常的索引增刪、欄位修改、配置同步上的維護成本,我們基於 Django 實現了最初版本的搜尋管理平臺,主要提供一套索引變更的審批流以及向不同叢集同步索引配置的功能,以視覺化的方式實現索引後設資料的管理,減少我們在平臺日常維護上的時間成本。 由於開源 head 外掛在效果展示上的不友好,以及暴露了部分粗暴功能:

有贊搜尋系統的架構演進

如圖,可以通過點按欄位使得索引按指定欄位排序展示結果,在早期版本 Elasticsearch 會通過 fielddata 載入需要排序的欄位內容,如果欄位資料量比較大,很容易導致 heap 記憶體佔滿引發 full gc 甚至 OOM,為了避免重複出現此類問題,我們也提供了定製的視覺化查詢元件以支援使用者瀏覽資料的需求。

#ESWriter

由於 es-hadoop 僅能通過控制 map-reduce 個數來調整讀寫流量,實際上 es-hadoop 是以 Elasticsearch 是否拒絕請求來調整自身行為,對線上工作的叢集相當不友好。為了解決這種離線讀寫流量上的不可控,我們在現有的 DataX 基礎上開發了一個 ESWriter 外掛,能夠實現記錄條數或者流量大小的秒級控制。

#挑戰

平臺化以及配套的文件體系完善降低了使用者的接入門檻,隨著業務的快速增長,Elasticsearch 叢集本身的運維成本也讓我們逐漸不堪,雖然有物理隔離的多個叢集,但不可避免的會有多個業務索引共享同一個物理叢集,在不同業務間各有出入的生產標準上支援不佳,在同一個叢集內部署過多的索引也是生產環境穩定執行的一個隱患。 另外叢集服務能力的彈性伸縮相對困難,水平擴容一個節點都需要經歷機器申請、環境初始化、軟體安裝等步驟,如果是物理機還需要更長時間的機器採購過程,不能及時響應服務能力的不足。

#未來的架構 4.0

當前架構通過開放介面接受使用者的資料同步需求,雖然實現了與業務解耦,降低了我們團隊自身的開發成本,但是相對的使用者開發成本也變高了,資料從資料庫到索引需要經歷從資料匯流排獲取資料、同步應用處理資料、呼叫搜尋平臺開放介面寫入資料三個步驟,其中從資料匯流排獲取資料與寫入搜尋平臺這兩個步驟在多個業務的同步程式中都會被重複開發,造成資源浪費。這裡我們目前也準備與 PaaS 團隊內自研的DTS(Data Transporter,資料同步服務)進行整合,通過配置化的方式實現 Elasticsearch 與多種資料來源之間的自動化資料同步。

要解決共享叢集應對不同生產標準應用的問題,我們希望進一步將平臺化的搜尋服務提升為雲化的服務申請機制,配合對業務的等級劃分,將核心應用獨立部署為相互隔離的物理叢集,而非核心應用通過不同的應用模板申請基於 k8s 執行的 Elasticsearch 雲服務。應用模板中會定義不同應用場景下的服務配置,從而解決不同應用的生產標準差異問題,而且雲服務可以根據應用執行狀況及時進行服務的伸縮容。

#小結

本文從架構上介紹了有贊搜尋系統演進產生的背景以及希望解決的問題,涉及具體技術細節的內容我們將會在本系列的下一篇文章中更新。

相關文章