前言
使用者搜尋元件和日志管理平台是個推推送服務的重要組成部分。ElasticSearch(簡稱ES)作為一個開源的分布式搜尋引擎,能較好地滿足上述要求。個推在ES的使用上經過了多年疊代,積累了豐富的經驗,特别是在資料量不斷增大時,如何管理叢集、維護叢集穩定、優化叢集性能,我們進行了許多實踐。
本文将從三部分講述個推ElasticSearch架構的演變過程:大叢集的挑戰、GProxy如何支援多叢集以及目前運作情況。

作者| 個推平台研發總監 逍遙 &進階Java工程師 南洋
**
01個推ES服務概述
個推使用ES的業務場景主要是對使用者資訊增删改查和日志存儲,是支援個推推送的核心服務之一,其特點是:
● 資料更新快:需要實時更新使用者畫像等資訊
● 查詢條件複雜:支援多種次元交并補,組合條件搜尋
● 查詢資料量大:一個推送任務可能會需要查詢千萬級的資料
我們選擇ES的原因主要有三:
● 提供近實時索引,最短可以在1s内搜尋到寫入的文檔
● 能夠支援複雜條件的查詢,滿足我們各種檢索條件的需求
● 分布式特性能夠較好地滿足水準擴容和高可用的要求
此外,ES官方和社群都非常活躍,周邊的生态産品也多,遇到問題基本能夠很好地得到解決。
02
大叢集的挑戰
個推叢集演進
上圖展示的是個推ES叢集演進的過程。個推使用ES時間較早,起初從0.20.x版本開始用,當時叢集規模較小,後來為了scroll查詢結果不帶source,進行了index-source的分離,更新到了0.90.x版本。接着官方推出了支援無source查詢功能的1.2.0版本,我們又進行了叢集合并。随後,為了使用官方的一些新特性,我們更新到了1.4.x和1.5.x版本。
此時,叢集規模已經很大了,更新一次版本比較不友善,而且後續的新特性對我們沒有很強的吸引力,于是很長時間沒更新,跳過了官方推出的2.x版本。但叢集由于規模大,開始出現很多難以解決的問題,需要進行拆分更新,并且官方版本也更新到了5.x版本,不支援直接從1.x更新5.x,于是我們考慮用資料網關的方式解決更新、重新開機的困難,叢集也演化到了上圖的最後一版架構。
大叢集的問題
大叢集還帶來很多問題,以下是我們在使用過程中遇到相對比較棘手的幾個問題。
● 第一個問題是JVM記憶體容易居高不下。
ES記憶體的占用主要有幾個部分:Segment Memory、Filter Cache、Field Data cache 、Bulk Queue、Indexing Buffer和Cluster State Buffer 。Segment Memory會随着段檔案增長而增長,大叢集這塊記憶體占用沒法避免。
Filter Cache和Field Data cache個推用到的場景不多,我們便通過參數配置予以禁用。Bulk Queue和Indexing Buffer都比較固定,記憶體不會不斷增長,也沒必要調整參數。Cluster State Buffer是當時比較棘手的一個問題,我們設定mapping的方式是在config目錄下配置default_mapping.json檔案,該檔案會比對所有寫入的文檔,進行格式解析處理,ES處了解析過之後會在記憶體中緩存一份ParserContext。而由于type數不斷增加,這部分記憶體占用會越來越多,直到耗盡,如果不索引新type,則不會增長。
在出現記憶體居高不下的問題時,我們分析了它的dump檔案,如下圖,發現是ParserContext占用導緻的。當時沒有很好的辦法徹底解決,隻能采用重新開機的方式清空ParserContext,暫時緩解一段時間。但随着文檔的不斷寫入,這部分記憶體占用還是會重新變大。
● 第二個問題是超大分片和超大段。
我們預設使用docid作為routing的依據,通過hash算法将文檔散列到不同分片上。這種方式在文檔數少的時候分片大小還比較均勻,但當文檔數漲到了一定程度時,分片的大小差距會變大。在每個分片平均100g大小的時候,差距最大可達20g。此時,達到最大段門檻值限制的段數量也非常多,這導緻在段合并的時候比較耗時。
● 第三個問題是磁盤IO高。
我們經常使用scroll查詢文檔,超大的段檔案會導緻檔案磁盤查找效率降低。并且機器記憶體被ES節點的JVM占用了大半,緻使檔案系統很難使用記憶體緩存段檔案頁。這就導緻scroll查詢會直接讀取磁盤檔案,IO被打滿。從監控看,叢集的IO基本是時刻處于滿的狀态。
此外還有許多隐患。
首先是擴充瓶頸,叢集預設的分片數原本是較充裕的,然而在經過多次擴容後,執行個體數已經和分片數相等了,擴容執行個體後,叢集也不會将分片配置設定到新執行個體。其次是原先機器的磁盤空間逐漸不足,ES預設水位線是85%,到達後分片就會開始亂跳難以恢複。
然後是調整難,重新開機恢複非常慢,如果更新重建,索引也很慢。
最後叢集的健壯性也會受到影響。文檔數變大,壓力也會變大,更容易出現故障,某個執行個體故障後壓力會分攤給其他節點,業務會産生感覺。
03
GProxy的解決方案
對于上面描述的這些問題,我們希望找到一個解決方案能夠提供如下功能:
1、能夠平滑更新叢集版本,并且更新期間不影響業務使用
2、通過拆分大叢集為小叢集,使業務分流,減輕叢集壓力
3、提供叢集資料的多IDC熱備,為異地多活提供資料層支援
但是之前的架構中,業務服務直接通路ES叢集,耦合嚴重,要實作這些需求就變得比較困難。于是,我們選擇了proxy-based架構,通過增加中間層proxy來隔離存儲叢集和業務服務叢集,為更靈活的運維存儲叢集提供支援。
上圖是GProxy的整體架構,它是一個三層架構:
● 最上層是業務層,隻需和 proxy 互動,通過etcd實作proxy的服務發現
● 中間是GProxy層,提供請求轉發和叢集的管理
● 最下方是多個ES叢集
GProxy包含了多個元件:
● etcd:高可用的元資訊存儲資料庫,包括路由規則、叢集資訊、proxy服務位址、遷移任務等
● sdk:擴充了es原生sdk,通過其sniffer機制,封裝了對proxy服務發現和熔斷等功能
● proxy是一個輕量級的代理服務,擴容很友善,啟動後可以将自己的位址注冊到etcd中
● dashboard 是整個叢集的管理服務,并提供 web 界面,友善運維人員對叢集進行管理和監控
● migrate服務提供不同叢集間的遷移功能
服務發現和路由規則
有了上面的總體架構後,還需要解決兩個問題:
1、業務服務如何發現proxy,也即服務發現問題
2、 proxy将請求轉發給哪一個叢集,也即路由問題
服務發現
etcd是一個高可用的分布式鍵值資料庫,且通過http api進行互動,操作簡便,是以我們選擇etcd來實作服務發現和中繼資料存儲。
proxy是一個無狀态的服務,啟動初始化完成之後,将自己的位址注冊到etcd中。通過etcd的lease機制,系統可以監控proxy的存活狀态。當proxy服務出現異常而不能定時續期lease時,etcd會将其摘除,避免其影響正常的業務請求。
ElasticSearch提供的sdk預留了sniffer接口,sdk可以通過sniffer接口來擷取後端位址。我們實作了sniffer接口,其定期從etcd擷取proxy清單,并通過etcd的watch機制,監聽服務的上下線,及時更新内部的連接配接清單。業務方還是可以按原來的方式使用原生的sdk,不需要過多的改動,隻需要将sniffer注入到SDK就行。
路由規則
在個推推送業務場景中,每個app推送需要的資料都可以視為是一個整體,是以我們選擇按照app的次元進行請求的路由,每個app推送所需的資料都存儲在一個叢集内。
路由資訊儲存在etcd中,格式是 appid->clusterName 這樣一個對應關系。如果沒有這樣一個對應關系,proxy會将appid歸屬到一個預設叢集。
proxy啟動時會拉取最新的路由表,并通過etcd的watch機制,監控路由表的變更。
路由關系的變更通過遷移操作實作,以下是遷移流程的介紹。
遷移流程
每個app都屬于一個叢集。當叢集的負載不均衡時,管理者可以按照app次元,通過遷移服務進行叢集間的資料遷移。
遷移流程包含兩個步驟:資料同步和路由規則的修改。資料同步需要同步兩份資料:全量資料和增量資料。
1、全量資料通過ElasticSearch的scroll api導出
2、因為ElasticSearch沒有提供增量資料的擷取方式(類似mysql的binlog協定來實作增量資料的擷取),是以我們通過proxy雙寫來實作增量資料的擷取。
遷移服務負責資料的同步,并在資料同步完成後通知dashboard,dashboard更新etcd的路由關系。proxy通過watch機制,得到新的路由關系,更新内部的路由表,此時app新的請求就會路由到新的叢集。
多IDC資料熱備
在個推實際業務場景中,推送作為企業級服務,對服務的可用性要求很高,個推有多個機房對外提供服務,每個app歸屬到一個機房。為了應對機房級故障,我們需要對資料進行多IDC的熱備,這樣才能在機房發生故障後,将客戶的請求路由到非故障機房,進而不影響客戶的正常使用。
我們對資料的熱備采用叢集次元進行,每個叢集的資料會備份到另一個機房。proxy收到請求後,根據叢集的熱備資訊,實時将增量資料寫入到MQ中,另一個機房的consumer服務不斷消費MQ的增量資料,并寫入至對應的叢集中。dashboard服務負責管控所有IDC的熱備任務的狀态。
性能
引入一個中間層後,不可避免地會帶來一定的性能損失。我們選擇 GO 開發的原因,就是希望損失盡可能減小。最終性能結果如下:
由上圖可知,QPS降低10%左右,平均延時約等于ES調用和proxy本身平均延時之和。雖然有了10%的性能下降,但是帶來了更靈活的運維能力。
目前運作情況
GProxy服務上線之後,順利完成了ES版本的更新(從1.5更新到6.4),并将原來大叢集拆分為多個小叢集。整個更新和拆分過程對于業務方無感,并且GProxy提供的無損復原功能可以讓操作更放心(資料的遷移需要十分謹慎)。
有了GProxy的支援,DBA日常對ES運維操作,如參數的優化、叢集間的壓力平衡,變得更加友善。
結語
個推通過使用Go語言,自主研發了Gproxy,成功解決ElasticSearch大叢集存在的問題,為上層業務提供了穩定可靠的資料存儲服務。此外,個推也将持續打磨自身技術,在搜尋和資料存儲領域不斷探索,不斷拓寬ElasticSearch的應用場景,與開發者一起分享關于如何保證資料存儲高可用的最新實踐。