1. 程式人生 > >[從原始碼學設計]螞蟻金服SOFARegistry之儲存結構

[從原始碼學設計]螞蟻金服SOFARegistry之儲存結構

# [從原始碼學設計]螞蟻金服SOFARegistry之儲存結構 [toc] ## 0x00 摘要 SOFARegistry 是螞蟻金服開源的一個生產級、高時效、高可用的服務註冊中心。 本系列文章重點在於分析設計和架構,即利用多篇文章,從多個角度反推總結 DataServer 或者 SOFARegistry 的實現機制和架構思路,讓大家藉以學習阿里如何設計。 本文為第六篇,介紹SOFARegistry的儲存結構,本文與業務聯絡密切。 ## 0x01 業務範疇 首先,我們從 Data Server 角度出發,看看本身可能涉及的儲存結構。 哪些需要儲存。 - 本身伺服器狀態; - 其他伺服器節點狀態,比如其他DataServer,SessionServer,MetaServer; - 註冊的服務狀態; 所以我們得到如下問題需要思考: - 問題:DataServer如何知道/儲存其他 DataServer? - 問題:考慮 其他DataServer 需要儲存什麼:ip,埠,狀態,如何hash,裡面儲存的資料怎麼對應hash? - 問題:幾個DataServer都從MetaServer獲取資料變化,互相傳送同步訊息,怎麼處理? - 問題:與其他 Data Server 怎麼合作,新加入一個DataServer,資料需要切換? - 問題:為什麼要把 DataServerNodeFactory 和 DataServerCache 分開。因為 Node 的結構不同導致的? - 問題:DataServerCache內部成員分成幾類模組?需要儲存哪些資訊?為什麼這樣儲存? 可能有些問題脫離了本文研究範疇,但是我們也一起羅列在這裡。 ## 1.1 快取 時間和空間之間的平衡關係可以說是計算機系統中最為本質的關係之一。 時間和空間這一對矛盾關係在推薦系統中的典型表現,主要體現在對快取的使用上。 快取通常用來儲存一些計算代價較高以及相對靜態變化較少的資料,常常在生產者和消費者之間起到緩衝的作用,使得二者可以解耦,各自非同步進行。 利用快取來解耦系統,帶來效能上的提升以及開發的便利,是在系統架構設計中需要掌握的一種通用的思路。 ## 1.2 DataServer 分片機制 在大部分的服務註冊中心繫統中,每臺伺服器都儲存著全量的服務註冊資料,伺服器之間通過一致性協議(paxos、Raft 等)實現資料的複製,或者採用只保障最終一致性的演算法,來實現非同步資料複製。這樣的設計對於一般業務規模的系統來說沒有問題,而當應用於有著海量服務的龐大的業務系統來說,就會遇到效能瓶頸
。 為解決這一問題,SOFARegistry 採用了資料分片的方法。全量服務註冊資料不再儲存在單機裡,而是分佈於每個節點中,每臺伺服器儲存一定量的服務註冊資料,同時進行多副本備份,從理論上實現了服務無限擴容,且實現了高可用,最終達到支撐海量資料的目的。 在各種資料分片演算法中,SOFARegistry 採用了業界主流的一致性 Hash 演算法做資料分片,當節點動態擴縮容時,資料仍能均勻分佈,維持資料的平衡。 在資料同步時,沒有采用與 Dynamo、Casandra、Tair、Codis、Redis cluster 等專案中類似的預分片機制,而是在 DataServer 記憶體裡以 dataInfoId 為粒度進行操作日誌記錄,這種實現方式在某種程度上也實現了“預分片”,從而保障了資料同步的有效性
。 ## 1.3 服務模型 為了更好的說明資料型別,我們只能從SOFA部落格中大段摘取文字。 ### 1.3.1 服務釋出模型(PublisherRegister) - dataInfoId:服務唯一標識,由``<分組 group>`和`<租戶 instanceId>`構成,例如在 SOFARPC 的場景下,一個 dataInfoId 通常是 `com.alipay.sofa.rpc.example.HelloService#@#SOFA#@#00001`,其中SOFA 是 group 名稱,00001 是租戶 id。group 和 instance 主要是方便對服務資料做邏輯上的切分,使不同 group 和 instance 的服務資料在邏輯上完全獨立。模型裡有 group 和 instanceId 欄位,但這裡不額外列出來,讀者只要理解 dataInfoId 的含義即可。 - zone:是一種單元化架構下的概念,代表一個機房內的邏輯單元,通常一個物理機房(Datacenter)包含多個邏輯單元(zone)。在服務發現場景下,釋出服務時需指定邏輯單元(zone),而訂閱服務者可以訂閱邏輯單元(zone)維度的服務資料,也可以訂閱物理機房(datacenter)維度的服務資料,即訂閱該 datacenter 下的所有 zone 的服務資料。 - dataList:服務註冊資料,通常包含“協議”、“地址”和“額外的配置引數”,例如 SOFARPC 所釋出的資料類似`bolt://192.168.1.100:8080?timeout=2000`”。這裡使用 dataList,表示一個 PublisherRegister 可以允許同時釋出多個服務資料(但是通常只會釋出一個)。 ### 1.3.2 服務訂閱模型(SubscriberRegister) - dataInfoId:服務唯一標識,上面已經解釋過了。 - scope: 訂閱維度,共有 3 種訂閱維度:zone、dataCenter 和 global。zone 和 datacenter 的意義,在上述有關“zone”的介紹裡已經解釋。global 維度涉及到機房間資料同步的特性,目前暫未開源。 關於“zone”和“scope”的概念理解,這裡再舉個例子。如下圖所示,物理機房內有 ZoneA 和 ZoneB 兩個單元,PublisherA 處於 ZoneA 裡,所以釋出服務時指定了 zone=ZoneA,PublisherB 處於 ZoneB 裡,所以釋出服務時指定了 zone=ZoneB;此時 Subscriber 訂閱時指定了 scope=datacenter 級別,因此它可以獲取到 PublisherA 和 PublisherB (如果 Subscriber 訂閱時指定了 scope=zone 級別,那麼它只能獲取到 PublisherA)。 ![](https://cdn.nlark.com/yuque/0/2019/png/226702/1556089042599-31e919e5-60e7-4818-9d45-c445998a9b99.png) ### 1.3.3 dataInfoId Data 層是資料伺服器叢集。Data 層通過分片儲存的方式儲存著所用應用的服務註冊資料。資料按照 dataInfoId(每一份服務資料的唯一標識)進行一致性 Hash 分片,多副本備份,保證資料的高可用
。 SOFARegistry 最早選擇了一致性雜湊分片,所以同樣遇到了資料分佈不固定帶來的資料同步難題。我們如何解決的呢?我們通過在 DataServer 記憶體中以 dataInfoId 的粒度記錄操作日誌,並且在 DataServer 之間也是以 dataInfoId 的粒度去做資料同步(一個服務就由一個 dataInfoId 唯標識)。其實這種日誌記錄的思想和虛擬桶是一致的,只是每個 datainfoId 就相當於一個 slot 了,這是一種因歷史原因而採取的妥協方案。在服務註冊中心的場景下,datainfoId 往往對應著一個釋出的服務,所以總量還是比較有限的,以螞蟻金服目前的規模,每臺 DataServer 中承載的 dataInfoId 數量也僅在數萬的級別,勉強實現了 dataInfoId 作為 slot 的資料多副本同步方案。 **最終一致性** SOFARegistry 在資料儲存層面採用了類似 Eureka 的最終一致性的過程,但是儲存內容上和 Eureka 在每個節點儲存相同內容特性不同,採用每個節點上的內容按照一致性 Hash 資料分片來達到資料容量無限水平擴充套件能力。 SOFARegistry 是一個 AP 分散式系統,表明了在已有條件 P 的前提下,選擇了 A 可用性。當資料進行同步時,獲取到的資料與實際資料不一致。但因為儲存的資訊為服務的註冊節點,儘管會有短暫的不一致產生,但對於客戶端來說,大概率還是能從這部分資料中找到可用的節點,不會因為資料暫時的不一致對業務系統帶來致命性的傷害。 **叢集內部資料遷移過程** SOFARegistry 的 DataServer 選擇了“一致性 Hash分片”來儲存資料。在“一致性 Hash分片”的基礎上,為了避免“分片資料不固定”這個問題,SOFARegistry 選擇了在 DataServer 記憶體裡以 dataInfoId 的粒度記錄操作日誌,並且在 DataServer 之間也是以 dataInfoId 的粒度去做資料同步。 ![img](https://imgconvert.csdnimg.cn/aHR0cHM6Ly9tbWJpei5xcGljLmNuL21tYml6X3BuZy9uaWJPWnBhUUt3MDhsYWxKYlg1RkJFUVEzUDE2V1pKRzh6bmljbWVvSktXanBRQnc4dHVnWXEwS1hLTG9PVmJTaEw3THJIWnlpYXhWcnl4d2daaWFyUWpSaEEvNjQw?x-oss-process=image/format,png) 圖 DataServer 之間進行非同步資料同步 資料和副本分別分佈在不同的節點上,進行一致性 Hash 分片,當時對主副本進行寫操作之後,主副本會把資料非同步地更新到其他副本中,實現了叢集內部不同副本之間的資料遷移工作。 ### 1.3.4 版本號 為了確定服務釋出資料的變更,SOFA對於一個服務不僅定義了服務 ID,還對一個服務 ID 定義了對應的版本資訊。 服務釋出資料變更主動通知到 Session 時,Session 對服務 ID 版本變更比較,高版本覆蓋低版本資料,然後進行推送。 因為有了服務 ID 的版本號,Session 可以定期發起版本號比較,如果Session 儲存的的服務 ID 版本號高於dataServer儲存的 ,Session再次拉取新版本資料進行推送,這樣避免了某次變更通知沒有通知到所有訂閱方的情況。 ## 0x02 基本概念 首先,我們講講一些基本概念。 ### 2.1 物理機房DataCenter DataCenter代表一個物理機房。一個數據中心包括多個DataNode,這些DataNode就是同機房資料節點。 ```java nodeList.add(new DataNode(new URL("192.168.0.1", 9632), "DefaultDataCenter")); nodeList.add(new DataNode(new URL("192.168.0.2", 9632), "DefaultDataCenter")); nodeList.add(new DataNode(new URL("192.168.0.3", 9632), "DefaultDataCenter")); ``` ### 2.2 Server節點DataNode DataNode是Server節點,可以代表任意型別的Server,無論是meta,data,session。 ```java public class DataNode implements Node, HashNode { private final URL nodeUrl; private final String nodeName; private final String dataCenter; private String regionId; private NodeStatus nodeStatus; private long registrationTimestamp; } ``` ### 2.3 資料節點DataServerNode 這是 Data Server 概念。 為什麼要有DataNode和DataServerNode兩個類似的資料結構型別。 原來這是分屬於不同的包,或者模組。 - DataNode 是從 MetaServer 傳來的,被 DataServerCache 使用,而 DataServerCache 放在 cache 包。 - DataServerNode 是 DataServer 本身自己依據資訊構建的,被DataServerNodeFactory 使用,放在 remoting 包。 具體定義如下: ```java public class DataServerNode implements HashNode { private String ip; private String dataCenter; private Connection connection; } ``` ### 2.4 服務Publisher Publisher 是服務概念,具體如下。 ```java public class Publisher extends BaseInfo { priv