1. 程式人生 > 程式設計 >螞蟻金服服務註冊中心資料分片和同步方案詳解 | SOFARegistry 解析

螞蟻金服服務註冊中心資料分片和同步方案詳解 | SOFARegistry 解析

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

SOFA:RegistryLab 資料分片和同步方案

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

本文為《剖析 | SOFARegistry 框架》第四篇,本篇作者明不二。《剖析 | SOFARegistry 框架》系列由 SOFA 團隊和原始碼愛好者們出品,專案代號:SOFA:RegistryLab/,文末包含往期系列文章。

GitHub 地址:github.com/sofastack/s…

概述

在前面的章節中我們已經提到,SOFARegistry 與其他服務發現領域的產品相比,最大的不同點在於支援海量資料。本章即將講述 SOFARegistry 在支撐海量資料上的一些特性。

本文將從如下幾個方面進行講解:

  • DataServer 總體架構:對 SOFARegistry 中支援海量資料的總體架構做一個簡述,講解資料分片和同步方案中所涉及到的關鍵技術點;
  • DataServer 啟動:講解 DataServer 啟動的服務,從而為接下來更直觀地理解資料分片、資料同步的觸發時機以及觸發方式等做一個鋪墊;
  • 資料分片:講解 SOFARegistry 中採用的一致性 Hash 演演算法進行資料分片的緣由以及具體實現方法;
  • 資料同步方案:講解 SOFARegistry 採用的資料同步方案;

DataServer 總體架構

在大部分的服務註冊中心繫統中,每臺伺服器都儲存著全量的服務註冊資料,伺服器之間通過一致性協議(paxos、Raft 等)實現資料的複製,或者採用只保障最終一致性的演演算法,來實現非同步資料複製。這樣的設計對於一般業務規模的系統來說沒有問題,而當應用於有著海量服務的龐大的業務系統來說,就會遇到效能瓶頸。

為解決這一問題,SOFARegistry 採用了資料分片的方法。全量服務註冊資料不再儲存在單機裡,而是分佈於每個節點中,每臺伺服器儲存一定量的服務註冊資料,同時進行多副本備份,從理論上實現了服務無限擴容,且實現了高可用,最終達到支撐海量資料的目的。

在各種資料分片演演算法中,SOFARegistry 採用了業界主流的一致性 Hash 演演算法做資料分片,當節點動態擴縮容時,資料仍能均勻分佈,維持資料的平衡。

在資料同步時,沒有采用與 Dynamo、Casandra、Tair、Codis、Redis cluster 等專案中類似的預分片機制,而是在 DataServer 記憶體裡以 dataInfoId 為粒度進行操作日誌記錄,這種實現方式在某種程度上也實現了“預分片”,從而保障了資料同步的有效性。

圖 1 SOFARegistry 總體架構圖

圖 1 SOFARegistry 總體架構圖

DataServer 啟動

啟動入口

DataServer 模組的各個 bean 在 JavaConfig 中統一配置,JavaConfig 類為 DataServerBeanConfiguration, 啟動入口類為 DataServerInitializer,該類不由 JavaConfig 管理配置,而是繼承了 SmartLifecycle 介面,在啟動時由 Spring 框架呼叫其 start 方法。

該方法中呼叫了 DataServerBootstrap#start 方法(圖 2),用於啟動一系列的初始化服務。

從程式碼中可以看出,DataServer 服務在啟動時,會啟動 DataServer、DataSyncServer、HttpServer 三個 bolt 服務。在啟動這些 Server 之時,DataServer 註冊了一系列 Handler 來處理各類訊息。

圖2 DataServerBootstrap 中的 start 方法

圖2 DataServerBootstrap 中的 start 方法

這幾個 Server 的作用如下:

  • DataServer:資料服務,獲取資料的推送,服務上下線通知等;
  • DataSyncServer:資料同步服務;
  • HttpServer:提供一系列 REST 介面,用於 dashboard 管理、資料查詢等;

各 Handler 具體作用如圖 3 所示:

圖 3 各 Handler 作用

圖 3 各 Handler 作用

同時啟動了 RaftClient 用於保障 DataServer 節點之間的分散式一致性,啟動了各項啟動任務,具體內容如圖 4 所示:

圖 4 DataServer 各項啟動任務

圖 4 DataServer 各項啟動任務

各個服務的啟動監聽埠如圖 5 所示:

圖5 監聽埠

圖5 監聽埠

其他初始化 Bean

除上述的啟動服務之外,還有一些 bean 在模組啟動時被初始化,系統初始化時的 bean 都在 DataServerBeanConfiguration 裡面通過 JavaConfig 來註冊,主要以如下幾個配置類體現(配置類會有變更,具體內容可以參照原始碼實現):

  • DataServerBootstrapConfigConfiguration:該配置類主要作用是提供一些 DataServer 服務啟動時基本的 Bean,比如 DataServerConfig 基礎配置 Bean、DataNodeStatus 節點狀態 Bean、DatumCache 快取 Bean 等;

  • LogTaskConfigConfiguration:該配置類主要用於提供一些日誌處理相關的 Bean;

  • SessionRemotingConfiguration:該配置類主要作用是提供一些與 SessionServer 相互通訊的 Bean,以及連線過程中的一些請求處理 Bean。比如 BoltExchange、JerseyExchange 等用於啟動服務的 Bean,還有節點上下線、資料釋出等的 Bean,為關鍵配置類;

  • DataServerNotifyBeanConfiguration:該配置類中配置的 Bean 主要用於進行事件通知,如用於處理資料變更的 DataChangeHandler 等;

  • DataServerSyncBeanConfiguration:該配置類中配置的 Bean 主要用於資料同步操作;

  • DataServerEventBeanConfiguration:該配置類中配置的 Bean 主要用於處理與資料節點相關的事件,如事件中心 EventCenter、資料變化事件中心 DataChangeEventCenter 等;

  • DataServerRemotingBeanConfiguration:該配置類中配置的 Bean 主要用於 DataServer 的連線管理;

  • ResourceConfiguration:該配置類中配置的 Bean 主要用於提供一些 Rest 介面資源;

  • AfterWorkingProcessConfiguration:該配置類中配置一些後處理 Handler Bean,用於處理一些業務邏輯結束後的後處理動作;

  • ExecutorConfiguration:該配置類主要配置一些執行緒池 Bean,用於執行不同的任務;

資料分片講解

資料分片機制是 SOFARegistry 支撐海量資料的核心所在,DataServer 負責儲存具體的服務資料,資料按照 dataInfoId 進行一致性 Hash 分片儲存,支援多副本備份,保證資料的高可用。

(對一致性 Hash 演演算法感興趣想深入瞭解的同學可以閱讀該演演算法的提出者 Karger 及其合作者的原始論文:Consistent hashing and random trees: distributed caching protocols for relieving hot spots on the World Wide Web。)

在講解 SOFARegistry 的資料分片之前,我們先看下最簡單的傳統資料分片 Hash 演演算法。

傳統資料分片 Hash 演演算法

在傳統的資料分片演演算法中,先對每個節點的 ID 進行 1 到 K 的標號,然後再對每個要儲存到節點上的資料使用 Hash 演演算法,計算之後的值對 K 取模,所得結果就是要落在的節點 ID。

該演演算法簡單且常用,很多場景中都使用該演演算法進行資料分片。

圖 6 傳統 Hash 分片演演算法
圖 6 傳統 Hash 分片演演算法

在這種演演算法下,當某個節點下線(如圖 6 中的 Node 2),該節點之後的所有節點需要重新標號。所有資料要重新求 Hash 值取模,再重新儲存到相應節點中。(圖 7)

在海量資料場景下,該方式將會帶來很大的效能開銷。

圖 7 傳統 Hash 分片演演算法,某個節點下線後將影響全域性資料分佈

圖 7 傳統 Hash 分片演演算法,某個節點下線後將影響全域性資料分佈

傳統一致性 Hash 進行資料分片

為了使服務節點上下線不會影響到全域性資料的分佈,在實際的生產環境中,很多系統使用的是一致性 Hash 演演算法進行資料分片。業界使用一致性 Hash 的代表專案有 Memcached、Twemproxy 等。

資料範圍

一致性 Hash 演演算法採用了 $$2^{32}$$ 個桶來儲存所有的 Hash 值,0 ~ $$2^{32}-1$$ 作為取值範圍,並且形成一個環。

資料分片原則

在圖 8 中,NodeA#1、NodeB#1、NodeC#1 分別為 A、B、C 三個節點的 ID 經過一致性 Hash 演演算法的計算後落在環上的位置。

三角形為不同的資料經過一致性 Hash 演演算法之後落在環上的位置。每個資料經過順時針,找尋最近的一個節點,作為資料儲存的節點。

圖 8 一致性 Hash 演演算法

圖 8 一致性 Hash 演演算法

從圖 8 中不難想到,當有節點上下線時,僅僅影響到上下線節點與該節點逆時針方向最近的一個節點之間的資料分佈。此時,只需要對掉落到這個區間內的資料重排即可。(如圖 9)

圖 9 一致性 Hash 演演算法中 NodeB#1 下線

圖 9 一致性 Hash 演演算法中 NodeB#1 下線

一致性 Hash 演演算法特點

該演演算法中,每個節點的 ID 需要通過一致性 Hash 演演算法計算後對映到圓環上,以此帶來了一致性 Hash 演演算法的兩個特點:

  • 當節點總量較少時,可以虛擬多個虛擬節點(如圖 10,實際中可能會交叉排布,在這裡方便描述則放在一起),當虛擬節點足夠多時,可以保障資料在真實節點上面能夠均勻分散分佈,這是一致性 Hash 演演算法的優點;
  • 採用一致性 Hash 之後,資料在節點環中的分佈範圍不固定。當節點動態擴縮容之後,部分資料要重新分佈,在資料同步時會帶來一定的問題;

圖 10 虛擬節點排布

圖 10 虛擬節點排布

SOFARegistry 的一致性 Hash 程式碼實現

在 SOFARegistry 中,由 ConsistentHash 類來實現一致性 Hash 類圖,如圖 11 所示:

圖 11 SOFARegistry 的一致性 Hash 類圖

圖 11 SOFARegistry 的一致性 Hash 類圖

在該類中,SIGN 為 ID 的分隔符,numberOfReplicas 則是每個節點的虛擬節點數,realNodes 為節點列表,hashFunction 為採用的 Hash 演演算法,circle 為預分片機制中的 Hash 環。

ConsistentHash 預設採用了 MD5 摘要演演算法來進行 hash,同時建構函式支援 hash 函式定製化,使用者可以定製自己的 Hash 演演算法。同時,該類中 circle 的實現為 TreeMap,巧妙地使用了 TreeMap 的 tailMap() 方法來實現一致性 Hash 的節點查詢能力,資料最近的節點 hash 值計算程式碼如圖 12 所示:

圖 12 資料最近節點 hash 值計算方法

圖 12 資料最近節點 hash 值計算方法

預分片機制

傳統的一致性 Hash 演演算法有資料分佈範圍不固定的特性,該特性使得服務註冊資料在伺服器節點宕機、下線、擴容之後,需要重新儲存排布,這為資料的同步帶來了困難。大多數的資料同步操作是利用操作日誌記錄的內容來進行的,傳統的一致性 Hash 演演算法中,資料的操作日誌是以節點分片來劃分的,節點變化導致資料分佈範圍的變化。

在計算機領域,大多數難題都可以通過增加一箇中間層來解決,那麼對於資料分佈範圍不固定所導致的資料同步難題,也可以通過同樣的思路來解決。

這裡的問題在於,當節點下線後,若再以當前存活節點 ID 一致性 Hash 值去同步資料,就會導致已失效節點的資料操作日誌無法獲取到,既然資料儲存在會變化的地方無法進行資料同步,那麼如果把資料儲存在不會變化的地方是否就能保證資料同步的可行性呢?答案是肯定的,這個中間層就是預分片層,通過把資料與預分片這個不會變化的層相互對應就能解決這個資料同步的難題。

目前業界主要代表專案如 Dynamo、Casandra、Tair、Codis、Redis cluster 等,都採用了預分片機制來實現這個不會變化的層。

事先將資料儲存範圍等分為 N 個 slot 槽位,資料直接與 slot 相對應,資料的操作日誌與相應的 solt 對應,slot 的數目不會因為節點的上下線而產生變化,由此保證了資料同步的可行性。除此之外,還需要引進“路由表”的概念,如圖 13,“路由表”負責存放每個節點和 N 個 slot 的對映關係,並保證儘量把所有 slot 均勻地分配給每個節點。這樣,當節點上下線時,只需要修改路由表內容即可。保持 slot 不變,即保證了彈性擴縮容,也大大降低了資料同步的難度。

圖 13 預分片機制

圖 13 預分片機制

SOFARegistry 的分片選擇

SOFARegistry 為了實現服務註冊資料的分散式儲存,採用了基於一致性 Hash 的資料分片。而由於歷史原因,為了實現資料在節點間的同步,則採用了在 DataServer 之間以 dataInfoId 為粒度進行資料同步。

節點分片

當 DataServer 節點初始化成功後,會啟動任務自動去連線 MetaServer。該任務會往事件中心 EventCenter 註冊一個 DataServerChangeEvent 事件,該事件註冊後會被觸發,之後將對新增節點計算 Hash 值,同時進行納管分片。

DataServerChangeEvent 事件被觸發後,由 DataServerChangeEventHandler 來進行相應的處理,分別分為如下一些步驟:

  1. 初始化當前資料節點的一致性 Hash 值,把當前節點新增進一致性的 Hash 環中。(圖 14)

圖 14 初始化一致性 Hash 環

圖 14 初始化一致性 Hash 環

  1. 獲取變更了的 DataServer 節點,這些節點在啟動 DataServer 服務的時候從 MetaServer 中獲取到的,並且通過 DataServerChangeEvent 事件中的 DataServerChangeItem 傳入。(圖 15)

圖 15 獲取變更了的 DataServer 節點

圖 15 獲取變更了的 DataServer 節點

  1. 獲取了當前的 DataServer 節點之後,若節點列表非空,則遍歷每個節點,建立當前節點與其餘資料節點之間的連線,同時刪除本地維護的不在節點列表中的節點資料。同時,若當前節點是 DataCenter 節點,則觸發 LocalDataServerChangeEvent 事件。

至此,節點初始化以及分片入 Hash 環的工作已經完成。

資料節點相關資料,儲存在 Map 中,相關的資料結構如圖 16 所示。

圖 16 DataServer 節點一致性 Hash 儲存結構

圖 16 DataServer 節點一致性 Hash 儲存結構

資料分片

當服務上線時,會計算新增服務的 dataInfoId Hash 值,從而對該服務進行分片,最後尋找最近的一個節點,儲存到相應的節點上。

前文已經說過,DataServer 服務在啟動時添加了 publishDataProcessor 來處理相應的服務釋出者資料釋出請求,該 publishDataProcessor 就是 PublishDataHandler。當有新的服務釋出者上線,DataServer 的 PublishDataHandler 將會被觸發。

該 Handler 首先會判斷當前節點的狀態,若是非工作狀態則返回請求失敗。若是工作狀態,則觸發資料變化事件中心 DataChangeEventCenter 的 onChange 方法。

DataChangeEventQueue 中維護著一個 DataChangeEventQueue 佇列陣列,陣列中的每個元素是一個事件佇列。當上文中的 onChange 方法被觸發時,會計算該變化服務的 dataInfoId 的 Hash 值,從而進一步確定出該服務註冊資料所在的佇列編號,進而把該變化的資料封裝成一個資料變化物件,傳入到佇列中。

DataChangeEventQueue#start 方法在 DataChangeEventCenter 初始化的時候被一個新的執行緒呼叫,該方法會源源不斷地從佇列中獲取新增事件,並且進行分發。新增資料會由此新增進節點內,實現分片。

資料同步方案講解

SOFARegistry 是 Client、SessionServer、DataServer 三層架構,同時通過 MetaServer 管理 Session 和 Data 叢集,在服務註冊的過程中,資料既有層間的資料同步,也有層內的節點間同步。

層內同步 —— 資料回放

Client 端在本地記憶體內已經儲存了需要訂閱和釋出的服務資料,在連線上 Session 後會回放訂閱和釋出資料給 Session,最終再發布到 Data。同時,Session 儲存著客戶端釋出的所有 Pub 資料,定期通過資料比對保持和 Data 一致性。當資料發生變更時,持有資料一方的 Data 發起變更通知,需要同步的 SessionServer 進行版本對比,在判斷出資料需要更新時,將拉取最新的資料操作日誌。

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

層間同步 —— 多副本

為保障 Data 層資料的可用性,SOFARegistry 做了 Data 層的多副本機制。當有 Data 節點縮容、宕機發生時,備份節點可以立即通過備份資料生效成為主節點,對外提供服務,並且把相應的備份資料再按照新列表計算備份給新的節點。

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

總結

在海量服務註冊場景下,為保障 DataServer 能否無限擴容面對海量資料的業務場景,與其他服務註冊中心不同的是,SOFARegistry 採用了一致性 Hash 演演算法進行資料分片,保障了資料的可擴充套件性。同時,通過在 DataServer 記憶體裡以 dataInfoId 的粒度記錄操作日誌,並且在 DataServer 之間也是以 dataInfoId 的粒度去做資料同步,保障了資料的一致性。

SOFARegistryLab 系列閱讀