螞蟻金服開源服務註冊中心 SOFARegistry | SOFA 開源一週年獻禮

螞蟻金服分散式架構發表於2019-03-28

SOFAStack

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

本文根據 SOFA Meetup#1 北京站 現場分享整理,完整的分享 PPT 獲取方式見文章底部。


SOFAStack 開源一週年,繼續補充開源大圖

2018 年 4 月, 螞蟻金服宣佈開源 SOFAStack 金融級分散式架構。這一年的時間,感謝社群的信任和支援,目前已經累積超過一萬的 Star 數目,超過 30 家企業使用者。

2019 年 3 月 24 日,SOFA 在北京舉辦了首場 Meetup,我們有幸見到了關心SOFA 的朋友們。

此次,我們宣佈開源螞蟻金服註冊中心 SOFARegistry 作為一週年的禮物之一,本文為根據現場分享整理的詳細介紹。

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

GitHub 地址:github.com/alipay/sofa…

概念

註冊中心在微服務架構中位置

服務發現,並非新鮮名詞,在早期的 SOA 框架和現在主流的微服務框架中,每個服務例項都需要對外提供服務,同時也需要依賴外部服務。如何找到依賴的服務(即服務定位),最初我們思考了很多方式,比如直接在 Consumer 上配置所依賴的具體的服務地址列表,或者使用 LVS、F5 以及 DNS(域名指向所有後端服務的 IP)等負載均衡。

螞蟻金服開源服務註冊中心 SOFARegistry | SOFA 開源一週年獻禮

但是以上方式都有明顯的缺點,比如無法動態感知服務提供方節點變更的情況,另外基於負載均衡的話還需要考慮它的瓶頸問題。所以需要藉助第三方的元件,即服務註冊中心來提供服務註冊和訂閱服務,在服務提供方服務資訊發生變化、或者節點上下線時,可以動態更新消費方的服務地址列表資訊,最終解耦服務呼叫方和服務提供方。

image.png

能力

服務註冊中心的主要能力:

  • 服務註冊
  • 服務訂閱

演進

螞蟻金服的服務註冊中心,經歷了 5 代技術架構演進,才最終形成了如今足以支撐螞蟻海量服務註冊訂閱的,具有高可用、高擴充套件性和高時效性的架構。

資料結構

SOFARegistry 的儲存模型比較簡單,主要基於 KV 儲存兩類資料,一類是訂閱關係,體現為多個訂閱方關心的 Topic(或服務鍵值)和他們的監聽器列表,另一類是同一個 Topic(或服務鍵值)的釋出者列表。基於觀察者模式,在服務提供方發生變化時(比如服務提供方的節點上下線),會從訂閱關係中尋找相應的訂閱者,最終推送最新的服務列表給訂閱者。

image.png

儲存擴充套件

主備模式

  • 既然服務註冊中心最主要能力之一是儲存,那麼就要思考:怎麼存?存哪兒?存了會不會丟?
  • 怎麼存,主要是由存什麼資料來決定的,由於 SOFARegistry 所儲存的資料結構比較簡單( KV),因為並沒有基於關聯式資料庫;另外由於服務發現對變更推送的時效性要求高,但並沒有很高的持久化要求(資料可以從服務提供方恢復),所以最終我們決定自己實現儲存能力。
  • SOFARegistry 的儲存節點,最初是主備模式。

image.png

強一致叢集

隨著螞蟻的服務資料量不斷增長,我們將儲存改為叢集方式,每個儲存節點的資料是一樣的,每一次寫入都保證所有節點寫入成功後才算成功。這種模式的特點是每臺伺服器都儲存了全量的服務資料,在當時資料規模比較小的情況下,尚可接受。

image.png

這樣的部署結構有兩個問題:

  • 首先,根據 CAP 原理,在分割槽容忍性(P)的前提下,為了保持強一致(C),必然犧牲高可用(A)為代價,比如 Zookeeper 是 CP 系統,它在內部選舉期間是無法對外提供服務的,另外由於需要保證 C(順序一致性),寫的效率會有所犧牲。
  • 其次,每個節點都儲存全量的服務資料,隨著業務的發展就有很大的瓶頸問題。

資料分片

如果要實現容量可無限擴充套件,需要把所有資料按照一定維度進行拆分,並儲存到不同節點,當然還需要儘可能地保證資料儲存的均勻分佈。我們很自然地想到可以進行 Hash 取餘,但簡單的取餘演算法在節點數增減時會影響全域性資料的分佈,所以最終採用了一致性 Hash 演算法(這個演算法在業界很多場景已經被大量使用,具體不再進行介紹)。

image.png

每個服務資料,經過一致性 Hash 演算法計算後會儲存到某個具體的 Data 上,整體形成環形的結構。理論上基於一致性 Hash 對資料進行分片,叢集可以根據資料量進行無限地擴充套件。

內部分層

連線承載

我們知道單機的 TCP 連線數是有限制的,業務應用不斷的增多,為了避免單機連線數過多,我們需要將儲存節點與業務應用數量成正比地擴容,而我們實際上希望儲存節點的數量只跟資料量成正比。所以我們選擇從儲存節點上把承載連線職責的能力獨立抽離出來成為新的一個角色,稱之為 Session 節點,Session 節點負責承載來自業務應用的連線。這麼一來,SOFARegistry 就由單個儲存角色被分為了 Session 和 Data 兩個角色,Session 承載連線,Data 承載資料,並且理論上 Session 和 Data 都支援無限擴充套件。

image.png

如圖,客戶端直接和 Session 層建立連線,每個客戶端只選擇連線其中一個 Session 節點,所有原本直接到達 Data層的連線被收斂到 Session 層。Session 層只進行資料透傳,不儲存資料。客戶端隨機連線一臺 Session 節點,當遇到 Session 不可用時重新選擇新的 Session 節點進行重連即可。

讀寫分離

分離出 Session 這一層負責承載連線,引起一個新的問題:資料到最終儲存節點 Data 的路徑變長了,整個叢集結構也變的複雜了,怎麼辦呢?

我們知道,服務註冊中心的一個主要職責是將服務資料推送到客戶端,推送需要依賴訂閱關係,而這個訂閱關係目前是儲存到 Data 節點上。在 Data 上儲存訂閱關係,但是 Client 並沒有直接和 Data 連線,那必須要在 Session 上儲存對映後才確定推送目標,這樣的對映關係佔據了大量儲存,並且還會隨 Session 節點變化進行大量變更,會引起很多不一致問題。

因此,我們後來決定,把訂閱關係資訊(Sub)直接儲存在 Session 上,並且通過這個關係 Session 直接承擔把資料變化推送給客戶端的職責。而對於服務的釋出資訊(Pub)還是通過 Session 直接透傳最終在 Data 儲存節點上進行匯聚,即同一個服務 ID 的資料來自於不同的客戶端和不同的 Session 最終在 Data 的一個節點儲存。

image.png

這樣劃分了 Sub 和 Pub 資料之後,通過訂閱關係(Sub)進行推送的過程就有點類似於對服務資料讀取的過程,服務釋出進行儲存的過程有點類似資料寫的過程。資料讀取的過程,如果有訂閱關係就可以確定推送目標,遷移訂閱關係資料到 Session,不會影響整個叢集服務資料的狀態,並且 Client 節點連線新的 Session 時,也會回放所有訂閱關係,Session 就可以無狀態的無限擴充套件。

其實這個讀寫叢集分離的概念,在 Eureka2.0 的設計文件上也有所體現,通常讀取的需求比寫入的需求要大很多,所以讀叢集用於支撐大量訂閱讀請求,寫叢集重點負責儲存。

image.png

高可用

資料回放

資料備份,採用逐級快取資料回放模式,Client 在本地記憶體裡快取著需要訂閱和釋出的服務資料,在連線上 Session 後會回放訂閱和釋出資料給 Session,最終再發布到 Data。

另一方面,Session 儲存著客戶端釋出的所有 Pub 資料,定期通過資料比對保持和 Data 一致性。

image.png

多副本

上述提到的資料回放能力,保證了資料從客戶端最終能恢復到儲存層(Data)。但是儲存層(Data)自身也需要保證資料的高可用,因為我們對儲存層(Data)還做了資料多副本的備份機制。如下圖:

圖片1.png

當有 Data 節點縮容、當機發生時,備份節點可以立即通過備份資料生效成為主節點,對外提供服務,並且把相應的備份資料再按照新列表計算備份給新的節點 E。

圖片2.png

當有 Data 節點擴容時,新增節點進入初始化狀態,期間禁止新資料寫入,對於讀取請求會轉發到後續可用的 Data 節點獲取資料。在其他節點的備份資料按照新節點資訊同步完成後,新擴容的 Data 節點狀態變成 Working,開始對外提供服務。

圖片2.png

資料同步

資料備份、以及內部資料的傳遞,主要通過操作日誌同步方式。

持有資料一方的 Data 發起變更通知,需要同步的 Client 進行版本對比,在判斷出資料需要更新時,將拉取最新的資料操作日誌。

操作日誌儲存採用堆疊方式,獲取日誌是通過當前版本號在堆疊內所處位置,把所有版本之後的操作日誌同步過來執行。

image.png

後設資料管理

上述所有資料複製和資料同步都需要通過一致性 Hash 計算,這個計算最基本的輸入條件是當前叢集的所有 Data節點列表資訊。所以如何讓叢集的每個節點能感知叢集其他節點的狀態,成為接下來需要解決的問題。

最初,我們直接將“Data 地址列表資訊” 配置在每個節點上,但這樣不具體動態性,所以又改為通過 DRM(螞蟻內部的動態配置中心)配置,但是這樣仍需要人為維護,無法做到自動感知。

image.png

後續又想到這個叢集列表通過叢集內節點進行選舉出主節點,其他節點直接上報給主節點,主節點再進行分發,這樣主節點自身狀態成為保證這個同步成功的關鍵,否則要重新選舉,這樣就無法及時通知這個列表資訊。

image.png

最後我們決定獨立一個角色進行專職做叢集列表資訊的註冊和發現,稱為 MetaServer。Session 和 Data 每個節點都在 MetaServer 上進行註冊,並且通過長連線定期保持心跳,這樣可以明確各個叢集節點的變化,及時通知各個其他節點。

image.png

我們的優勢

目前,SOFARegistry 可以支撐如下的資料量:

  • 2000+ 應用 2.3w 服務註冊發現;
  • 單機房 Data 叢集支援百萬級 Pub 資料,千萬級 Sub 資料;
  • 高可用,叢集當機 ½ 以內節點服務自恢復;
  • 支援數百應用同時啟動釋出訂閱。

SOFARegistry 與開源同類產品的比較:

比較

SOFARegistry

Eureka 1.0

ZooKeeper

一致性

最終一致

最終一致

強一致

可用性

高可用,叢集節點可動態擴縮容,資料保持多副本

高可用

節點選舉過程整個叢集不可用,無法提供服務

可擴充套件

一致性 Hash 資料分片,理論上無限制擴充套件

資料節點相互同步方式保持一致,有上限瓶頸

資料強一致,同樣存在上限

時效性

秒級服務發現,通過連線狀態進行服務資料變更通知

採用長輪詢健康檢查方式獲取節點狀態,時效不敏感

強一致要求,多寫效率低

將來

挑戰

  • 面向現有主流微服務框架。目前主流微服務框架需要我們進行適配,比如 SpringCloud、Dubbo 等,這些框架在服務發現方面有一定的規範可以進行遵循後接入,對於適應性有所增強。此外對於雲上很多服務發現元件可以進行對接比如 Cloudmap 等。
  • 面向雲原生微服務運維。眾所周知,服務註冊中心的運維非常複雜,必須要有特定的運維釋出方式來嚴格控制步驟,否則影響是不可逆的。我們也進行了大量的運維嘗試,做了很多狀態劃分保持整個叢集釋出的順暢,後續雲原生和 k8s 接入必將對傳統的運維方式進行變革,這個過程要求我們產品自身可以微服務化、映象化才能適應容器運維的需求,並且需要考慮新的資料一致性方式。
  • 面對單元化部署。單元化其實在螞蟻內部已經大規模實踐,服務註冊中心在這裡承載的職責尤為重要,當前我們對多機房多城市等部署模式在資料結構上已經進行開放,但是對於機房間一些特定功能考慮進一步開放和完善。

Roadmap

一些新的 Feature 規劃和上述過程的開源路徑:

image.png

總結

以上就是本期分享的所有內容。當前,程式碼已開源託管在 GitHub 上,歡迎關注,同時也歡迎業界愛好者共同創造更好的 SOFARegistry。

螞蟻金服開源服務註冊中心 SOFARegistry | SOFA 開源一週年獻禮

SOFA 開源社群,感謝有你!

公眾號:金融級分散式架構(Antfin_SOFA)


相關文章