SOFAStack
Scalable
Open
Financial
Architecture Stack 是螞蟻金服自主研發的金融級分佈式架構,包含了構建金融級雲原生架構所需的各個組件,是在金融場景裏錘鍊出來的最佳實踐。
SOFARegistry 是螞蟻金服開源的具備承載海量服務註冊和訂閱能力的、高可用的服務註冊中心,最先源自於淘寶的第一版 ConfigServer,在支付寶/螞蟻金服的業務發展驅動下,近十年間已經演進至第五代。git
本文爲《剖析 | SOFARegistry 框架》第二篇,本篇做者尚彧,是 SOFARegistry 開源負責人。《剖析 | SOFARegistry 框架》系列由 SOFA 團隊和源碼愛好者們出品,項目代號:<SOFA:RegistryLab/>,文末附共建列表,歡迎領取共建~github
GitHub 地址:https://github.com/sofastack/sofa-registry算法
概述
不管傳統的 SOA 仍是目前的微服務架構,都離不開分佈式的特性,既然服務是分佈的就必須解決服務尋址的問題。服務註冊中心是這個過程最主要的組件,經過服務註冊和服務發現特性收集服務供求關係,解耦服務消費方對服務提供方的服務定位問題。緩存
服務註冊中心的最主要能力是服務註冊和服務發現兩個過程。服務註冊的過程最重要是對服務發佈的信息進行存儲,服務發現的過程是把服務發佈端的全部變化(包括節點變化和服務信息變化)及時準確的通知到訂閱方的過程。性能優化
本文詳細描述服務註冊中心 SOFARegistry 對於服務發現的實現和技術演進過程,主要涉及 SOFARegistry 的服務發現實現模式以及服務數據變化後及時推送到海量客戶端感知的優化過程。服務器
服務發現分類
分佈式理論最重要的一個理論是 CAP 原理。關於註冊中心的解決方案,根據存儲數據一致性維度劃分業界有不少實現,好比最有表明性的強一致性 CP 系統 ZooKeeper 和最終一致性 AP 系統 Eureka。SOFARegistry 在數據存儲層面採用了相似 Eureka 的最終一致性的過程,可是存儲內容上和 Eureka 在每一個節點存儲相同內容特性不一樣,採用每一個節點上的內容按照一致性 Hash 數據分片來達到數據容量無限水平擴展能力。網絡
服務端發現和客戶端發現
拋開數據存儲的一致性,咱們從服務發現的實現維度考慮服務註冊中心的分類,業界也按照服務地址選擇發生主體和負載均衡策略實現主體分爲客戶端服務發現和服務端服務發現。架構
- 客戶端服務發現:即由客戶端負責決定可用的服務實例的"位置"以及與其相關的負載均衡策略,就是服務發現的地址列表在客戶端緩存後由客戶端本身根據負載均衡策略進行選址完成最終調用,地址列表按期進行刷新或服務端主動通知變動。最主要的缺點是須要有客戶端實現,對於各類異構系統不一樣語言不一樣結構的實現必需要進行對應的客戶端開發,不夠靈活,成本較高。

- 服務端服務發現:在服務端引入了專門的負載均衡層,將客戶端與服務發現相關的邏輯搬離到了負載均衡層來作。客戶端全部的請求只會經過負載均衡模塊,其並不須要知會微服務實例在哪裏,地址是多少。負載均衡模塊會查詢服務註冊中心,並將客戶端的請求路由到相關可用的微服務實例上。這樣能夠解決大量不一樣實現應用對客戶端的依賴,只要對服務端的負載均衡模塊發請求就能夠了,由負載均衡層獲取服務發現的地址列表並最終肯定目標地址進行調用。

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

服務發現的推、拉模型
服務發現最重要的過程是獲取服務發佈方地址列表的過程,這個過程能夠分爲兩種實現模式:客戶端主動獲取的拉模式和服務端主動變動通知的推送模式:併發
- 拉模式主要是在客戶端按照訂閱關係發起主動拉取過程。客戶端在首次訂閱能夠進行一次相關服務 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 進行任務入隊處理。隊列當中若是是相同的服務變動產生推送任務,則進行任務覆蓋,執行最後一次版本變動的任務。此外任務執行進行分批次處理,批次大小能夠配置,每一個批次處理完成再獲取任務批次進行處理。

異常處理
對於這麼大數據量的推送過程必然會由於網絡等因素推送失敗,對於失敗的異常推送場景咱們如何處理:

- 補償措施:對於推送失敗以前也說有定時任務進行輪訓服務 ID 版本,服務 ID 的版本在全部推送方都接受到這個版本變動推送才進行更新,若是有一個訂閱方推送失敗,就不更新版本。後續持續檢查版本再啓動任務,對沒有推送成功的訂閱方反覆執行推送,直到推送成功或者訂閱方不存在,這個過程相似於無限重試的過程。
數據處理分階段
註冊中心數據的來源主要來自於兩個方向,一個是大量應用客戶端新鏈接上來而且發佈和訂閱數據並存儲在註冊中心的階段,另一個是以前這些發佈的服務數據必須按照訂閱方的需求推送出去的階段。這兩個階段數據量都很是巨大,都在首次部署註冊中心後發生,若是同時對服務器進行衝擊網絡和 CPU 都會成爲瓶頸,故咱們經過運維模式進行了兩個階段數據的分離處理:
- 關閉推送開關:咱們在全部註冊中心啓動初期進行了推送開關關閉的處理,這樣在服務註冊中心新啓動或者新發布初期,由於客戶端有本地緩存,在推送關閉的狀況下,註冊中心的啓動只從客戶端新註冊數據,沒有推送新的內容給客戶端,作到對現有運行系統最小影響。而且,因爲推送關閉,數據只處理新增的內容這樣對網絡和 CPU 壓力減小。
- 開推送:在關閉推送時刻記錄沒有推送過的訂閱者,全部數據註冊完成(主要和發佈以前的數據數量比較),沒有明顯增加狀況下,打開推送,對於全部訂閱方進行數據推送更新內存。
總結
面對海量的數據進行服務註冊和服務推送,SOFARegistry 採用了數據合併、任務合併處理,對於數據註冊和數據推送兩個大量數據過程進行了分開處理,而且在數據推送失敗進行了重試機制優化,以及進行了按期版本號比對機制保證了數據一致性。
歡迎加入,參與 SOFARegistry 源碼解析

本文爲《剖析 | SOFARegistry 實現原理》第二篇,分享了 SOFARegistry 在面對海量數據處理中的服務優化方式。以後咱們會逐步詳細介紹各個部分的代碼設計和實現,預計按照以下的目錄進行:
- 【已完成】海量數據下的註冊中心 - SOFARegistry 架構介紹
- 【已完成】SOFARegistry 服務發現優化之路
- 【已領取】SOFARegistry 如何實現秒級服務上下線通知
- 【已領取】SOFARegistry MetaServer 功能介紹和實現剖析
- 【已領取】SOFARegistry 數據分片和同步方案詳解
- 【待領取】SOFARegistry 如何實現 DataServer 平滑擴縮容
- 【待領取】SOFARegistry 數據推送機制詳解
若是有同窗對以上某個主題特別感興趣的,能夠留言討論,咱們會適當根據你們的反饋調整文章的順序,謝謝你們關注 SOFAStack ,關注 SOFARegistry,咱們會一直與你們一塊兒成長。
領取方式
關注公衆號:金融級分佈式架構,回覆公衆號想認領的文章名稱,咱們將會主動聯繫你,確認資質後,便可加入,It's your show time!
除了源碼解析,也歡迎提交 issue 和 PR:
SOFARegistry:https://github.com/sofastack/sofa-registry