1. 程式人生 > >螞蟻金服服務註冊中心 SOFARegistry 解析 | 服務發現優化之路

螞蟻金服服務註冊中心 SOFARegistry 解析 | 服務發現優化之路

SOFAStack Scalable Open Financial  Architecture Stack 是螞蟻金服自主研發的金融級分散式架構,包含了構建金融級雲原生架構所需的各個元件,是在金融場景裡錘鍊出來的最佳實踐。

SOFARegistry 是螞蟻金服開源的具有承載海量服務註冊和訂閱能力的、高可用的服務註冊中心,最早源自於淘寶的初版 ConfigServer,在支付寶/螞蟻金服的業務發展驅動下,近十年間已經演進至第五代。

本文為《剖析 | SOFARegistry 框架》第二篇,本篇作者尚彧,是 SOFARegistry 開源負責人。《剖析 | SOFARegistry 框架》系列由 SOFA 團隊和原始碼愛好者們出品,專案代號:

SOFA:RegistryLab/,文末附共建列表,歡迎領取共建~

GitHub 地址:https://github.com/sofastack/sofa-registry

概述

無論傳統的 SOA 還是目前的微服務架構,都離不開分散式的特性,既然服務是分佈的就必須解決服務定址的問題。服務註冊中心是這個過程最主要的元件,通過服務註冊和服務發現特性收集服務供求關係,解耦服務消費方對服務提供方的服務定位問題。

服務註冊中心的最主要能力是服務註冊和服務發現兩個過程。服務註冊的過程最重要是對服務釋出的資訊進行儲存,服務發現的過程是把服務釋出端的所有變化(包括節點變化和服務資訊變化)及時準確的通知到訂閱方的過程。

本文詳細描述服務註冊中心 SOFARegistry 對於服務發現的實現和技術演進過程,主要涉及 SOFARegistry 的服務發現實現模式以及服務資料變化後及時推送到海量客戶端感知的優化過程。

服務發現分類

分散式理論最重要的一個理論是 CAP 原理。關於註冊中心的解決方案,根據儲存資料一致性維度劃分業界有很多實現,比如最有代表性的強一致性 CP 系統 ZooKeeper 和最終一致性 AP 系統 Eureka。SOFARegistry 在資料儲存層面採用了類似 Eureka 的最終一致性的過程,但是儲存內容上和 Eureka 在每個節點儲存相同內容特性不同,採用每個節點上的內容按照一致性 Hash 資料分片來達到資料容量無限水平擴充套件能力。

服務端發現和客戶端發現

拋開資料儲存的一致性,我們從服務發現的實現維度考慮服務註冊中心的分類,業界也按照服務地址選擇發生主體和負載均衡策略實現主體分為客戶端服務發現和服務端服務發現。

  • 客戶端服務發現:即由客戶端負責決定可用的服務例項的"位置"以及與其相關的負載均衡策略,就是服務發現的地址列表在客戶端快取後由客戶端自己根據負載均衡策略進行選址完成最終呼叫,地址列表定期進行重新整理或服務端主動通知變更。最主要的缺點是需要有客戶端實現,對於各種異構系統不同語言不同結構的實現必須要進行對應的客戶端開發,不夠靈活,成本較高。

客戶端服務發現

  • 服務端服務發現:在服務端引入了專門的負載均衡層,將客戶端與服務發現相關的邏輯搬離到了負載均衡層來做。客戶端所有的請求只會通過負載均衡模組,其並不需要知會微服務例項在哪裡,地址是多少。負載均衡模組會查詢服務註冊中心,並將客戶端的請求路由到相關可用的微服務例項上。這樣可以解決大量不同實現應用對客戶端的依賴,只要對服務端的負載均衡模組發請求就可以了,由負載均衡層獲取服務發現的地址列表並最終確定目標地址進行呼叫。

服務端服務發現

  • SOFARegistry 服務發現模式:以客戶端服務發現模式為主。這樣的模式實現比較直接,因為在同一個公司內部實踐面對的絕大多數應用基本上都是同一個語言實現的,客戶端實現也只需要確定一套,每個客戶端通過業務內嵌依賴方式部署,並且可以根據業務需求進行定製負載均衡策略進行選址呼叫。當然也會遇到特殊的異構系統,這個隨著微服務架構 RPC 呼叫等通訊能力下沉到 Mesh 執行也得到解決,可以在 Mesh 層進行特定的服務註冊中心客戶端嵌入,選擇路由都在這裡統一進行,對不同語言實現的系統進行無感知。

SOFARegistry 服務發現模式

服務發現的推、拉模型

服務發現最重要的過程是獲取服務釋出方地址列表的過程,這個過程可以分為兩種實現模式:客戶端主動獲取的拉模式和服務端主動變更通知的推送模式:

  • 拉模式主要是在客戶端按照訂閱關係發起主動拉取過程。客戶端在首次訂閱可以進行一次相關服務 ID 的服務列表查詢,並拉取到本地快取,後續通過長輪詢定期進行服務端服務 ID 的版本變更檢測,如果有新版本變更則及時拉取更新本地快取達到和服務端一致。這種模式在服務端可以不進行訂閱關係的儲存,只需要儲存和更新服務釋出資料。由客戶端主動發起的資料獲取過程,對於客戶端實現較重,需要主動獲取和定時輪訓,服務端只需要關注服務註冊資訊的變更和健康情況及時更新記憶體。這個過程由於存在輪訓週期,對於時效性要求不高的情況比較適用。

拉模式

  • 推模式主要是從服務端發起的主動變更推送。這個模式主要資料壓力集中在服務端,對於服務註冊資料的變更和提供方,節點每一次變更情況都需要及時準確的推送到客戶端,更新客戶端快取。這個資料推送量較大,在資料釋出頻繁變更的過程,對於大量訂閱方的大量資料推送頻繁執行,資料壓力巨大,但是資料變更資訊及時,對於每次變更都準確反映到客戶端。

推模式

  • **SOFARegistry 服務發現模式採用的是推拉結合方式。**客戶端訂閱資訊釋出到服務端時可以進行一次地址列表查詢,獲取到全量資料,並且把對應的服務 ID 版本資訊儲存在 Session 回話層,後續如果服務端釋出資料變更,通過服務 ID 版本變更通知回話層 Session,Session 因為儲存客戶端訂閱關係,瞭解哪些客戶端需要這個服務資訊,再根據版本號大小決定是否需要推送給這個版本較舊的訂閱者,客戶端也通過版本比較確定是否更新本次推送的結果覆蓋記憶體。此外,為了避免某次變更通知獲取失敗,定期還會進行版本號差異比較,定期去拉取版本低的訂閱者所需的資料進行推送保證資料最終一致。

推拉結合方式

SOFARegistry 服務發現模式

資料分層

前面的文章介紹過 SOFARegistry 內部進行了資料分層,在服務註冊中心的服務端因為每個儲存節點對應的客戶端的連結資料量有限,必須進行特殊的一層劃分用於專門收斂無限擴充的客戶端連線,然後在透傳相應的請求到儲存層,這一層是一個無資料狀態的代理層,我們稱之為 Session 層。

此外,Session 還承載了服務資料的訂閱關係,因為 SOFARegistry 的服務發現需要較高的時效性,對外表現為主動推送變更到客戶端,所以推送的主體實現也集中在 Session 層,內部的推拉結合主要是通過 Data 儲存層的資料版本變更推送到所有 Session 節點,各個 Session 節點根據儲存的訂閱關係和首次訂閱獲取的資料版本資訊進行比對,最終確定推送給那些服務消費方客戶端。

資料分層

觸發服務推送的場景

直觀上服務推送既然是主動的,必然發生在主動獲取服務時刻和服務提供方變更時刻:

  • 主動獲取:服務訂閱資訊註冊到服務端時,需要查詢所有的服務提供方地址,並且需要將查詢結果推送到客戶端。這個主動查詢並且拉取的過程,推送端是一個固定的客戶端訂閱方,不涉及服務 ID 版本資訊判定,直接獲取列表進行推送即可,主要發生在訂閱方應用剛啟動時刻,這個時期可能沒有服務釋出方釋出資料,會查詢到空列表給客戶端,這個過程基本上類似一個同步過程,體現為客戶端一次查詢結果的同步返回。
  • 版本變更:為了確定服務釋出資料的變更,我們對於一個服務不僅定義了服務 ID,還對一個服務 ID 定義了對應的版本資訊。服務釋出資料變更主動通知到 Session 時,Session 對服務 ID 版本變更比較,高版本覆蓋低版本資料,然後進行推送。這次推送是比較大面積的推送,因為對於這個服務 ID 感興趣的所有客戶端訂閱方都需要推送,並且需要按照不同訂閱維度和不同型別的客戶端進行資料組裝,進行推送。這個過程資料量較大,並且需要所有訂閱方都推送成功才能更新當前儲存服務 ID 版本,需要版本更新確認,由於效能要求必須併發執行並且非同步確定推送成功。
  • 定期輪訓:因為有了服務 ID 的版本號,Session 可以定期發起版本號比較,如果Session 儲存的的服務 ID 版本號高於dataServer儲存的 ,Session再次拉取新版本資料進行推送,這樣避免了某次變更通知沒有通知到所有訂閱方的情況。

服務推送效能優化

服務訂閱方的數量決定了資料推送一次的數量,對於一臺 Session 機器來說目前我們儲存 sub 數量達到60w+,如果服務釋出方頻繁變更,對於每次變更推送量是巨大的,故我們對整個推送的過程進行優化處理:

  • 服務釋出方頻繁變更優化:在所有業務叢集啟動初期,每次對於一個相同的服務,會有很多服務提供方併發不停的新增,如果對於每次新增的提供方都進行一次推送顯然不合理,我們對這個情況進行服務提供方的合併,即每個服務推送前進行一定延遲等待所有pub新增到一定時間進行一次推送。這個處理極大的減少推送的頻率,提升推送效率。

服務釋出方頻繁變更優化

  • 即使對服務變更進行了合併延遲處理,但是推送任務產生也是巨大的,所以對於瞬間產生的這麼大的任務量進行佇列緩衝處理是必須的。目前進行所有推送任務會根據服務 ID、推送方 IP 和推送方資訊組成唯一任務 ID 進行任務入隊處理。隊列當中如果是相同的服務變更產生推送任務,則進行任務覆蓋,執行最後一次版本變更的任務。此外任務執行進行分批次處理,批次大小可以配置,每個批次處理完成再獲取任務批次進行處理。

佇列緩衝處理

異常處理

對於這麼大資料量的推送過程必然會因為網路等因素推送失敗,對於失敗的異常推送場景我們如何處理:

  • 重試機制:很顯然推送失敗的客戶端訂閱依然還在,或者對應的連結還存在,這個失敗的推送必須進行重試,重試機制定義十分重要。
    • 目前對於上述首次啟動主動獲取資料進行推送的重試進行了有限次重試,並且每次重試之前進行網路監測和新版本變更檢測,此外進行了時間延遲間隔,保證網路故障重試的成功機率。
    • 這個延遲重試,最初我們採用簡單的 sleep 方式,終止當前執行緒然後再發起推送請求。這個方式對於資源消耗巨大,如果出現大量的任務重試,會產生大量的執行緒停止佔用記憶體,同時 sleep 方式對於恢復執行也不是很準確,完全取決於系統排程時間。後續我們對重試任務進行時間輪演算法分片進行,對於所有重試任務進行了時間片定義,時間輪詢執行對應時間片重試任務執行,效率極大提升,並且佔用資源很小。

重試機制

  • 補償措施:對於推送失敗之前也說有定時任務進行輪訓服務 ID 版本,服務 ID 的版本在所有推送方都接受到這個版本變更推送才進行更新,如果有一個訂閱方推送失敗,就不更新版本。後續持續檢查版本再啟動任務,對沒有推送成功的訂閱方反覆執行推送,直到推送成功或者訂閱方不存在,這個過程類似於無限重試的過程。

資料處理分階段

註冊中心資料的來源主要來自於兩個方向,一個是大量應用客戶端新連線上來並且釋出和訂閱資料並存儲在註冊中心的階段,另外一個是之前這些釋出的服務資料必須按照訂閱方的需求推送出去的階段。這兩個階段資料量都非常巨大,都在首次部署註冊中心後發生,如果同時對伺服器進行衝擊網路和 CPU 都會成為瓶頸,故我們通過運維模式進行了兩個階段資料的分離處理:

  • 關閉推送開關:我們在所有註冊中心啟動初期進行了推送開關關閉的處理,這樣在服務註冊中心新啟動或者新發布初期,因為客戶端有本地快取,在推送關閉的情況下,註冊中心的啟動只從客戶端新註冊資料,沒有推送新的內容給客戶端,做到對現有執行系統最小影響。並且,由於推送關閉,資料只處理新增的內容這樣對網路和 CPU 壓力減少。
  • 開推送:在關閉推送時刻記錄沒有推送過的訂閱者,所有資料註冊完成(主要和釋出之前的資料數量比較),沒有明顯增長情況下,開啟推送,對於所有訂閱方進行資料推送更新記憶體。

總結

面對海量的資料進行服務註冊和服務推送,SOFARegistry 採用了資料合併、任務合併處理,對於資料註冊和資料推送兩個大量資料過程進行了分開處理,並且在資料推送失敗進行了重試機制優化,以及進行了定期版本號比對機制保證了資料一致性。

歡迎加入,參與 SOFARegistry 原始碼解析

SOFALab

本文為《剖析 | SOFARegistry  實現原理》第二篇,分享了 SOFARegistry 在面對海量資料處理中的服務優化方式。之後我們會逐步詳細介紹各個部分的程式碼設計和實現,預計按照如下的目錄進行:

  • 【已完成】海量資料下的註冊中心 - SOFARegistry 架構介紹
  • 【已完成】SOFARegistry 服務發現優化之路
  • 【已領取】SOFARegistry 如何實現秒級服務上下線通知
  • 【已領取】SOFARegistry MetaServer 功能介紹和實現剖析
  • 【已領取】SOFARegistry 資料分片和同步方案詳解
  • 【待領取】SOFARegistry 如何實現 DataServer 平滑擴縮容
  • 【待領取】SOFARegistry 資料推送機制詳解

如果有同學對以上某個主題特別感興趣的,可以留言討論,我們會適當根據大家的反饋調整文章的順序,謝謝大家關注 SOFAStack ,關注 SOFARegistry,我們會一直與大家一起成長。

領取方式

關注公眾號:金融級分散式架構,回覆公眾號想認領的文章名稱,我們將會主動聯絡你,確認資質後,即可加入,It's your show time!

除了原始碼解析,也歡迎提交 issue 和 PR:

SOFARegistry:https://github.com/sofast