我們做出了一個(gè)分布式注冊中心
這篇文章是基于 SOFA Meetup 合肥站的分享總結(jié),主要針對于注冊中心的定位以及功能介紹,通過對螞蟻?zhàn)灾行陌l(fā)展史的分析,帶領(lǐng)大家了解,螞蟻的注冊中心是如何一步一步演變?yōu)楝F(xiàn)在的規(guī)模和特性的。
更多深入的技術(shù)細(xì)節(jié),歡迎大家加入到 SOFA 和 SOFARegistry 的社區(qū)中,探尋結(jié)果。
一、服務(wù)發(fā)現(xiàn) & 服務(wù)注冊
注冊中心簡單來說,是為了解決分布式場景下,服務(wù)之間互相發(fā)現(xiàn)的問題。
如下圖所示,服務(wù) A 想要調(diào)用服務(wù) B 的時(shí)候,需要知道 B 的地址在哪里,如何解決這個(gè)問題?

一般來說,分為兩個(gè)點(diǎn):
服務(wù)發(fā)現(xiàn)可以有一個(gè)中心化的組件或者說是存儲(chǔ),它承載了所有服務(wù)的地址,同時(shí)提供出來一個(gè)可供查詢和訂閱的能力,服務(wù)的消費(fèi)方可以通過和這個(gè)中心化的存儲(chǔ)交互,獲取服務(wù)提供方的地址列表。
服務(wù)注冊:同樣是上文中中心化的組件,但是,這個(gè)時(shí)候的服務(wù)信息可以有兩種措施:
服務(wù)連接注冊中心,同時(shí)上報(bào)自身的服務(wù)以及元數(shù)據(jù)(也是今天本文講述的重點(diǎn))
有一個(gè)集中的控制面(control plane)將用戶定義的服務(wù)和 IP 的映射寫入注冊中心,例如 AWS 的 CloudMap。
二、調(diào)用流程

如上圖所示,就是目前一種主流的注冊中心模式,SOFARegistry 和 Nacos 都是這種模式。
服務(wù) A,服務(wù) B 通過 SDK 或者 REST 將自身的服務(wù)信息上報(bào)給注冊中心;
服務(wù) A 需要調(diào)用服務(wù) B 的時(shí)候,就對注冊中心發(fā)起請求,拉取和服務(wù) B 相關(guān)的服務(wù) IP 列表以及信息;
在獲取到服務(wù) B 的列表之后,就可以通過自身定義的負(fù)載均衡算法訪問服務(wù) B。
三、心跳
心跳是注冊中心用于解決服務(wù)不可用時(shí),及時(shí)拉出服務(wù)降低影響的默認(rèn)方式,如下圖所示。
服務(wù) B 的一個(gè)節(jié)點(diǎn)斷網(wǎng)或是 hang 住,引發(fā)心跳超時(shí);或是宕機(jī)、斷鏈直接引發(fā)心跳失敗;
注冊中心把問題節(jié)點(diǎn)從自身的存儲(chǔ)中拉出(這里拉出根據(jù)具體實(shí)現(xiàn):有的是直接刪除,有的是標(biāo)記為不健康);
服務(wù) A 收到注冊中心的通知,獲取到服務(wù) B 最新的列表。

四、Dubbo 注冊中心
下面通過 Dubbo 的例子,我們來看一下注冊中心是如何使用的,以及流程:首先,Dubbo 在 2.7 和 3.0 中的配置略有不同,但是都是簡單易懂的,這里都放上來。
Dubbo-2.7

Dubbo-3.0

在 RPC 客戶端只需要配置一個(gè)注冊中心的地址即可,地址中包含了基礎(chǔ)三元素:
protocol(協(xié)議類型)比如,zookeeper;
host;
port。
基于此,Dubbo 的注冊流程如下圖所示:
服務(wù)的生產(chǎn)方通過 Dubbo 客戶端向注冊中心(Registry)發(fā)起注冊行為(register);
服務(wù)的消費(fèi)方通過 Dubbo 客戶端訂閱信息(subscribe);
注冊中心通過通知的方式,下發(fā)服務(wù)列表給服務(wù)消費(fèi)方。

五、注冊中心的本質(zhì)
通過前文的講解,以及 Dubbo 組件的具體例子,我們大概可以歸納注冊中心的本質(zhì)。
“存儲(chǔ)” + “可運(yùn)維”
一方面,注冊中心需要存儲(chǔ)能力去記錄服務(wù)的信息,比如應(yīng)用列表;
另一方面,注冊中心在實(shí)踐過程中,需要提供必需的運(yùn)維手段,比如關(guān)閉某一服務(wù)流量。

一、史前時(shí)代
史前時(shí)代的螞蟻是相當(dāng)久遠(yuǎn)的架構(gòu),當(dāng)時(shí)所有的服務(wù)部署在同一臺物理機(jī)上或者 JVM 上,服務(wù)之間不存在有跨機(jī)器調(diào)用的場景,這里略過不表述。

二、硬負(fù)載時(shí)代
后來,為了解決應(yīng)用之間的耦合帶來的部署難,運(yùn)維難問題,我們對服務(wù)進(jìn)行了拆分,拆分后的服務(wù),遇到了一個(gè)問題,就是如何處理服務(wù)之間的調(diào)用關(guān)系,這個(gè)時(shí)候,螞蟻用了兩種硬負(fù)載 F5 或是 LVS。
通過簡單的 4 層代理,我們可以把服務(wù)部署在代理的后面,服務(wù)與服務(wù)之間通過代理互相訪問,達(dá)到了跨機(jī)調(diào)用的目的。

三、第一代注冊中心 -- 硬負(fù)載到軟負(fù)載的演變
通過硬負(fù)載訪問的方式,一方面解決了服務(wù)之間互相調(diào)用的問題,部署架構(gòu)也簡單易懂;另一方面,在業(yè)務(wù)快速增長之后,卻帶來了一定的問題:
單點(diǎn)的問題(所有調(diào)用都走 F5 的話,F(xiàn)5 一旦掛了,很多服務(wù)會(huì)不可用);
容量問題(F5 承載的流量太高,本身會(huì)到一個(gè)性能瓶頸)。
這個(gè)時(shí)候,螞蟻引進(jìn)了阿里集團(tuán)的一款產(chǎn)品叫 ConfigServer,作為注冊中心進(jìn)行使用,這個(gè)注冊中心的架構(gòu)就和開頭提到的架構(gòu)很像了,服務(wù)之間可以通過 IP 直接訪問,而降低了對負(fù)載均衡產(chǎn)品的強(qiáng)依賴,減少了單點(diǎn)風(fēng)險(xiǎn)。

四、第二代注冊中心 -- ScaleUp?ScaleOut?It's a problem
但是,問題還在持續(xù),那就是注冊中心,本身是一個(gè)單點(diǎn),那么,他就會(huì)繼續(xù)遇到上文中所說的兩個(gè)問題:
單點(diǎn)風(fēng)險(xiǎn)(注冊中心本身是單機(jī)應(yīng)用);
容量瓶頸(單臺注冊中心的連接數(shù)和存儲(chǔ)數(shù)據(jù)的容量是有限的)。
解決的方式有兩種:
scale-up(淘寶):通過增加機(jī)器的配置,來增強(qiáng)容量以及扛鏈接能力;同時(shí),通過主-備這樣的架構(gòu),來保障可用性;
scale-out(螞蟻):通過分片機(jī)制,將數(shù)據(jù)和鏈接均勻分布在多個(gè)節(jié)點(diǎn)上,做到水平拓展;通過分片之后的備份,做到高可用。
螞蟻和淘寶走了兩條不同的路,也推進(jìn)了螞蟻后面演進(jìn)出一套獨(dú)立的生態(tài)系統(tǒng)。
螞蟻的演進(jìn)架構(gòu)如下,產(chǎn)生了兩種不同的應(yīng)用節(jié)點(diǎn):
Session 節(jié)點(diǎn),專門用來抗鏈接使用,本身無狀態(tài)可以快速擴(kuò)展,單機(jī)對資源的占用很?。?br>
Data 節(jié)點(diǎn),專門用來存儲(chǔ)數(shù)據(jù),通過分片的方式降低單個(gè)節(jié)點(diǎn)的存儲(chǔ)量,控制資源占用。

五、第五代注冊中心 -- Meta 節(jié)點(diǎn)的誕生
上面的架構(gòu)已經(jīng)很符合目前主流的分布式架構(gòu)了,但是在運(yùn)維過程中,產(chǎn)生了一系列問題,比如:
所有 Data 都是分布式的,Data 之間的服務(wù)發(fā)現(xiàn)需要通過啟動(dòng)時(shí)給定一個(gè)配置文件,這樣就和標(biāo)準(zhǔn)運(yùn)維脫鉤;
Data 節(jié)點(diǎn)的上下線需要去及時(shí)修改配置文件,否則集群重啟會(huì)受到影響;
分布式存儲(chǔ)一致性問題,每次迭代發(fā)布,需要鎖定 paas 平臺,防止節(jié)點(diǎn)變動(dòng)帶來的不一致。
所有這些問題的產(chǎn)生,我們發(fā)現(xiàn)可以引入一個(gè)元數(shù)據(jù)管理中心(Meta)節(jié)點(diǎn)來,解決對 Data 和 Session 管理的問題,Data 和 Session 通過 4 層負(fù)載或是 7 層負(fù)載對 Meta 訪問即可。
對比業(yè)界的解決方案,都有類似的模型,比如 HDFS 的 Name Node、Kafka 依賴于 ZK,Oceanbase 依賴于 RootServer 或者配置中心 Apollo 依賴于 Euraka。
Meta 節(jié)點(diǎn)的出現(xiàn),緩解了手工運(yùn)維注冊中心的瓶頸,但是,依然沒有從根本上解決問題,那么問題在哪里?詳見下文分析。

六、第六代注冊中心 -- 面向運(yùn)維的注冊中心
上文說道,Meta 節(jié)點(diǎn)的出現(xiàn),承接了 Data 以及 Session 之間服務(wù)發(fā)現(xiàn)的問題,但是,叢云未測來講,還是有很多問題解決不了,比如:
1. Data 節(jié)點(diǎn)的發(fā)布在數(shù)據(jù)量大的前提下,依然是個(gè)痛點(diǎn);
2. Session 節(jié)點(diǎn)的新加節(jié)點(diǎn)上,可能很久都沒有流量。
等等,對于這些問題,在 SOFARegistry 5.x 的基礎(chǔ)上,我們快速迭代了 6.0 版本,主要是面向運(yùn)維的注冊中心。
Data 節(jié)點(diǎn)發(fā)布難的問題,說到底是一個(gè)影響范圍的問題,如何控制單一 Data 節(jié)點(diǎn)發(fā)布或者掛掉對數(shù)據(jù)的影響面,是解決問題的本源,這里我們采用了兩個(gè)措施:
1. 改進(jìn)數(shù)據(jù)存儲(chǔ)算法(consistent-hash -> hash-slot);
2. 應(yīng)用級服務(wù)發(fā)現(xiàn)。
(1)存儲(chǔ)算法的演進(jìn)
之前我們使用了一致性 hash 的算法,如下圖所示,每一個(gè)節(jié)點(diǎn)承載一部分?jǐn)?shù)據(jù),通過是存儲(chǔ)進(jìn)行 hash 運(yùn)算,算出存儲(chǔ)內(nèi)容的 hash 值,再計(jì)算出 hash 值落在哪一個(gè) Data 所負(fù)責(zé)的存儲(chǔ)區(qū)間,來存儲(chǔ)數(shù)據(jù)。
當(dāng) Data 節(jié)點(diǎn)宕機(jī)或者重啟時(shí),由下一個(gè) Data 節(jié)點(diǎn)接收宕機(jī)節(jié)點(diǎn)的數(shù)據(jù)以及數(shù)據(jù)的訪問支持。

這樣依賴,數(shù)據(jù)遷移的粒度只能以單個(gè) Data 節(jié)點(diǎn)所存儲(chǔ)的數(shù)據(jù)為單位,在數(shù)據(jù)量較大(單節(jié)點(diǎn) 8G)的情況下,對數(shù)據(jù)的重建有一定的影響,而且,在 Data 連續(xù)宕機(jī)的情況下,可能存在數(shù)據(jù)丟失或是不一致的場景。
改進(jìn)后的算法,我們參考了 Redis Cluster 的算法機(jī)制,使用 hash slot 進(jìn)行數(shù)據(jù)分片;

這樣,在 Data 發(fā)布過程中,可以控制數(shù)據(jù)的遷移以 slot 為單位(單個(gè) Data 節(jié)點(diǎn)多個(gè) slot,可配置)


同時(shí),為了解決遷移或是宕機(jī)期間,數(shù)據(jù)寫入不一致的場景,我們引入了數(shù)據(jù)回放的補(bǔ)償機(jī)制,Data 在 promotion 為 slot 的 master 之后,會(huì)主動(dòng)地去和所有的 Session 完成一次數(shù)據(jù)比對/校驗(yàn),增量同步新增數(shù)據(jù)。

(2)應(yīng)用級服務(wù)發(fā)現(xiàn)
應(yīng)用級服務(wù)發(fā)現(xiàn)是為了解決數(shù)據(jù)存儲(chǔ)量大的問題,因?yàn)槠?,這里略過不表述。
SOFARegistry 從項(xiàng)目早期就開始了開源的進(jìn)程,與目前主流的注冊中心的對比如下:
我們認(rèn)為,注冊中心首先需要解決的是可用性的問題,所以,在分布式一致性的問題上,我們選擇了 AP 的模型,這點(diǎn)也和主流的注冊中心,例如 Euraka 以及 Nacos 保持一致的觀點(diǎn)。
其次,在性能方面,基于長連接的 SOFARegistry 擁有更短的推送延遲,相較于 Nacos 1.0 的推送時(shí)延更短(Nacos 1.0 基于 Long Polling 的模型,Nacos 2.0 也使用了長連接的模型)。
在協(xié)議方面,SOFARegistry 使用了螞蟻開源協(xié)議棧:BOLT 協(xié)議(類似于 HTTP 2.0)的流式協(xié)議,更加輕量級,同時(shí)協(xié)議本身的全雙工模式:無阻塞,大大提升了資源利用率。

和大家所熟知的 Nacos 對比,我們在金融級和分布式(存儲(chǔ)量級)上具有很大優(yōu)勢,易用性和云原生方面,目前還在追趕。

一個(gè)人可以走得很快,但一群人可以走的更遠(yuǎn)。
——題記
SOFARegistry 是一個(gè)開源項(xiàng)目,也是開源社區(qū) SOFA 重要的一環(huán),我們希望用社區(qū)的力量推動(dòng) SOFARegistry 的前進(jìn),而不是只有螞蟻的工程師去開發(fā)。我們在今年也啟動(dòng)了兩個(gè)項(xiàng)目,用于支持更多的開發(fā)者參與進(jìn)來:
Trun-Key Project (開箱即用計(jì)劃):
https://github.com/sofastack/sofa-registry/projects/5
Deep-Dive Project(深入淺出計(jì)劃):
https://github.com/sofastack/sofa-registry/projects/4
計(jì)劃目前還處在初期階段,歡迎大家加入進(jìn)來,可以幫助我們解決一個(gè) issue,或是寫一篇文檔,都可以更好地幫助社區(qū),幫助自己去成長。
