文|宋國磊(GitHub ID:glmapper )
SOFAStack Committer、華米科技高階研發工程師
負責華米賬號系統、框架治理方向的開發
本文 3024 字 閱讀 10 分鐘
|前言|
本文主要圍繞 SOFARegistry 的資料同步模組進行了原始碼的解析。其中,對於註冊中心的概念以及 SOFARegistry 的基礎架構將不再做詳細的闡述,有興趣的小夥伴在《海量資料下的註冊中心 - SOFARegistry 架構介紹》[1]一文中獲取相關介紹。
本文主要的思路大致分為以下 2 個部分:
- 第一部分,藉助 SOFARegistry 中的角色分類來說明哪些角色之間會進行資料同步;
- 第二部分,對資料同步的具體實現進行解析。
PART. 1——SOFARegistry 的角色分類
如上圖所示,SOFARegistry 包含以下 4 個角色:
Client
提供應用接入服務註冊中心的基本 API 能力,應用系統通過依賴客戶端 JAR 包,通過程式設計方式呼叫服務註冊中心的服務訂閱和服務釋出能力。
SessionServer
會話伺服器,負責接受 Client 的服務釋出和服務訂閱請求,並作為一箇中間層將寫操作轉發 DataServer 層。SessionServer 這一層可隨業務機器數的規模的增長而擴容。
DataServer
資料伺服器,負責儲存具體的服務資料,資料按 dataInfoId 進行一致性 Hash 分片儲存,支援多副本備份,保證資料高可用。這一層可隨服務資料量的規模的增長而擴容。\
MetaServer
後設資料伺服器,負責維護叢集 SessionServer 和 DataServer 的一致列表,作為 SOFARegistry 叢集內部的地址發現服務,在 SessionServer 或 DataServer 節點變更時可以通知到整個叢集。
在這 4 個角色中,MetaServer 作為後設資料伺服器本身不處理實際的業務資料,僅負責維護叢集 SessionServer 和 DataServer 的一致列表,不涉及資料同步問題。
Client 與 SessionServer 之間的核心動作是訂閱和釋出,從廣義上來說,屬於使用者側客戶端與 SOFARegistry 叢集的資料同步,詳情可以見:
https://github.com/sofastack/sofa-registry/issues/195,因此不在本文討論範疇之內。
SessionServer 作為會話服務,它主要解決海量客戶端連線問題,其次是快取客戶端釋出的所有 pub 資料。Session 本身不持久化服務資料,而是將資料轉寫到 DataServer。DataServer 儲存服務資料是按照 dataInfoId 進行一致性 Hash 分片儲存的,支援多副本備份,保證資料高可用。
從 SessionServer 和 DataServer 的功能分析中可以得出:
SessionServer 快取的服務資料需要與 DataServer 儲存的服務資料保持一致。
DataServer 支援多副本來保證高可用,因此 DataServer 多副本之間需要保持服務資料一致。
SOFARegistry 中,針對上述兩個對於資料的一致性保證就是通過資料同步機制來實現的。
PART. 2——資料同步的具體實現
瞭解了 SOFARegistry 的角色分類之後,我們開始深入資料同步具體的實現細節。我將主要圍繞 SessionServer 和 DataServer 之間的資料同步,以及 DataServer 多副本之間的資料同步兩塊內容來展開。
「SessionServer 和 DataServer 之間的資料同步」
SessionServer 和 DataServer 之間的資料同步,是基於如下推拉結合的機制:
推: DataServer 在資料有變化時,會主動通知 SessionServer,SessionServer 檢查確認需要更新 (對比 version) 後主動向 DataServer 獲取資料。
拉: 除了上述的 DataServer 主動推以外,SessionServer 每隔一定的時間間隔,會主動向 DataServer 查詢所有 dataInfoId 的 version 資訊,然後再與 SessionServer 記憶體的 version 作比較。若發現 version 有變化,則主動向 DataServer 獲取資料。這個“拉”的邏輯,主要是對“推”的一個補充,若在“推”的過程中出現錯漏的情況可以在這個時候及時彌補。
推和拉兩種模式檢查的 version 會有一些差異,這部分內容詳見下面“推模式下的資料同步”和“拉模式下的資料同步”中的具體介紹。
「推模式下的資料同步流程」
推模式是通過 SyncingWatchDog 這個守護執行緒不斷 loop 執行來實現資料變更檢查和通知發起的:
根據 slot 分組彙總資料版本。data 與每個 session 的連線都對應一個 SyncSessionTask,SyncSessionTask 負責執行同步資料的任務,核心同步邏輯在:
com.alipay.sofa.registry.server.data.slot.SlotDiffSyncer#sync 方法中完成,大致流程如下時序圖所示:
上圖圈紅部分的邏輯第四步,根據 dataInfoId diff 更新 data 記憶體資料,可以看到這裡僅處理了被移除的 dataInfoId,對於新增和更新的沒有做任務處理,而是通過後面的第 5-7 步來完成。這麼做的主要原因在於避免產生空推送而導致一些危險情況發生。
第 5 步中,比較的是所有變更 dataInfoId 的 pub version,具體比較邏輯可以參考後面 diffPublisher 小節中的介紹。
「資料變更的事件通知處理」
資料變更事件會被收集在 DataChangeEventCenter 的 dataCenter2Changes 快取中,然後由一個守護執行緒 ChangeMerger 負責從 dataCenter2Changes 快取中不斷的讀取,這些被撈到的事件源會被組裝成 ChangeNotifier 任務,提交給一個單獨的執行緒池 (notifyExecutor) 處理,整個過程全部是非同步的。
「拉模式下的資料同步流程」
拉模式下,由 SessionServer 負責發起,
com.alipay.sofa.registry.server.session.registry.SessionRegistry.VersionWatchDog
預設情況下每 5 秒掃描一次版本資料,如果版本有發生變更,則主動進行一次拉取,流程大致如下:
需要注意的是,拉模式對推送流程的補充,這裡的 version 是每個 sub 的 lastPushedVersion, 而推模式的 version 是 pub 的資料的 version。關於 lastPushedVersion 的獲取可以參考:
com.alipay.sofa.registry.server.session.store.SessionInterests#selectSubscribers
「DataServer 多副本之間的資料同步」
主要是 slot 對應的 data 的 follower 定期和 leader 進行資料同步,其同步邏輯與 SessionServer 和 DataServer 之間的資料同步邏輯差異不大;發起方式也是一樣的;data 判斷如果當前節點不是 leader,就會進行與 leader 之間的資料同步。
「增量同步 diff 計算邏輯分析」
不管是 SessionServer 和 DataServer 之間的同步,還是 DataServer 多副本之間的同步,都是基於增量 diff 同步的,不會一次性同步全量資料。
本節對增量同步 diff 計算邏輯進行簡單分析,核心程式碼在:
com.alipay.sofa.registry.common.model.slot.DataSlotDiffUtils (建議閱讀這部分程式碼時直接結合程式碼中的測試用例來看) 。
主要包括計算 digest 和 publishers 兩個。
diffDigest
用 DataSlotDiffUtils#diffDigest 方法接收兩個引數:
- targetDigestMap 可以理解為目標資料
- sourceDigestMap 可以理解為基線資料
核心計算邏輯如下程式碼分析:
那麼根據上述 diff 計算邏輯,這裡有如下幾種場景 (假設基線資料集資料中 dataInfoId 為 a 和 b) :
1. 目標資料集為空:返回 dataInfoId 為 a 和 b 兩項作為新增項;
2. 目標資料集與基線資料集相等,新增項、待更新項與待移除項均為空;
3. 目標資料集中包括 a、b、c 三個 dataInfoId,則返回 c 作為待移除項;
4. 目標資料集中包括 a 和 c 兩個 dataInfoId,則返回 c 作為待移除項,b 作為新增項
diffPublisher
diffPublisher 與 diffDigest 計算稍有不同,diffPublisher 接收三個引數,除了目標資料集和基線資料集之外,還有一個 publisherMaxNum (預設 400) ,用於限制每次處理的資料個數;這裡同樣給出核心程式碼解釋:
這裡同樣對幾種場景做一下分析 (下面指的是更新 dataInfoId 對應的 publisher,registerId 與 publisher 一一對應) :
1. 目標資料集與基線資料集相同,且資料沒有超過 publisherMaxNum,返回的待更新和待移除均為空,且沒有剩餘未處理資料;
2. 需要移除的情況:
基線中不包括目標資料集 dataInfoId 的 registerId (移除的是 registerId,不是 dataInfoId)
3. 需要更新的情況:
- 目標資料集中存在基線資料集不存在的 registerId
- 目標資料集和基線資料集存在的 registerId 的版本不同
PART. 3——總結
本文主要介紹了 SOFARegistry 中的資料同步模組。在整個過程中,我們首先從 SOFARegistry 角色分類闡述不同角色之間存在的資料同步問題,並針對其中 SessionServer 與 DataServer 之間的資料同步 和 DataServer 多副本之間的資料同步進行了展開。
在 SessionServer 與 DataServer 資料同步分析中,著重分析了推和拉兩種場景下資料同步的整體流程。最後對 SOFARegistry 中資料增加的 diff 計算邏輯進行了介紹,並結合相關核心程式碼描述了具體的場景。
整體來看,SOFARegistry 在資料同步上的處理上有如下三點可以對我們有所啟發:
1. 在一致性方面,SOFARegistry 基於 ap,滿足了最終一致性,並在實際的同步邏輯處理上,結合事件機制,基本都是非同步化完成的,這樣一來就有效弱化了資料同步對於核心流程的影響;
2. 在拉模式和資料變更通知兩個部分,內部採用了類似“生產-消費模型”,一方面是對於生產和消費邏輯的解耦,從程式碼上更獨立;另一方面通過快取或者佇列來消除生產和消費速度不同而導致的相互阻塞的問題;
3. 拉模式對推模式的補充;我們知道推模式是 server -> client,發生在資料變更時,如果出現一些異常,導致某條 server -> client 鏈路推送失敗,則會導致不同 client 持有的資料不一致的情況;拉模式的補充,讓 client 能主動去完成對於資料一致性的檢查。
【參考連結】
[1]《海量資料下的註冊中心 - SOFARegistry 架構介紹》:https://www.sofastack.tech/blog/sofa-registry-introduction/
專案原文地址:https://www.sofastack.tech/projects/sofa-registry/code-analyze/code-analyze-data-synchronization/
瞭解更多
SOFARegistry Star 一下✨:\
https://github.com/sofastack/sofa-registry
本週推薦閱讀
SOFARegistry 原始碼|資料分片之核心-路由表 SlotTable 剖析
探索 SOFARegistry(一)|基礎架構篇
MOSN 構建 Subset 優化思路分享
Nydus —下一代容器映象探索實踐