這篇文章將為大家詳細(xì)講解有關(guān)如何解析SOFARegistry,文章內(nèi)容質(zhì)量較高,因此小編分享給大家做個參考,希望大家閱讀完這篇文章后對相關(guān)知識有一定的了解。
創(chuàng)新互聯(lián)是網(wǎng)站建設(shè)技術(shù)企業(yè),為成都企業(yè)提供專業(yè)的網(wǎng)站建設(shè)、成都網(wǎng)站制作,網(wǎng)站設(shè)計,網(wǎng)站制作,網(wǎng)站改版等技術(shù)服務(wù)。擁有10年豐富建站經(jīng)驗和眾多成功案例,為您定制適合企業(yè)的網(wǎng)站。10年品質(zhì),值得信賴!
SOFAStack(Scalable Open Financial Architecture Stack) 是螞蟻金服自主研發(fā)的金融級分布式架構(gòu),包含了構(gòu)建金融級云原生架構(gòu)所需的各個組件,是在金融場景里錘煉出來的最佳實踐。
SOFARegistry 是螞蟻金服開源的具有承載海量服務(wù)注冊和訂閱能力的、高可用的服務(wù)注冊中心,在支付寶/螞蟻金服的業(yè)務(wù)發(fā)展驅(qū)動下,近十年間已經(jīng)演進(jìn)至第五代。
在前面的章節(jié)中我們已經(jīng)提到,SOFARegistry 與其他服務(wù)發(fā)現(xiàn)領(lǐng)域的產(chǎn)品相比,最大的不同點(diǎn)在于支持海量數(shù)據(jù)。本章即將講述 SOFARegistry 在支撐海量數(shù)據(jù)上的一些特性。
本文將從如下幾個方面進(jìn)行講解:
DataServer 總體架構(gòu):對 SOFARegistry 中支持海量數(shù)據(jù)的總體架構(gòu)做一個簡述,講解數(shù)據(jù)分片和同步方案中所涉及到的關(guān)鍵技術(shù)點(diǎn);
DataServer 啟動:講解 DataServer 啟動的服務(wù),從而為接下來更直觀地理解數(shù)據(jù)分片、數(shù)據(jù)同步的觸發(fā)時機(jī)以及觸發(fā)方式等做一個鋪墊;
數(shù)據(jù)分片:講解 SOFARegistry 中采用的一致性 Hash 算法進(jìn)行數(shù)據(jù)分片的緣由以及具體實現(xiàn)方法;
數(shù)據(jù)同步方案:講解 SOFARegistry 采用的數(shù)據(jù)同步方案;
在大部分的服務(wù)注冊中心系統(tǒng)中,每臺服務(wù)器都存儲著全量的服務(wù)注冊數(shù)據(jù),服務(wù)器之間通過一致性協(xié)議(paxos、Raft 等)實現(xiàn)數(shù)據(jù)的復(fù)制,或者采用只保障最終一致性的算法,來實現(xiàn)異步數(shù)據(jù)復(fù)制。這樣的設(shè)計對于一般業(yè)務(wù)規(guī)模的系統(tǒng)來說沒有問題,而當(dāng)應(yīng)用于有著海量服務(wù)的龐大的業(yè)務(wù)系統(tǒng)來說,就會遇到性能瓶頸。
為解決這一問題,SOFARegistry 采用了數(shù)據(jù)分片的方法。全量服務(wù)注冊數(shù)據(jù)不再保存在單機(jī)里,而是分布于每個節(jié)點(diǎn)中,每臺服務(wù)器保存一定量的服務(wù)注冊數(shù)據(jù),同時進(jìn)行多副本備份,從理論上實現(xiàn)了服務(wù)無限擴(kuò)容,且實現(xiàn)了高可用,最終達(dá)到支撐海量數(shù)據(jù)的目的。
在各種數(shù)據(jù)分片算法中,SOFARegistry 采用了業(yè)界主流的一致性 Hash 算法做數(shù)據(jù)分片,當(dāng)節(jié)點(diǎn)動態(tài)擴(kuò)縮容時,數(shù)據(jù)仍能均勻分布,維持?jǐn)?shù)據(jù)的平衡。
在數(shù)據(jù)同步時,沒有采用與 Dynamo、Casandra、Tair、Codis、redis cluster 等項目中類似的預(yù)分片機(jī)制,而是在 DataServer 內(nèi)存里以 dataInfoId 為粒度進(jìn)行操作日志記錄,這種實現(xiàn)方式在某種程度上也實現(xiàn)了“預(yù)分片”,從而保障了數(shù)據(jù)同步的有效性。
圖 1 SOFARegistry 總體架構(gòu)圖
DataServer 模塊的各個 bean 在 JavaConfig 中統(tǒng)一配置,JavaConfig 類為 DataServerBeanConfiguration, 啟動入口類為 DataServerInitializer,該類不由 JavaConfig 管理配置,而是繼承了 SmartLifecycle 接口,在啟動時由 Spring 框架調(diào)用其 start 方法。
該方法中調(diào)用了 DataServerBootstrap#start 方法(圖 2),用于啟動一系列的初始化服務(wù)。
從代碼中可以看出,DataServer 服務(wù)在啟動時,會啟動 DataServer、DataSyncServer、HttpServer 三個 bolt 服務(wù)。在啟動這些 Server 之時,DataServer 注冊了一系列 Handler 來處理各類消息。
圖2 DataServerBootstrap 中的 start 方法
這幾個 Server 的作用如下:
DataServer:數(shù)據(jù)服務(wù),獲取數(shù)據(jù)的推送,服務(wù)上下線通知等;
DataSyncServer:數(shù)據(jù)同步服務(wù);
HttpServer:提供一系列 REST 接口,用于 dashboard 管理、數(shù)據(jù)查詢等;
各 Handler 具體作用如圖 3 所示:
圖 3 各 Handler 作用
同時啟動了 RaftClient 用于保障 DataServer 節(jié)點(diǎn)之間的分布式一致性,啟動了各項啟動任務(wù),具體內(nèi)容如圖 4 所示:
圖 4 DataServer 各項啟動任務(wù)
各個服務(wù)的啟動監(jiān)聽端口如圖 5 所示:
圖5 監(jiān)聽端口
除上述的啟動服務(wù)之外,還有一些 bean 在模塊啟動時被初始化, 系統(tǒng)初始化時的 bean 都在 DataServerBeanConfiguration 里面通過 JavaConfig 來注冊,主要以如下幾個配置類體現(xiàn)(配置類會有變更,具體內(nèi)容可以參照源碼實現(xiàn)):
DataServerBootstrapConfigConfiguration:該配置類主要作用是提供一些 DataServer 服務(wù)啟動時基本的 Bean,比如 DataServerConfig 基礎(chǔ)配置 Bean、DataNodeStatus 節(jié)點(diǎn)狀態(tài) Bean、DatumCache 緩存 Bean 等;
LogTaskConfigConfiguration:該配置類主要用于提供一些日志處理相關(guān)的 Bean;
SessionRemotingConfiguration:該配置類主要作用是提供一些與 SessionServer 相互通信的 Bean,以及連接過程中的一些請求處理 Bean。比如 BoltExchange、JerseyExchange 等用于啟動服務(wù)的 Bean,還有節(jié)點(diǎn)上下線、數(shù)據(jù)發(fā)布等的 Bean,為關(guān)鍵配置類;
DataServerNotifyBeanConfiguration:該配置類中配置的 Bean 主要用于進(jìn)行事件通知,如用于處理數(shù)據(jù)變更的 DataChangeHandler 等;
DataServerSyncBeanConfiguration:該配置類中配置的 Bean 主要用于數(shù)據(jù)同步操作;
DataServerEventBeanConfiguration:該配置類中配置的 Bean 主要用于處理與數(shù)據(jù)節(jié)點(diǎn)相關(guān)的事件,如事件中心 EventCenter、數(shù)據(jù)變化事件中心 DataChangeEventCenter 等;
DataServerRemotingBeanConfiguration:該配置類中配置的 Bean 主要用于 DataServer 的連接管理;
ResourceConfiguration:該配置類中配置的 Bean 主要用于提供一些 Rest 接口資源;
AfterWorkingProcessConfiguration:該配置類中配置一些后處理 Handler Bean,用于處理一些業(yè)務(wù)邏輯結(jié)束后的后處理動作;
ExecutorConfiguration:該配置類主要配置一些線程池 Bean,用于執(zhí)行不同的任務(wù);
數(shù)據(jù)分片機(jī)制是 SOFARegistry 支撐海量數(shù)據(jù)的核心所在,DataServer 負(fù)責(zé)存儲具體的服務(wù)數(shù)據(jù),數(shù)據(jù)按照 dataInfoId 進(jìn)行一致性 Hash 分片存儲,支持多副本備份,保證數(shù)據(jù)的高可用。
(對一致性 Hash 算法感興趣想深入了解的同學(xué)可以閱讀該算法的提出者 Karger 及其合作者的原始論文:Consistent hashing and random trees: distributed caching protocols for relieving hot spots on the World Wide Web。)
在講解 SOFARegistry 的數(shù)據(jù)分片之前,我們先看下最簡單的傳統(tǒng)數(shù)據(jù)分片 Hash 算法。
在傳統(tǒng)的數(shù)據(jù)分片算法中,先對每個節(jié)點(diǎn)的 ID 進(jìn)行 1 到 K 的標(biāo)號,然后再對每個要存儲到節(jié)點(diǎn)上的數(shù)據(jù)使用 Hash 算法,計算之后的值對 K 取模,所得結(jié)果就是要落在的節(jié)點(diǎn) ID。
該算法簡單且常用,很多場景中都使用該算法進(jìn)行數(shù)據(jù)分片。
圖 6 傳統(tǒng) Hash 分片算法
在這種算法下,當(dāng)某個節(jié)點(diǎn)下線(如圖 6 中的 Node 2),該節(jié)點(diǎn)之后的所有節(jié)點(diǎn)需要重新標(biāo)號。所有數(shù)據(jù)要重新求 Hash 值取模,再重新存儲到相應(yīng)節(jié)點(diǎn)中。(圖 7)
在海量數(shù)據(jù)場景下,該方式將會帶來很大的性能開銷。
圖 7 傳統(tǒng) Hash 分片算法,某個節(jié)點(diǎn)下線后將影響全局?jǐn)?shù)據(jù)分布
為了使服務(wù)節(jié)點(diǎn)上下線不會影響到全局?jǐn)?shù)據(jù)的分布,在實際的生產(chǎn)環(huán)境中,很多系統(tǒng)使用的是一致性 Hash 算法進(jìn)行數(shù)據(jù)分片。業(yè)界使用一致性 Hash 的代表項目有 Memcached、Twemproxy 等。
一致性 Hash 算法采用了 $$2^{32}$$ 個桶來存儲所有的 Hash 值,0 ~ $$2^{32}-1$$ 作為取值范圍,并且形成一個環(huán)。
在圖 8 中,NodeA#1、NodeB#1、NodeC#1 分別為 A、B、C 三個節(jié)點(diǎn)的 ID 經(jīng)過一致性 Hash 算法的計算后落在環(huán)上的位置。
三角形為不同的數(shù)據(jù)經(jīng)過一致性 Hash 算法之后落在環(huán)上的位置。每個數(shù)據(jù)經(jīng)過順時針,找尋最近的一個節(jié)點(diǎn),作為數(shù)據(jù)存儲的節(jié)點(diǎn)。
圖 8 一致性 Hash 算法
從圖 8 中不難想到,當(dāng)有節(jié)點(diǎn)上下線時,僅僅影響到上下線節(jié)點(diǎn)與該節(jié)點(diǎn)逆時針方向最近的一個節(jié)點(diǎn)之間的數(shù)據(jù)分布。此時,只需要對掉落到這個區(qū)間內(nèi)的數(shù)據(jù)重排即可。(如圖 9)
圖 9 一致性 Hash 算法中 NodeB#1 下線
該算法中,每個節(jié)點(diǎn)的 ID 需要通過一致性 Hash 算法計算后映射到圓環(huán)上,以此帶來了一致性 Hash 算法的兩個特點(diǎn):
當(dāng)節(jié)點(diǎn)總量較少時,可以虛擬多個虛擬節(jié)點(diǎn)(如圖 10,實際中可能會交叉排布,在這里方便描述則放在一起),當(dāng)虛擬節(jié)點(diǎn)足夠多時,可以保障數(shù)據(jù)在真實節(jié)點(diǎn)上面能夠均勻分散分布,這是一致性 Hash 算法的優(yōu)點(diǎn);
采用一致性 Hash 之后,數(shù)據(jù)在節(jié)點(diǎn)環(huán)中的分布范圍不固定。當(dāng)節(jié)點(diǎn)動態(tài)擴(kuò)縮容之后,部分?jǐn)?shù)據(jù)要重新分布,在數(shù)據(jù)同步時會帶來一定的問題;
圖 10 虛擬節(jié)點(diǎn)排布
在 SOFARegistry 中,由 ConsistentHash 類來實現(xiàn)一致性 Hash 類圖,如圖 11 所示:
圖 11 SOFARegistry 的一致性 Hash 類圖
在該類中,SIGN 為 ID 的分隔符,numberOfReplicas 則是每個節(jié)點(diǎn)的虛擬節(jié)點(diǎn)數(shù),realNodes 為節(jié)點(diǎn)列表,hashFunction 為采用的 Hash 算法,circle 為預(yù)分片機(jī)制中的 Hash 環(huán)。
ConsistentHash 默認(rèn)采用了 MD5 摘要算法來進(jìn)行 hash,同時構(gòu)造函數(shù)支持 hash 函數(shù)定制化,用戶可以定制自己的 Hash 算法。同時,該類中 circle 的實現(xiàn)為 TreeMap,巧妙地使用了 TreeMap 的 tailMap() 方法來實現(xiàn)一致性 Hash 的節(jié)點(diǎn)查找能力,數(shù)據(jù)最近的節(jié)點(diǎn) hash 值計算代碼如圖 12 所示:
圖 12 數(shù)據(jù)最近節(jié)點(diǎn) hash 值計算方法
傳統(tǒng)的一致性 Hash 算法有數(shù)據(jù)分布范圍不固定的特性,該特性使得服務(wù)注冊數(shù)據(jù)在服務(wù)器節(jié)點(diǎn)宕機(jī)、下線、擴(kuò)容之后,需要重新存儲排布,這為數(shù)據(jù)的同步帶來了困難。大多數(shù)的數(shù)據(jù)同步操作是利用操作日志記錄的內(nèi)容來進(jìn)行的,傳統(tǒng)的一致性 Hash 算法中,數(shù)據(jù)的操作日志是以節(jié)點(diǎn)分片來劃分的,節(jié)點(diǎn)變化導(dǎo)致數(shù)據(jù)分布范圍的變化。
在計算機(jī)領(lǐng)域,大多數(shù)難題都可以通過增加一個中間層來解決,那么對于數(shù)據(jù)分布范圍不固定所導(dǎo)致的數(shù)據(jù)同步難題,也可以通過同樣的思路來解決。
這里的問題在于,當(dāng)節(jié)點(diǎn)下線后,若再以當(dāng)前存活節(jié)點(diǎn) ID 一致性 Hash 值去同步數(shù)據(jù),就會導(dǎo)致已失效節(jié)點(diǎn)的數(shù)據(jù)操作日志無法獲取到,既然數(shù)據(jù)存儲在會變化的地方無法進(jìn)行數(shù)據(jù)同步,那么如果把數(shù)據(jù)存儲在不會變化的地方是否就能保證數(shù)據(jù)同步的可行性呢?答案是肯定的,這個中間層就是預(yù)分片層,通過把數(shù)據(jù)與預(yù)分片這個不會變化的層相互對應(yīng)就能解決這個數(shù)據(jù)同步的難題。
目前業(yè)界主要代表項目如 Dynamo、Casandra、Tair、Codis、Redis cluster 等,都采用了預(yù)分片機(jī)制來實現(xiàn)這個不會變化的層。
事先將數(shù)據(jù)存儲范圍等分為 N 個 slot 槽位,數(shù)據(jù)直接與 slot 相對應(yīng),數(shù)據(jù)的操作日志與相應(yīng)的 solt 對應(yīng),slot 的數(shù)目不會因為節(jié)點(diǎn)的上下線而產(chǎn)生變化,由此保證了數(shù)據(jù)同步的可行性。除此之外,還需要引進(jìn)“路由表”的概念,如圖 13,“路由表”負(fù)責(zé)存放每個節(jié)點(diǎn)和 N 個 slot 的映射關(guān)系,并保證盡量把所有 slot 均勻地分配給每個節(jié)點(diǎn)。這樣,當(dāng)節(jié)點(diǎn)上下線時,只需要修改路由表內(nèi)容即可。保持 slot 不變,即保證了彈性擴(kuò)縮容,也大大降低了數(shù)據(jù)同步的難度。
圖 13 預(yù)分片機(jī)制
SOFARegistry 為了實現(xiàn)服務(wù)注冊數(shù)據(jù)的分布式存儲,采用了基于一致性 Hash 的數(shù)據(jù)分片。而由于歷史原因,為了實現(xiàn)數(shù)據(jù)在節(jié)點(diǎn)間的同步,則采用了在 DataServer 之間以 dataInfoId 為粒度進(jìn)行數(shù)據(jù)同步。
當(dāng) DataServer 節(jié)點(diǎn)初始化成功后,會啟動任務(wù)自動去連接 MetaServer。該任務(wù)會往事件中心 EventCenter 注冊一個 DataServerChangeEvent 事件,該事件注冊后會被觸發(fā),之后將對新增節(jié)點(diǎn)計算 Hash 值,同時進(jìn)行納管分片。
DataServerChangeEvent 事件被觸發(fā)后,由 DataServerChangeEventHandler 來進(jìn)行相應(yīng)的處理,分別分為如下一些步驟:
初始化當(dāng)前數(shù)據(jù)節(jié)點(diǎn)的一致性 Hash 值,把當(dāng)前節(jié)點(diǎn)添加進(jìn)一致性的 Hash 環(huán)中。(圖 14)
圖 14 初始化一致性 Hash 環(huán)
獲取變更了的 DataServer 節(jié)點(diǎn),這些節(jié)點(diǎn)在啟動 DataServer 服務(wù)的時候從 MetaServer 中獲取到的,并且通過 DataServerChangeEvent 事件中的 DataServerChangeItem 傳入。(圖 15)
圖 15 獲取變更了的 DataServer 節(jié)點(diǎn)
獲取了當(dāng)前的 DataServer 節(jié)點(diǎn)之后,若節(jié)點(diǎn)列表非空,則遍歷每個節(jié)點(diǎn),建立當(dāng)前節(jié)點(diǎn)與其余數(shù)據(jù)節(jié)點(diǎn)之間的連接,同時刪除本地維護(hù)的不在節(jié)點(diǎn)列表中的節(jié)點(diǎn)數(shù)據(jù)。同時,若當(dāng)前節(jié)點(diǎn)是 DataCenter 節(jié)點(diǎn),則觸發(fā) LocalDataServerChangeEvent 事件。
至此,節(jié)點(diǎn)初始化以及分片入 Hash 環(huán)的工作已經(jīng)完成。
數(shù)據(jù)節(jié)點(diǎn)相關(guān)數(shù)據(jù),儲存在 Map 中,相關(guān)的數(shù)據(jù)結(jié)構(gòu)如圖 16 所示。
圖 16 DataServer 節(jié)點(diǎn)一致性 Hash 存儲結(jié)構(gòu)
當(dāng)服務(wù)上線時,會計算新增服務(wù)的 dataInfoId Hash 值,從而對該服務(wù)進(jìn)行分片,最后尋找最近的一個節(jié)點(diǎn),存儲到相應(yīng)的節(jié)點(diǎn)上。
前文已經(jīng)說過,DataServer 服務(wù)在啟動時添加了 publishDataProcessor 來處理相應(yīng)的服務(wù)發(fā)布者數(shù)據(jù)發(fā)布請求,該 publishDataProcessor 就是 PublishDataHandler。當(dāng)有新的服務(wù)發(fā)布者上線,DataServer 的 PublishDataHandler 將會被觸發(fā)。
該 Handler 首先會判斷當(dāng)前節(jié)點(diǎn)的狀態(tài),若是非工作狀態(tài)則返回請求失敗。若是工作狀態(tài),則觸發(fā)數(shù)據(jù)變化事件中心 DataChangeEventCenter 的 onChange 方法。
DataChangeEventQueue 中維護(hù)著一個 DataChangeEventQueue 隊列數(shù)組,數(shù)組中的每個元素是一個事件隊列。當(dāng)上文中的 onChange 方法被觸發(fā)時,會計算該變化服務(wù)的 dataInfoId 的 Hash 值,從而進(jìn)一步確定出該服務(wù)注冊數(shù)據(jù)所在的隊列編號,進(jìn)而把該變化的數(shù)據(jù)封裝成一個數(shù)據(jù)變化對象,傳入到隊列中。
DataChangeEventQueue#start 方法在 DataChangeEventCenter 初始化的時候被一個新的線程調(diào)用,該方法會源源不斷地從隊列中獲取新增事件,并且進(jìn)行分發(fā)。新增數(shù)據(jù)會由此添加進(jìn)節(jié)點(diǎn)內(nèi),實現(xiàn)分片。
SOFARegistry 是 Client、SessionServer、DataServer 三層架構(gòu),同時通過 MetaServer 管理 Session 和 Data 集群,在服務(wù)注冊的過程中,數(shù)據(jù)既有層間的數(shù)據(jù)同步,也有層內(nèi)的節(jié)點(diǎn)間同步。
Client 端在本地內(nèi)存內(nèi)已經(jīng)存儲了需要訂閱和發(fā)布的服務(wù)數(shù)據(jù),在連接上 Session 后會回放訂閱和發(fā)布數(shù)據(jù)給 Session,最終再發(fā)布到 Data。同時,Session 存儲著客戶端發(fā)布的所有 Pub 數(shù)據(jù),定期通過數(shù)據(jù)比對保持和 Data 一致性。當(dāng)數(shù)據(jù)發(fā)生變更時,持有數(shù)據(jù)一方的 Data 發(fā)起變更通知,需要同步的 SessionServer 進(jìn)行版本對比,在判斷出數(shù)據(jù)需要更新時,將拉取最新的數(shù)據(jù)操作日志。
操作日志存儲采用堆棧方式,獲取日志是通過當(dāng)前版本號在堆棧內(nèi)所處位置,把所有版本之后的操作日志同步過來執(zhí)行。
為保障 Data 層數(shù)據(jù)的可用性,SOFARegistry 做了 Data 層的多副本機(jī)制。當(dāng)有 Data 節(jié)點(diǎn)縮容、宕機(jī)發(fā)生時,備份節(jié)點(diǎn)可以立即通過備份數(shù)據(jù)生效成為主節(jié)點(diǎn),對外提供服務(wù),并且把相應(yīng)的備份數(shù)據(jù)再按照新列表計算備份給新的節(jié)點(diǎn)。
當(dāng)有 Data 節(jié)點(diǎn)擴(kuò)容時,新增節(jié)點(diǎn)進(jìn)入初始化狀態(tài),期間禁止新數(shù)據(jù)寫入,對于讀取請求會轉(zhuǎn)發(fā)到后續(xù)可用的 Data 節(jié)點(diǎn)獲取數(shù)據(jù)。在其他節(jié)點(diǎn)的備份數(shù)據(jù)按照新節(jié)點(diǎn)信息同步完成后,新擴(kuò)容的 Data 節(jié)點(diǎn)狀態(tài)變成 Working,開始對外提供服務(wù)。
在海量服務(wù)注冊場景下,為保障 DataServer 能否無限擴(kuò)容面對海量數(shù)據(jù)的業(yè)務(wù)場景,與其他服務(wù)注冊中心不同的是,SOFARegistry 采用了一致性 Hash 算法進(jìn)行數(shù)據(jù)分片,保障了數(shù)據(jù)的可擴(kuò)展性。同時,通過在 DataServer 內(nèi)存里以 dataInfoId 的粒度記錄操作日志,并且在 DataServer 之間也是以 dataInfoId 的粒度去做數(shù)據(jù)同步,保障了數(shù)據(jù)的一致性。
關(guān)于如何解析SOFARegistry就分享到這里了,希望以上內(nèi)容可以對大家有一定的幫助,可以學(xué)到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到。