天天看點

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

SOFAStack (Scalable Open Financial  Architecture Stack) 是螞蟻金服自主研發的金融級分布式架構,包含了建構金融級雲原生架構所需的各個元件,是在金融場景裡錘煉出來的最佳實踐。
螞蟻金服服務注冊中心資料分片和同步方案詳解 | SOFARegistry 解析

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

本文為《剖析 | SOFARegistry 架構》第四篇,本篇作者明不二。《剖析 | SOFARegistry 架構》系列由 SOFA 團隊和源碼愛好者們出品,項目代号:,文末包含往期系列文章。

GitHub 位址:

https://github.com/sofastack/sofa-registry

概述

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

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

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

DataServer 總體架構

在大部分的服務注冊中心系統中,每台伺服器都存儲着全量的服務注冊資料,伺服器之間通過一緻性協定(paxos、Raft 等)實作資料的複制,或者采用隻保障最終一緻性的算法,來實作異步資料複制。這樣的設計對于一般業務規模的系統來說沒有問題,而當應用于有着海量服務的龐大的業務系統來說,就會遇到性能瓶頸。

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

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

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

螞蟻金服服務注冊中心資料分片和同步方案詳解 | 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 來處理各類消息。

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

圖2 DataServerBootstrap 中的 start 方法

這幾個 Server 的作用如下:

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

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

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

圖 3 各 Handler 作用

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

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

圖 4 DataServer 各項啟動任務

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

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

圖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。

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

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

圖 6 傳統 Hash 分片算法

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

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

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

圖 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 算法之後落在環上的位置。每個資料經過順時針,找尋最近的一個節點,作為資料存儲的節點。

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

圖 8 一緻性 Hash 算法

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

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

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

一緻性 Hash 算法特點

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

  • 當節點總量較少時,可以虛拟多個虛拟節點(如圖 10,實際中可能會交叉排布,在這裡友善描述則放在一起),當虛拟節點足夠多時,可以保障資料在真實節點上面能夠均勻分散分布,這是一緻性 Hash 算法的優點;
  • 采用一緻性 Hash 之後,資料在節點環中的分布範圍不固定。當節點動态擴縮容之後,部分資料要重新分布,在資料同步時會帶來一定的問題;
螞蟻金服服務注冊中心資料分片和同步方案詳解 | SOFARegistry 解析

圖 10 虛拟節點排布

SOFARegistry 的一緻性 Hash 代碼實作

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

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

圖 11 SOFARegistry 的一緻性 Hash 類圖

在該類中,SIGN 為 ID 的分隔符,numberOfReplicas 則是每個節點的虛拟節點數,realNodes 為節點清單,hashFunction 為采用的 Hash 算法,circle 為預分片機制中的 Hash 環。

ConsistentHash 預設采用了 MD5 摘要算法來進行 hash,同時構造函數支援 hash 函數定制化,使用者可以定制自己的 Hash 算法。同時,該類中 circle 的實作為 TreeMap,巧妙地使用了 TreeMap 的 tailMap() 方法來實作一緻性 Hash 的節點查找能力,資料最近的節點 hash 值計算代碼如圖 12 所示:

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

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

預分片機制

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

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

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

目前業界主要代表項目如 Dynamo、Casandra、Tair、Codis、Redis

cluster 等,都采用了預分片機制來實作這個不會變化的層。

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

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

圖 13 預分片機制

SOFARegistry 的分片選擇

SOFARegistry 為了實作服務注冊資料的分布式存儲,采用了基于一緻性 Hash 的資料分片。而由于曆史原因,為了實作資料在節點間的同步,則采用了在 DataServer 之間以 dataInfoId 為粒度進行資料同步。

節點分片

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

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

  1. 初始化目前資料節點的一緻性 Hash 值,把目前節點添加進一緻性的 Hash 環中。(圖 14)
螞蟻金服服務注冊中心資料分片和同步方案詳解 | SOFARegistry 解析

圖 14 初始化一緻性 Hash 環

  1. 擷取變更了的 DataServer 節點,這些節點在啟動 DataServer 服務的時候從 MetaServer 中擷取到的,并且通過 DataServerChangeEvent 事件中的 DataServerChangeItem 傳入。(圖 15)
螞蟻金服服務注冊中心資料分片和同步方案詳解 | SOFARegistry 解析

圖 15 擷取變更了的 DataServer 節點

  1. 擷取了目前的 DataServer 節點之後,若節點清單非空,則周遊每個節點,建立目前節點與其餘資料節點之間的連接配接,同時删除本地維護的不在節點清單中的節點資料。同時,若目前節點是 DataCenter 節點,則觸發 LocalDataServerChangeEvent 事件。

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

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

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

圖 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 系列閱讀

歡迎

閱讀原文

公衆号:金融級分布式架構(Antfin_SOFA)

繼續閱讀