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

SOFARegistry 是螞蟻金服開源的具有承載海量服務注冊和訂閱能力的、高可用的服務注冊中心,在支付寶/螞蟻金服的業務發展驅動下,近十年間已經演進至第五代。
本文為《剖析 | SOFARegistry 架構》第七篇,本篇作者明不二。《剖析 | SOFARegistry 架構》系列由 SOFA 團隊和源碼愛好者們出品,項目代号:,文末包含往期系列文章。
GitHub 位址:
https://github.com/sofastack/sofa-registry概述
在前面的文章已經做過介紹,與其他注冊中心相比,SOFARegistry 主要特點在于支援海量資料、支援海量用戶端、秒級的服務上下線通知以及高可用特性。本文将從如下幾個方面來講述 SOFARegistry 的一緻性方案:
- MetaServer 資料一緻性
為支援高可用特性,對于 MetaServer 來說,存儲了 SOFARegistry 的中繼資料,為了保障 MetaServer 叢集的一緻性,其采用了 Raft 協定來進行選舉和複制。
- SessionServer 資料一緻性
為支援海量用戶端的連接配接,SOFARegistry 在用戶端與 DataServer 之間添加了一個 SessionServer 層,用戶端與 SessionServer 連接配接,避免了用戶端與 DataServer 之間存在大量連接配接所導緻的連接配接數過多不可控的問題。用戶端通過 SessionServer 與 DataServer 連接配接的時候,Publisher 資料同時會緩存在 SessionServer 中,此時就需要解決 DataServer 與 SessionServer 之間資料一緻性的問題。
- DataServer 資料一緻性
為支援海量資料,SOFARegistry 采用了一緻性 Hash 來分片存儲 Publisher 資料,避免了單個伺服器存儲全量資料時産生的容量瓶頸問題。而在這個模型中,每個資料分片擁有多個副本,當存儲注冊數的 DataServer 進行擴容、縮容時,MetaServer 會把這個變更通知到 DataServer 和 SessionServer,資料分片會在叢集内部進行資料遷移與同步,此時就出現了 DataServer 内部資料的一緻性問題。
MetaServer 在 SOFARegistry 中,承擔着叢集中繼資料管理的角色,用來維護叢集成員清單,可以認為是 SOFARegistry 注冊中心的注冊中心。當 SessionServer 和 DataServer 需要知道叢集清單,并且需要擴縮容時,MetaServer 将會提供相應的資料。
圖1 MetaServer 内部結構
圖源自
《螞蟻金服服務注冊中心 MetaServer 功能介紹和實作剖析 | SOFARegistry 解析》因為 SOFARegistry 叢集節點清單資料并不是很多,是以不需要使用資料分片的方式在 MetaServer 中存儲。如圖 1 所示,叢集節點清單存儲在 Repository 中,上面通過 Raft 強一緻性協定對外提供節點注冊、續約、清單查詢等 Bolt 請求,進而保障叢集獲得的資料是強一緻性的。
Raft 協定
關于 Raft 協定算法,具體可以參考
The Raft Consensus Algorithm中的解釋。在 SOFA 體系中,對于 Raft 協定有
SOFAJRaft實作。下面對 Raft 協定算法的原理進行簡要介紹。
Raft 協定由三個部分組成,上司人選舉(Leader Election)、日志複制(Log Replication)、安全性(Safety)。
- 上司人選舉
通過一定的算法選舉出上司人,用于接受用戶端請求,并且把指令追加到日志中。
圖2 Raft 狀态機狀态轉換圖
Understanding the Raft consensus algorithm: an academic article summary- 日志複制
上司人接受到用戶端請求之後,把操作追加到日志中,同時與其他追随者同步消息,最終 Commit 日志,并且把結果傳回給用戶端。
圖3 複制狀态機
Raft一緻性算法筆記- 安全性
安全性保證了資料的一緻性。
基于 Raft 協定的資料一緻性保障
圖4 SOFARegistry 中的 Raft 存儲過程
如圖 4 所示,SOFARegistry 中的 Raft 協定資料存儲經曆了如上的一些流程。用戶端發起 Raft 協定調用,進行資料注冊、續約、查詢等操作時,會通過動态代理實作
ProxyHandler
類進行代理,通過
RaftClient
把資料發送給
RaftServer
,并且通過内部的狀态機
Statemachine
,最終實作資料的操作,進而保證了 MetaServer 内部的資料一緻性。
SessionServer 在 SOFARegistry 中,承擔着會話管理及連接配接的功能。同時,Subscriber 需要通過 SessionServer 來訂閱 DataServer 的服務資料,Publisher 需要通過 SessionServer 來把服務資料釋出到 DataServer 中。
在這個場景下,SessionServer 作為中間代理層,緩存從 DataServer 中擷取的資料成了必然。DataServer 的資料需要通過 SessionServer 推送到 Subscriber 中,觸發 SessionServer 推送的場景有兩個:一個是 Publisher 到 DataServer 的資料發生變化;另外一個是 Subscriber 有了新增。
而在實際的場景中,Subscriber 新增的情況更多,在這種場景下,直接把 SessionServer 緩存的資料推送到 Subscriber 中即可,能夠大大減輕 SessionServer 從 DataServer 擷取資料對 DataServer 的壓力。是以,這也進一步确認了在 SessionServer 緩存資料的必要性。
圖5 兩種場景的資料推送對比圖
SessionServer 與 DataServer 資料對比機制
當服務 Publisher 上下線或者斷連時,相應的資料會通過 SessionServer 注冊到 DataServer 中。此時,DataServer 的資料與 SessionServer 會出現短暫的不一緻性。為了保障這個資料的一緻性,DataServer 與 SessionServer 之間通過
推
和
拉
兩種方式實作了資料的同步。
- 資料推送模式
DataServer 在服務資料有變化時會主動通知到 SessionServer 中,此時 SessionServer 會比對兩者資料的版本号
version
,對比之後若需要更新資料,則會主動向 DataServer 擷取相應的資料。
- 資料拉取模式
SessionServer 會每隔一定的時間(預設 30s)主動向 DataServer 查詢所有
dataInfoId
的
version
資訊,若發現有版本号有變化,則會進行相應的同步操作。
- SessionServer 從 DataServer 同步資料:正常情況下,一般是 DataServer 的資料要比 SessionServer 更新,此時,當 SessionServer 發現資料版本号有變化時,會主動拉取 DataServer 的資料進行同步。注意,此時緩存的資料隻與目前 SessionServer 管理的用戶端所訂閱的服務資訊有關,并不會緩存全量的資料,而且容量也不允許。
- DataServer 從 SessionServer 同步資料:特殊情況下,DataServer 資料出現缺失,并且副本資料也出現問題之後,當 SessionServer 與 DataServer 資料進行版本号比對時,會觸發資料恢複操作,能夠把 SessionServer 記憶體中所存儲的全量資料恢複到 DataServer 中,實作了資料的反向同步與補償機制。
- 資料的緩存方式
SOFARegistry 中采用了
LoadingCache<Key, Value>
的資料結構來在 SessionServer 中緩存從 DataServer 中同步來的資料。每個 cache 中的 entry 都有過期時間,在拉取資料的時候可以設定過期時間(預設是 30s),使得 cache 定期去 DataServer 查詢目前 session 所有 sub 的 dataInfoId,對比如果 session 記錄的最近推送version(見
com.alipay.sofa.registry.server.session.store.SessionInterests#interestVersions
)比 DataServer 小,說明需要推送,然後 SessionServer 主動從 DataServer 擷取該 dataInfoId 的資料(此時會緩存到 cache 裡),推送給 client。
同時,當 DataServer 中有資料更新時,也會主動向 SessionServer 發請求使對應 entry 失效,進而促使 SessionServer 去更新失效 entry。
SessionServer 與 Subscriber 之間的資料一緻性同步
當 SessionServer 的資料發生變更時,會與 Subscriber 之間進行資料同步,把變化的
dataInfoId
資料推送到 Subscriber 中,保證用戶端本地所緩存的資料與 SessionServer 中的一緻。
DataServer 在 SOFARegistry 中,承擔着核心的資料存儲功能。資料按 dataInfoId 進行一緻性 Hash 分片存儲,支援多副本備份,保證資料高可用。這一層可随服務資料量的規模的增長而擴容。
如果 DataServer 當機,MetaServer 能感覺,并通知所有 DataServer 和 SessionServer,資料分片可 failover 到其他副本,同時 DataServer 叢集内部會進行分片資料的遷移。
DataServer 請求接收過程
在講解一緻性之前,先講一下 DataServer 的啟動之後關于資料同步方面做了哪些事情。DataServer 啟動之時,會啟動一個資料同步 Bolt 服務 openDataSyncServer ,進行相應的 DataServer 資料同步處理。
啟動 DataSyncServer 時,注冊了如下幾個 handler 用于處理 bolt 請求 :
圖5 DayaSyncServer 注冊的 Handler
- getDataHandler
該 Handler 主要用于資料的擷取,當一個請求過來時,會通過請求中的 DataCenter 和 DataInfoId 擷取目前 DataServer 節點存儲的相應資料。
- publishDataProcessor unPublishDataHandler
當有資料釋出者 publisher 上下線時,會分别觸發 publishDataProcessor 或 unPublishDataHandler ,Handler 會往 dataChangeEventCenter 中添加一個資料變更事件,用于異步地通知事件變更中心資料的變更。事件變更中心收到該事件之後,會往隊列中加入事件。此時 dataChangeEventCenter 會根據不同的事件類型異步地對上下線資料進行相應的處理。
與此同時,DataChangeHandler 會把這個事件變更資訊通過 ChangeNotifier 對外釋出,通知其他節點進行資料同步。
- notifyFetchDatumHandler
這是一個資料拉取請求,當該 Handler 被觸發時,通知目前 DataServer 節點進行版本号對比,若請求中資料的版本号高于目前節點緩存中的版本号,則會進行資料同步操作,保證資料是最新的。
- notifyOnlineHandler
這是一個 DataServer 上線通知請求 Handler,當其他節點上線時,會觸發該 Handler,進而目前節點在緩存中存儲新增的節點資訊。用于管理節點狀态,究竟是 INITIAL 還是 WORKING 。
- syncDataHandler
節點間資料同步 Handler,該 Handler 被觸發時,會通過版本号進行比對,若目前 DataServer 所存儲資料版本号含有目前請求版本号,則會傳回所有大于目前請求資料版本号的所有資料,便于節點間進行資料同步。
- dataSyncServerConnectionHandler
連接配接管理 Handler,當其他 DataServer 節點與目前 DataServer 節點連接配接時,會觸發 connect 方法,進而在本地緩存中注冊連接配接資訊,而當其他 DataServer 節點與目前節點斷連時,則會觸發 disconnect 方法,進而删除緩存資訊,進而保證目前 DataServer 節點存儲有所有與之連接配接的 DataServer 節點。
最終一緻性
SOFARegistry 在資料存儲層面采用了類似 Eureka 的最終一緻性的過程,但是存儲内容上和 Eureka 在每個節點存儲相同内容特性不同,采用每個節點上的内容按照一緻性 Hash 資料分片來達到資料容量無限水準擴充能力。
SOFARegistry 是一個 AP 分布式系統,表明了在已有條件 P 的前提下,選擇了 A 可用性。當資料進行同步時,擷取到的資料與實際資料不一緻。但因為存儲的資訊為服務的注冊節點,盡管會有短暫的不一緻産生,但對于用戶端來說,大機率還是能從這部分資料中找到可用的節點,不會因為資料暫時的不一緻對業務系統帶來緻命性的傷害。
叢集内部資料遷移過程
SOFARegistry 的 DataServer 選擇了“一緻性 Hash分片”來存儲資料。在“一緻性 Hash分片”的基礎上,為了避免“分片資料不固定”這個問題,SOFARegistry 選擇了在 DataServer 記憶體裡以 dataInfoId 的粒度記錄記錄檔,并且在 DataServer 之間也是以 dataInfoId 的粒度去做資料同步。
圖6 DataServer 之間進行異步資料同步
資料和副本分别分布在不同的節點上,進行一緻性 Hash 分片,當時對主副本進行寫操作之後,主副本會把資料異步地更新到其他副本中,實作了叢集内部不同副本之間的資料遷移工作。
總結
在分布式系統的設計中,可用性、分區容錯性、一緻性是我們必須進行權衡的選項,CAP 理論告訴我們,這三者中隻能同時滿足兩個的要求。在設計分布式系統時,如何進行權衡選擇,是擺在每個系統設計者面前的一個難題。
SOFARegistry 系統分為三個叢集,分别是中繼資料叢集 MetaServer、會話叢集 SessionServer、資料叢集 DataServer。複雜的系統有多個地方需要考慮到一緻性問題,SOFARegistry 針對不同子產品的一緻性需求也采取了不同的方案。對于 MetaServer 子產品來說,采用了強一緻性的 Raft 協定來保證叢集資訊的一緻性。對于資料子產品來說,SOFARegistry 選擇了 AP 保證可用性,同時保證了最終一緻性。
SOFARegistry 的設計給了我們啟示,在設計一個多子產品的分布式系統時,可以根據不同子產品的需求選擇不同的一緻性方案,同時 CAP 三者的權衡也需要結合系統不同子產品的目标作出合理的權衡,不必拘泥。