從平臺到中臺 | Elasticsearch 在螞蟻金服的實踐經驗

螞蟻金服分散式架構發表於2018-11-12

善仁,螞蟻金服通用搜尋產品負責人,通用搜尋目前擁有上萬億文件,服務了上百個業務方,是螞蟻內部最大的搜尋產品。其所在的螞蟻中介軟體搜尋團隊專注於構建簡單可信的搜尋產品,是阿里經濟體中最大的搜尋服務提供商。目前專注於抽象各種複雜場景下的搜尋解決方案,力求讓搜尋人人可用,人人會用。


本文根據他在 2018 Elastic 中國開發者大會的分享整理

從平臺到中臺 | Elasticsearch 在螞蟻金服的實踐經驗

大家好,我是來自螞蟻金服中介軟體團隊的善仁,目前是螞蟻通用搜尋產品的負責人。今天給大家分享的主題是《Elasticsearch 在螞蟻金服的中臺實踐經驗》

從平臺到中臺 | Elasticsearch 在螞蟻金服的實踐經驗

基於 Elasticsearch 的通用搜尋是螞蟻內部最大的搜尋產品,目前擁有上萬億文件,服務了上百個業務方。而通用搜尋的發展主要分為兩個階段:平臺化和中臺化。

今天我將分別介紹下我們在這兩個階段的發展中為業務解決了哪些痛點以及我們是如何去解決這些痛點的。

源動力:架構複雜、運維艱難

從平臺到中臺 | Elasticsearch 在螞蟻金服的實踐經驗

和大多數大型企業一樣,螞蟻內部也有一套自研的搜尋系統,我們稱之為主搜。

但是由於這種系統可定製性高,所以一般業務接入比較複雜,週期比較長。而對於大量新興的中小業務而言,迭代速度尤為關鍵,因此難以用主搜去滿足。

主搜不能滿足,業務又實際要用,怎麼辦呢?那就只能自建了。在前幾年螞蟻內部有很多小的搜尋系統,有 ES,也有 solr,甚至還有自己用 lucene 的。

業務痛點

從平臺到中臺 | Elasticsearch 在螞蟻金服的實踐經驗

然而業務由於自身迭代速度很快,去運維這些搜尋系統成本很大。就像 ES,雖然搭建一套很是簡單,但是用在真實生產環境中還是需要很多專業知識的。作為業務部門很難去投入人力去運維維護。

並且由於螞蟻自身的業務特性,很多業務都是需要高可用保證的。而我們都知道ES本身的高可用目前只能跨機房部署了,先不談跨機房部署時的分配策略,光是就近訪問一點,業務都很難去完成。

因為這些原因,導致這類場景基本都沒有高可用,業務層寧願寫兩套程式碼,準備一套兜底方案。覺得容災時直接降級也比高可用簡單。

架構痛點

從平臺到中臺 | Elasticsearch 在螞蟻金服的實踐經驗

從整體架構層面看,各個業務自行搭建搜尋引擎造成了煙囪林立,各種重複建設,並且這種中小業務一般資料量都比較小,往往一個業務一套三節點叢集只有幾萬條資料,造成整體資源利用率很低,而且由於搜尋引擎選用的版本,部署的方式都不一致,也難以保證質量。在架構層面只能當做不存在搜尋能力。

『低成本,高可用,少運維』的 Elasticsearch 平臺應運而生

基於以上痛點,我們產生了構建一套標準搜尋平臺的想法,將業務從運維中解放出來,也從架構層面統一基礎設施。提供一種簡單可信的搜尋服務。

架構:

從平臺到中臺 | Elasticsearch 在螞蟻金服的實踐經驗

如何做『低成本,高可用,少運維』呢?
我們先來一起看一下整體架構,如圖
首先說明一下我們這兩個框框代表兩個機房,我們整體就是一種多機房的架構,用來保證高可用

  • 最上層是使用者接入層,有 API,Kibana,Console 三種方式,使用者和使用 ES 原生的 API 一樣可以直接使用我們的產品;

  • 中間為路由層(Router),負責將使用者請求真實傳送到對應叢集中,負責一些干預處理邏輯;

  • 下面每個機房中都有佇列(Queue),負責削峰填谷和容災多寫。

  • 每個機房中有多個 ES 叢集,使用者的資料最終落在一個真實的叢集中,或者一組對等的高可用叢集中;

  • 右邊紅色的是 Meta,負責所有元件的一站式自動化運維和後設資料管理;

  • 最下面是 kubernetes, 我們所有的元件均是以容器跑在k8s上的,這解放了我們很多物理機運維操作,使得滾動重啟這些變得非常方便;

低成本:多租戶

看完了整體,下面就逐點介紹下我們是怎麼做的,第一個目標是低成本。
在架構層面,成本優化是個每年必談的話題。那麼降低成本是什麼意思?實際上就是提高資源利用率。提高資源利用率方法有很多,比如提高壓縮比,降低查詢開銷。但是在平臺上做簡單有效的方式則是多租戶。

今天我就主要介紹下我們的多租戶方案:
多租戶的關鍵就是租戶隔離,租戶隔離分為邏輯隔離和物理隔離。

邏輯隔離

從平臺到中臺 | Elasticsearch 在螞蟻金服的實踐經驗

首先介紹下我們的邏輯隔離方案,邏輯隔離就是讓業務還是和之前自己搭 ES 一樣的用法,也就是透明訪問,但是實際上訪問的只是真實叢集中屬於自己的一部分資料,而看不到其他人的資料,也就是保證水平許可權。而 ES 有一點很適合用來做邏輯隔離,ES 的訪問實際上都是按照 index 的。因此我們邏輯隔離的問題就轉化為如何讓使用者只能看到自己的表了。

我們是通過 console 儲存使用者和表的對映關係,然後在訪問時通過 router,也就是前面介紹的路由層進行干預,使得使用者只能訪問自己的 index。具體而言,我們路由層採用 OpenResty+Lua 實現,將請求過程分為了右圖的四步,Dispatch,Filter,Router,Reprocess

  1. 在 Dispatch 階段我們將請求結構化,抽出其使用者,app,index,action 資料

  2. 然後進入 Filter 階段,進行寫過濾和改寫,filter 又分為三步

  • Access 進行限流和驗權這類的准入性攔截,

  • Action 對具體的操作進行攔截處理,比如說 DDL ,也就是建表,刪表,修改結構這些操作,我們將其轉發到 Console 進行處理,一方面方便記錄其 index 和 app 的對應資訊,另一方面由於建刪表這種還是很影響叢集效能的,我們通過轉發給 console 可以對使用者進行進一步限制,防止惡意行為對系統的影響。

  • Params 則是請求改寫,在這一步我們會根據具體的 index 和 action 進行相應的改寫。比如去掉使用者沒有許可權的 index,比如對於 kibana 索引將其改為使用者自己的唯一 kibana 索引以實現 kibana 的多租戶,比如對ES不同版本的簡單相容。在這一步我們可以做很多,不過需要注意的有兩點,一是儘量不要解析 body, 解 body是一種非常影響效能的行為,除了特殊的改寫外應該盡力避免,比如 index 就應該讓使用者寫在 url 上,並利用ES本身的引數關閉 body 中指定 index 的功能,這樣改寫速度可以快很多。 二是對於_all 和 getMapping 這種對所有 index 進行訪問的,如果我們替換為使用者所有的索引會造成 url 過長,我們採用的是建立一個和應用名同名的別名,然後將其改寫成這個別名

  • 進行完 Filter 就到了真實的 router 層,這一層就是根據 filter 的結果做真實的路由請求,可能是轉發到真實叢集也能是轉發到我們其他的微服務中。

  • 最後是 Reprocess ,這是拿到業務響應後的最終處理,我們在這邊會對一些結果進行改寫,並且非同步記錄日誌

  • 上面這四步就是我們路由層的大致邏輯,通過 app 和 index 的許可權關係控制水平許可權,通過 index 改寫路由進行共享叢集。

    物理隔離

    從平臺到中臺 | Elasticsearch 在螞蟻金服的實踐經驗

    做完了邏輯隔離,我們可以保證業務的水平許可權了,那麼是否就可以了呢?顯然不是的,實際中不同業務訪問差異還是很大的,只做邏輯隔離往往會造成業務間相互影響。這時候就需要物理隔離了。不過物理隔離我們目前也沒有找到非常好的方案,這邊給大家分享下我們的一些嘗試

    首當其衝,我們採用的方法是服務分層,也就是將不同用途,不同重要性的業務分開,對於關鍵性的主鏈路業務甚至可以獨佔叢集。對於其他的,我們主要分為兩類,寫多查少的日誌型和查多寫少的檢索型業務,按照其不同的要求和流量預估將其分配在我們預設的叢集中。不過需要注意的是申報的和實際的總會有差異的,所以我們還有定期巡檢機制,會將已上線業務按照其真實流量進行叢集遷移

    做完了服務分層,我們基本可以解決了低重要性業務影響高重要性業務的場景,但是在同級業務中依舊會有些業務因為比如說做營銷活動這種造成突發流量。對於這種問題怎麼辦?

    一般而言就是全侷限流,但是由於我們的訪問都是長連線,所以限流並不好做。如右圖所示,使用者通過一個 LVS 訪問了我們多個 Router,然後我們又通過了 LVS 訪問了多個 ES 節點,我們要做限流,也就是要保證所有 Router 上的令牌總數。一般而言全侷限流有兩種方案,一是以限流維度將所有請求打在同一例項上,也就是將同一表的所有訪問打在一臺機器上,但是在 ES 訪問量這麼高的場景下,這種並不合適,並且由於我們前面已經有了一層lvs 做負載均衡,再做一層路由會顯得過於複雜。

    第二種方案就是均分令牌,但是由於長連線的問題,會造成有些節點早已被限流,但是其他節點卻沒有什麼流量。


    那麼怎麼辦呢?

    從平臺到中臺 | Elasticsearch 在螞蟻金服的實踐經驗

    既然是令牌使用不均衡,那麼我們就讓其分配也不均衡就好了唄。所以我們採用了一種基於反饋的全侷限流方案,什麼叫基於反饋呢?就是我們用巡檢去定時去定時採集用量,用的多就多給一些,用的少就少給你一點。那麼多給一些少給一點到底是什麼樣的標準呢?這時我們就需要決策單元來處理了,目前我們採取的方案是簡單的按比例分配。這邊需要注意的一點是當有新機器接入時,不是一開始就達到終態的,而是漸進的過程。所以需要對這個收斂期設定一些策略,目前因為我們機器效能比較好,不怕突發毛刺,所以我們設定的是全部放行,到穩定後再進行限流。

    這裡說到長連線就順便提一個 nginx 的小引數,keepalive_timeout, 用過 nginx 的同學應該都見過,表示長連線超時時間,預設有75s,但是這個引數實際上還有一個可選配置,表示寫在響應頭裡的超時時間,如果這個引數沒寫的話就會出現在服務端釋放的瞬間客戶端正好複用了這個連線,造成 Connection Reset或者 NoHttpResponse 的問題。出現頻率不高,但是真實影響使用者體驗,因為隨機低頻出現,我們之前一直以為是客戶端問題,後來才發現是原來是這個釋放順序的問題。

    至此服務分層,全侷限流都已經完成了,是不是可以睡個好覺了呢? 很遺憾,還是不行,因為ES語法非常靈活,並且有許多大代價的操作,比如上千億條資料做聚合,或者是用萬用字元做箇中綴查詢,寫一個複雜的script都有可能造成拖垮我們整個叢集,那麼對於這種情況怎麼辦呢? 我們目前也是處於探索階段,目前看比較有用的一種方式是事後補救,也就是我們通過巡檢去發現一些耗時大的 Task,然後對其應用的後繼操作進行懲罰,比如降級,甚至熔斷。這樣就可以避免持續性的影響整個叢集。但是一瞬間的rt上升還是不可避免的,因此我們也在嘗試事前攔截,不過這個比較複雜,感興趣的同學可以一起線下交流一下

    高可用:對等多叢集

    從平臺到中臺 | Elasticsearch 在螞蟻金服的實踐經驗

    講完了低成本,那麼就來到了我們第二個目標,高可用。
    正如我之前提到那樣,ES 本身其實提供了跨機房部署的方案,通過打標就可以進行跨機房部署,然後通過preference可以保證業務就近查詢。我這裡就不再詳細說了。但是這種方案需要兩地三中心, 而我們很多對外輸出的場景出於成本考慮,並沒有三中心,只有兩地兩中心,因此雙機房如何保證高可用就是我們遇到的一個挑戰。下面我主要就給大家分享下我們基於對等多機房的高可用方案,我們提供了兩種型別,共三種方案分別適用於不同的業務場景。

    我們有單寫多讀和多寫多讀兩種型別

    單寫多讀我們採用的是跨叢集複製的方案,通過修改 ES,我們增加了利用 translog 將主叢集資料推送給備的能力。就和 6.5 的 ccr 類似,但是我們採用的是推模式,而不是拉模式,因為我們之前做過測試,對於海量資料寫入,推比拉的效能好了不少。容災時進行主備互換,然後恢復後再補上在途資料。由上層來保證單寫,多讀和容災切換邏輯。這種方案通過 ES 本身的t ranslog 同步,部署結構簡單,資料也很準確,類似與資料庫的備庫,比較適合對寫入 rt 沒有過高要求的高可用場景。

    從平臺到中臺 | Elasticsearch 在螞蟻金服的實踐經驗

    多寫多讀,我們提供了兩種方案

    • 第一種方案比較取巧,就是因為很多關鍵鏈路的業務場景都是從DB同步到搜尋中的,因此我們打通了資料通道,可以自動化的從DB寫入到搜尋,使用者無需關心。那麼對於這類使用者的高可用,我們採用的就是利用DB的高可用,搭建兩條資料管道,分別寫入不同的叢集。這樣就可以實現高可用了,並且還可以絕對保證最終一致性。

    • 第二種方案則是在對寫入rt有強要求,有沒有資料來源的情況下,我們會採用中間層的多寫來實現高可用。我們利用訊息佇列作為中間層,來實現雙寫。就是使用者寫的時候,寫成功後保證佇列也寫成功了才返回成功,如果一個不成功就整體失敗。然後由佇列去保證推送到另一個對等叢集中。用外部版本號去保證一致性。但是由於中間層,對於Delete by Query的一致性保證就有些無能為力了。所以也僅適合特定的業務場景。

    最後,在高可用上我還想說的一點是對於平臺產品而言,技術方案有哪些,怎麼實現的業務其實並不關心,業務關心的僅僅是他們能不能就近訪問降低rt,和容災時自動切換保證可用。因此我們在平臺上遮蔽了這些複雜的高可用型別和這些適用的場景,完全交由我們的後端去判斷,讓使用者可以輕鬆自助接入。並且在互動上也將讀寫控制,容災操作移到了我們自己系統內,對使用者無感知。只有使用者可以這樣透明擁有高可用能力了,我們的平臺才真正成為了高可用的搜尋平臺。

    少運維

    從平臺到中臺 | Elasticsearch 在螞蟻金服的實踐經驗

    最後一個目標,少運維
    今天運維的話題已經分享了很多了,我這邊就不在贅述了。就簡單介紹一下我們在整體運維繫統搭建過程中沉澱出的四個原則。自包含,元件化,一站到底,自動化。

    • 自包含ES做的就很不錯了,一個jar就可以啟動,而我們的整套系統也都應該和單個ES一樣,一條很簡單的命令就能啟動,沒有什麼外部依賴,這樣就很好去輸出。

    • 元件化是指我們每個模組都應該可以插拔,來適應不同的業務場景,比如有的不需要多租戶,有的不需要削峰填谷。

    • 一站到底是指我們的所有元件,router,queue,es,還有很多微服務的管控都應該在一個系統中去管控,萬萬不能一個元件一套自己的管控。

    • 自動化就不說了,大家都懂

    • 右邊就是我們的一個大盤頁面,展現了router,es和queue的訪問情況。當然,這是mock的資料

    回看業務:無需運維,卻依舊不爽

    從平臺到中臺 | Elasticsearch 在螞蟻金服的實踐經驗

    至此我們已經擁有了一套『低成本,高可用,少運維』的 Elasticsearch 平臺了,也解決了之前談到的業務痛點,那麼使用者用的是否就爽了呢?我們花了大半個月的時間,對我們的業務進行了走訪調研,發現業務雖然已經從運維中解放了出來,但是身上還是有不少搜尋的枷鎖。

    從平臺到中臺 | Elasticsearch 在螞蟻金服的實踐經驗

    我們主要分為兩類使用者,資料分析和全文檢索的。

    • 資料分析主要覺得配置太複雜,他只是想匯入一個日誌資料,要學一堆的欄位配置,而且很久才會用到一次,每次學完就忘,用到再重學,很耽誤事情。其次,無關邏輯重,因為資料分析類的一般都是保留多天的資料,過期的資料就可以刪除了,為了實現這一個功能,資料分析的同學要寫很多程式碼,還要控制不同的別名,很是麻煩。

    • 而全文檢索類的同學主要痛點有三個,一是分詞配置複雜,二是難以修改欄位,reindex 太複雜,還要自己先建立別名,再控制無縫切換。第三點是Debug艱難,雖然現在有 explain,但是用過的同學應該都懂,想要整體梳理出具體的算分原因還是需要自己在腦中開闢很大的一塊快取的。對於不熟悉 ES 的同學就太痛苦了。

    整理一下,這些痛點歸類起來就兩個痛點,學習成本高和介面過於原子。

    搜尋中臺:抽象邏輯,解放業務

    從平臺到中臺 | Elasticsearch 在螞蟻金服的實踐經驗

    學習成本高和介面過於原子,雖然是業務的痛點,但是對ES本身而言卻反而是優點,為什麼學習成本高呢?因為功能豐富。而為什麼介面原子呢,為了讓上層可以靈活使用。

    這些對於專家使用者而言,非常不錯,但是對於業務而言,的確很是麻煩。

    因此我們開始了我們第二個階段,搜尋中臺。什麼叫中臺呢,就是把一些通用的業務邏輯下移,來減少業務的邏輯,讓業務專注於業務本身。

    而為什麼業務不能做這些呢?當然也能做。但是俗話說『天下武功,唯快不破』,前臺越輕,越能適應這變化極快的業務訴求。

    因此我們的搜尋中臺的主要目標就是兩點:
    一是降低業務學習成本,加快上手速度。我們這次介紹的主要是如何降低對於配置類這種低頻操作的學習成本;
    二是抽象複雜邏輯來加速業務迭代,我們這次主要會介紹抽象了哪兩種業務邏輯。

    降低學習成本

    從平臺到中臺 | Elasticsearch 在螞蟻金服的實踐經驗

    降低學習成本,這個怎麼做呢?眾所周知,黑屏變白屏,也就是白屏化。但是很多的白屏化就是把命令放在了 web 上,回車變按鈕。 這樣真的可以降低使用者學習成本麼? 我想毋庸置疑,這樣是不行的。

    我們在視覺化上嘗試了許多方案,也走了許多彎路,最後發現要想真正降低使用者學習成本,需要把握三個要點

    1. 使用者分層,區分出小白使用者和專家使用者,不要讓專家使用者的意見影響整體產品的極簡設計,對於小白使用者一定是越少越好,選擇越少,路徑越短,反饋越及時,效果越好。正如所謂的沉默的大多數,很多小白使用者並不會去主動發聲,只會隨著複雜的配置而放棄使用。 下圖就是我們對於專家使用者和小白使用者在配置表結構時不同的頁面,對於專家使用者,基本就是ES所有的功能視覺化,加快使用速度。對於小白使用者而言,則是完全遮蔽這些功能點,讓其可以直接使用。

    從平臺到中臺 | Elasticsearch 在螞蟻金服的實踐經驗

    1. 引導式配置,引導式配置其實也就是加上限制,通過對使用者的上一步輸入決定下一步的可選。要避免一個頁面開啟一堆配置項,這樣使用者就會無從下手,更不要談學習成本了。通過引導式配置來減少使用者的選擇,降低使用者的記憶成本。限制不一定就意味著約束使用者,合適的限制更可以降低使用者的理解成本。比如右圖就是我們的一個分詞器配置,很簡單的引導,使用者選擇了中文字典後才可以選擇相應的詞典

    從平臺到中臺 | Elasticsearch 在螞蟻金服的實踐經驗

    第三點,深層次結構打平,什麼叫深層次結構打平,就是指像現在的分詞器,相似度這些都是在index級別下的,我們將其抽象出來,變為全域性的。使用者可以自行建立全域性的分詞器,相似度,並且還可以共享給其他人,就像一個資源一樣。然後在index中則是引用這個分詞器。雖然這邊做的僅僅是將分詞器從index級別變為了全域性,但是缺真正的減少了很多業務操作,因為在一個業務場景中,往往存在多張表,而多張表往往會使用同一套分詞器。通過這種全域性性的分詞器使用者僅需修改一處即可作用於所有位置。

    抽象複雜邏輯

    從平臺到中臺 | Elasticsearch 在螞蟻金服的實踐經驗

    好的,說完了白屏化的一些經驗,這邊給大家分享我們對於複雜邏輯的抽象封裝的兩種新型表結構。這兩種分別是資料分析類場景,我們抽象出了日誌型表,另一種是全文檢索類場景,我們抽象出了別名型表。

    日誌型表的作用顧名思義就是存日誌,也就是之前說的對於資料分析類業務,往往只保留幾天,比如我們現在有個業務場景,有張es的日誌表,只想保留3天,於是我們就給他按天建立索引,然後寫入索引掛載到今天,查詢索引掛載所有的,用router去自動改寫別名,使用者還是傳入es,但是執行寫入操作時實際就是在es_write執行,查詢就是在es_read執行。當然實際中我們並不是按天建的索引,我們會利用Rollover建立很多的索引來保證海量寫入下的速度。但是整體邏輯還是和這個是一樣的

    從平臺到中臺 | Elasticsearch 在螞蟻金服的實踐經驗

    而對於全文檢索類場景,主要的痛點就是表結構的變更和分詞器,字典類的變更,需要重建索引。所以我們則抽象了一個叫別名表的表結構,使用者建立一張表es,實際建立的是一個es的別名,我們會把他和我們真實的index一一對應上。這樣利用這個別名,我們就可以自動幫使用者完成索引重建的操作。而索引重建,我們有兩種方式,一是使用者配置了資料來源的,我們會直接從資料來源進行重建,重建完成後直接切換。另外對於沒有資料來源,直接api寫入的,目前我們是利用了 ES 的 reindex 再配合我們訊息佇列的訊息回放實現的。具體而言,我們就是首先提交 reindex,同時資料開始進 queue 轉發,然後待 reindex 完成後,queue 再從 reindex 開始時進行回放,追平時切別名即可

    總結

    從平臺到中臺 | Elasticsearch 在螞蟻金服的實踐經驗

    總結一下這次分享的內容,我們首先構建了一個『低成本,高可用,少運維』的ES平臺將業務從運維中解脫出來,然後又進一步構建了搜尋中臺,通過降低業務學習成本,下沉通用業務邏輯來加速業務迭代,賦能業務。 當然,這裡介紹的搜尋中臺只是最基礎的中臺能力,我們還在進一步探索些複雜場景下如何抽象來降低業務成本,也就是垂直化的搜尋產品,有興趣的同學歡迎加入我們,一同構建,讓天下沒有難用的搜尋。我們各類崗位持續招聘中,產品,設計,前端,後端,運維都歡迎加入。 郵箱: xinyu.jxy@antfin.com

    完整 PPT 地址:

    http://www.sofastack.tech/posts/2018-11-12-01

    或點選文字底部“閱讀全文”直接訪問


    從平臺到中臺 | Elasticsearch 在螞蟻金服的實踐經驗

    長按關注,獲取分散式架構乾貨

    歡迎大家共同打造 SOFAStack https://github.com/alipay



    相關文章