你應(yīng)該了解的Nacos注冊(cè)中心
背景
前段時(shí)間有新聞報(bào)道,國(guó)外HashiCorp在官網(wǎng)宣布:不允許中國(guó)境內(nèi)使用、部署和安裝該企業(yè)旗下的企業(yè)版產(chǎn)品和軟件。

其中Consul是Java的spring cloud開發(fā)者非常熟悉的一個(gè)服務(wù)發(fā)現(xiàn)和配置中心的中間件,很多人擔(dān)心是否Consul會(huì)受到影響,目前來(lái)看HashiCorp只是對(duì)商業(yè)版進(jìn)行了禁止使用,還沒(méi)有對(duì)開源版本進(jìn)行限制,所以使用Consul的小伙伴不用擔(dān)心。但是隨著時(shí)間的發(fā)展,不同地區(qū)的對(duì)抗會(huì)不斷的升級(jí),說(shuō)不定有一天開源的版本會(huì)被也會(huì)被宣布禁用,所以我們需要知道如何去替代Consul。
在2008年的時(shí)候,那個(gè)時(shí)候zk還沒(méi)出來(lái),阿里巴巴當(dāng)時(shí)內(nèi)部需要做服務(wù)發(fā)現(xiàn),于是自研了ConfigServer,過(guò)了十年,在2018年7月的時(shí)候,阿里發(fā)布了Nacos(ConfigServer開源實(shí)現(xiàn))0.1.0版本,到現(xiàn)在快兩年了已經(jīng)到了1.3.0版本,現(xiàn)在已經(jīng)可以支持很多功能了:
服務(wù)注冊(cè)和發(fā)現(xiàn):nacos和很多rpc框架已經(jīng)做了集成,比如dubbo,SpringCloud等,方便我們拿來(lái)即用,同時(shí)也開放了比較簡(jiǎn)易的api方便我們對(duì)自己的rpc進(jìn)行定制。
配置管理:類似apllo的一個(gè)配置管理中心,讓我們不用把配置寫在文件中了,在后臺(tái)進(jìn)行統(tǒng)一的管理。
地址服務(wù)器: 方便我們對(duì)不同環(huán)境不同隔離場(chǎng)景的nacos進(jìn)行尋址。
安全與穩(wěn)定性: 性能監(jiān)控,加密傳輸,權(quán)限控制管理等等。
對(duì)于nacos的來(lái)說(shuō),最大的核心功能就是服務(wù)注冊(cè)和配置管理,我的文章主要也是介紹這兩大模塊,這篇文章主要是介紹Nacos服務(wù)發(fā)現(xiàn)-注冊(cè)相關(guān)的一些使用,原理以及對(duì)比其他的一些優(yōu)化。
基本概念
首先我們來(lái)看看再Nacos中服務(wù)發(fā)現(xiàn)-注冊(cè)的一些基本概念:
命名空間(namespace):命名空間屬于Nacos頂層的結(jié)構(gòu),用于進(jìn)行租戶級(jí)別的隔離,我們最常用的就是不同環(huán)境比如測(cè)試環(huán)境,線上環(huán)境進(jìn)行隔離。
服務(wù)(Service):服務(wù)的概念就和我們平常的微服務(wù)一一對(duì)應(yīng),比如訂單服務(wù),物流服務(wù)等等。一個(gè)命名空間下可以有多個(gè)Service,不同的命名空間可以有相同的Service,比如測(cè)試環(huán)境和線上環(huán)境都可以有訂單服務(wù)。
虛擬集群:一個(gè)服務(wù)下所有的機(jī)器組成一個(gè)集群,在Nacos中還可以對(duì)集群根據(jù)需要進(jìn)行進(jìn)一步劃分成虛擬集群。
實(shí)例:粗略一點(diǎn)理解就是一臺(tái)機(jī)器或者一個(gè)虛擬機(jī)就是一個(gè)實(shí)例,細(xì)粒一點(diǎn)理解就是一個(gè)或多個(gè)服務(wù)的具有可訪問(wèn)網(wǎng)絡(luò)地址(IP:Port)的進(jìn)程。

上面是Nacos官網(wǎng)文檔中給出的服務(wù)領(lǐng)域模型圖,從圖上我們可以知道層級(jí)關(guān)系的屬于是:服務(wù)-集群-實(shí)例,同時(shí)在服務(wù),集群和實(shí)例中都會(huì)保存一些數(shù)據(jù)用作其他的需求。
其實(shí)一說(shuō)到服務(wù)注冊(cè)很多人首先會(huì)想到Zookeeper,其實(shí)ZK并沒(méi)有直接提供服務(wù)注冊(cè)訂閱的功能,在ZK中要實(shí)現(xiàn)這些功能,你必須要自己一個(gè)一個(gè)的去劃分文件目錄,非常不方便,并且ZK的Api也特別難用,對(duì)于Nacos來(lái)說(shuō)服務(wù)注冊(cè)的Api使用如下:
????????Properties?properties?=?new?Properties();
????????properties.setProperty("serverAddr",?System.getProperty("serverAddr"));
????????properties.setProperty("namespace",?System.getProperty("namespace"));
????????NamingService?naming?=?NamingFactory.createNamingService(properties);
????????naming.registerInstance("microservice-mmp-marketing",?"11.11.11.11",?8888,?"TEST1");
????????naming.subscribe("microservice-mmp-marketing",?new?EventListener()?{
????????????@Override
????????????public?void?onEvent(Event?event)?{
????????????????System.out.println(((NamingEvent)event).getServiceName());
????????????????System.out.println(((NamingEvent)event).getInstances());
????????????}
????????});
我們只需要?jiǎng)?chuàng)建一個(gè)NamingService,然后調(diào)用registerInstance方法和subscribe方法就可以完成我們服務(wù)的注冊(cè)和訂閱了。
如果對(duì)Nacos快速接入有興趣的同學(xué)可以去官網(wǎng)詳細(xì)看一下,這里就不展開介紹了,https://nacos.io/zh-cn/docs/quick-start.html
AP or CP
CAP
說(shuō)到分布式系統(tǒng)就一定離不開CAP定理,CAP定理叫作布魯爾定理。對(duì)于設(shè)計(jì)分布式系統(tǒng)來(lái)說(shuō)(不僅僅是分布式事務(wù))的架構(gòu)師來(lái)說(shuō),CAP就是你的入門理論。
C (一致性):對(duì)某個(gè)指定的客戶端來(lái)說(shuō),讀操作能返回最新的寫操作。對(duì)于數(shù)據(jù)分布在不同節(jié)點(diǎn)上的數(shù)據(jù)上來(lái)說(shuō),如果在某個(gè)節(jié)點(diǎn)更新了數(shù)據(jù),那么在其他節(jié)點(diǎn)如果都能讀取到這個(gè)最新的數(shù)據(jù),那么就稱為強(qiáng)一致,如果有某個(gè)節(jié)點(diǎn)沒(méi)有讀取到,那就是分布式不一致。
A (可用性):非故障的節(jié)點(diǎn)在合理的時(shí)間內(nèi)返回合理的響應(yīng)(不是錯(cuò)誤和超時(shí)的響應(yīng))。可用性的兩個(gè)關(guān)鍵一個(gè)是合理的時(shí)間,一個(gè)是合理的響應(yīng)。合理的時(shí)間指的是請(qǐng)求不能無(wú)限被阻塞,應(yīng)該在合理的時(shí)間給出返回。合理的響應(yīng)指的是系統(tǒng)應(yīng)該明確返回結(jié)果并且結(jié)果是正確的,這里的正確指的是比如應(yīng)該返回50,而不是返回40。
P (分區(qū)容錯(cuò)性):當(dāng)出現(xiàn)網(wǎng)絡(luò)分區(qū)后,系統(tǒng)能夠繼續(xù)工作。打個(gè)比方,這里個(gè)集群有多臺(tái)機(jī)器,有臺(tái)機(jī)器網(wǎng)絡(luò)出現(xiàn)了問(wèn)題,但是這個(gè)集群仍然可以正常工作。
熟悉CAP的人都知道,三者不能共有,如果感興趣可以搜索CAP的證明,在分布式系統(tǒng)中,網(wǎng)絡(luò)無(wú)法100%可靠,分區(qū)其實(shí)是一個(gè)必然現(xiàn)象,如果我們選擇了CA而放棄了P,那么當(dāng)發(fā)生分區(qū)現(xiàn)象時(shí),為了保證一致性,這個(gè)時(shí)候必須拒絕請(qǐng)求,但是A又不允許,所以分布式系統(tǒng)理論上不可能選擇CA架構(gòu),只能選擇CP或者AP架構(gòu)。
對(duì)于CP來(lái)說(shuō),放棄可用性,追求一致性和分區(qū)容錯(cuò)性,我們的zookeeper其實(shí)就是追求的強(qiáng)一致。
對(duì)于AP來(lái)說(shuō),放棄一致性(這里說(shuō)的一致性是強(qiáng)一致性),追求分區(qū)容錯(cuò)性和可用性,這是很多分布式系統(tǒng)設(shè)計(jì)時(shí)的選擇,后面的BASE也是根據(jù)AP來(lái)擴(kuò)展。
順便一提,CAP理論中是忽略網(wǎng)絡(luò)延遲,也就是當(dāng)事務(wù)提交時(shí),從節(jié)點(diǎn)A復(fù)制到節(jié)點(diǎn)B,但是在現(xiàn)實(shí)中這個(gè)是明顯不可能的,所以總會(huì)有一定的時(shí)間是不一致。同時(shí)CAP中選擇兩個(gè),比如你選擇了CP,并不是叫你放棄A。因?yàn)镻出現(xiàn)的概率實(shí)在是太小了,大部分的時(shí)間你仍然需要保證CA。就算分區(qū)出現(xiàn)了你也要為后來(lái)的A做準(zhǔn)備,比如通過(guò)一些日志的手段,是其他機(jī)器回復(fù)至可用。
注冊(cè)中心的選擇
上面說(shuō)了所有的分布式系統(tǒng)都會(huì)對(duì)CAP進(jìn)行抉擇,同樣的注冊(cè)中心也不例外。Zookeeper是很多做服務(wù)注冊(cè)中心的首選,我目前所在的公司也在使用ZooKeeper當(dāng)做注冊(cè)中心,但是隨著公司的發(fā)展,ZK越來(lái)越不穩(wěn)定,并且多機(jī)房的服務(wù)發(fā)現(xiàn)也非常困難,在這篇文章中阿里巴巴為什么不用 ZooKeeper 做服務(wù)發(fā)現(xiàn)?,更加詳細(xì)的闡述了阿里為什么不用ZK當(dāng)作注冊(cè)中心。這里我簡(jiǎn)單的闡述一下:
性能不滿足,無(wú)法水平擴(kuò)展:熟悉ZK的同學(xué)都知道ZK是往Leader(主節(jié)點(diǎn))去寫數(shù)據(jù)的,所以很難進(jìn)行水平擴(kuò)展,當(dāng)公司達(dá)到一定規(guī)模的時(shí)候,ZK就不適合做注冊(cè)中心,頻繁的讀寫很容易導(dǎo)致ZK不穩(wěn)定。對(duì)于這種情況,可以分多個(gè)ZK集群,但是就涉及到一個(gè)問(wèn)題,開始的時(shí)候各個(gè)集群可能不會(huì)打交道,但是到后期如果有什么協(xié)同的業(yè)務(wù),各個(gè)集群的服務(wù)之間互相調(diào)用就成為了一個(gè)難點(diǎn)。
ZooKeeper API難用:Zookeeper的使用真的是需要一個(gè)比較精通的專家,你需要熟悉他的很多異常,以及對(duì)這些異常到底做什么處理。
注冊(cè)中心不需要存儲(chǔ)一些歷史的變化:注冊(cè)中心理論上來(lái)說(shuō)只需要知道此時(shí)此刻在注冊(cè)中心上,有哪些服務(wù)和實(shí)例進(jìn)行了注冊(cè),但是ZK會(huì)持續(xù)的記錄事務(wù)日志,以便后續(xù)進(jìn)行修復(fù)。
同機(jī)房不可連通:如果我們有一個(gè)三機(jī)房容災(zāi)5節(jié)點(diǎn)部署結(jié)構(gòu),如下圖所示:

如果機(jī)房3 和 機(jī)房1,2 出現(xiàn)了網(wǎng)絡(luò)分區(qū),也就是機(jī)房?jī)?nèi)部是好的,但是機(jī)房之間是不連通的,由于本機(jī)房的ZK出現(xiàn)了分區(qū),當(dāng)前ZK也會(huì)不可用的,那么機(jī)房?jī)?nèi)部的服務(wù)就會(huì)無(wú)法使用注冊(cè)中心導(dǎo)致無(wú)法進(jìn)行互相調(diào)用,很明顯這個(gè)是不允許的,機(jī)房?jī)?nèi)部之間的網(wǎng)絡(luò)是好的,就應(yīng)該允許在同機(jī)房?jī)?nèi)部進(jìn)行調(diào)用。
基于上面來(lái)看ZK已經(jīng)不太適合作為我們的注冊(cè)中心來(lái)用了,換句話來(lái)說(shuō)注冊(cè)中心不太需要CP,我們應(yīng)該更多的提供是AP。
Nacos中的協(xié)議
Distro
在Nacos的Instance(實(shí)例)中提供了一個(gè)ephemeral字段,這個(gè)字段是bool類型,這個(gè)字段和ZK中的含義差不多,都代表的是否是臨時(shí)節(jié)點(diǎn),在Nacos中如果是臨時(shí)節(jié)點(diǎn)就會(huì)使用AP協(xié)議,如果不是臨時(shí)節(jié)點(diǎn)就會(huì)走CP。當(dāng)然在注冊(cè)中心中所有的實(shí)例其實(shí)默認(rèn)都是臨時(shí)節(jié)點(diǎn)。
在Nacos中為了實(shí)現(xiàn)AP,自己定制了一套Distro協(xié)議。下面我們分析一下Distro到底是什么:
純內(nèi)存保存
在Distro中所有的數(shù)據(jù)都是用內(nèi)存進(jìn)行保存,在DistroConsistencyService中有:


可以看見(jiàn)Distro是用ConcurrentHashMap作為存儲(chǔ)的容器,不需要使用額外的文件進(jìn)行存儲(chǔ)。有同學(xué)會(huì)問(wèn)了如果我這個(gè)機(jī)器宕機(jī)了,內(nèi)存信息就全部丟失了,那我這部分?jǐn)?shù)據(jù)怎么恢復(fù)呢?

在DistroConsistencyService的load方法中,我們遍歷所有的非自己的Server,然后將數(shù)據(jù)進(jìn)行同步,如果其中某一個(gè)同步成功了,那就不需要向其他的Server發(fā)出同步信息,。這樣就可以將數(shù)據(jù)全部恢復(fù)。
最終一致
雖然我們說(shuō)的是AP,但是其實(shí)我們還是需要保證最終一致,防止長(zhǎng)時(shí)間各個(gè)節(jié)點(diǎn)數(shù)據(jù)不一致,在DistroConsistencyService的Put方法中會(huì)對(duì)TaskDispatcher添加一個(gè)任務(wù),如下圖:

TaskDispatcher其實(shí)叫DataSyncTaskDispatcher 更加貼切,主要用來(lái)進(jìn)行數(shù)據(jù)同步:

其核心邏輯就是上面的whie循環(huán),主要看我標(biāo)紅的代碼,這里并不是來(lái)一個(gè)更新就發(fā)送一個(gè),如果服務(wù)的變更比較頻繁,如果來(lái)一個(gè)發(fā)送一個(gè)效率必然很低,所以在這里采取了一個(gè)合并的策略,如果更新的數(shù)據(jù)達(dá)到一定的數(shù)量這里默認(rèn)是1000,或者說(shuō)距離上一次發(fā)送已經(jīng)超過(guò)了一定的時(shí)間這里默認(rèn)是2s,都會(huì)進(jìn)行一個(gè)發(fā)送,這里的發(fā)送也是生成一個(gè)SyncTask然后放到線程池中進(jìn)行異步發(fā)送。
這個(gè)時(shí)候有同學(xué)會(huì)問(wèn),如果我某個(gè)機(jī)器剛剛上線,剛好沒(méi)有收到更新的這個(gè)數(shù)據(jù)這個(gè)怎么辦呢?在Nacos中也會(huì)有一個(gè)兜底的策略TimedSync,這個(gè)任務(wù)每5s會(huì)執(zhí)行一次,具體的執(zhí)行任務(wù)代碼如下:

注意圈紅的部分,這里并不是同步所有的數(shù)據(jù),而是遍歷所有的數(shù)據(jù),將屬于自己管理那部分?jǐn)?shù)據(jù)才會(huì)同步(什么數(shù)據(jù)才屬于自己管理呢?下個(gè)小節(jié)會(huì)細(xì)講),然后獲取所有的非自己的Server將這些數(shù)據(jù)進(jìn)行check發(fā)送。
通過(guò)這兩種方式:實(shí)時(shí)的更新和定時(shí)的更新我們就可以保證所有的Nacos節(jié)點(diǎn)上的數(shù)據(jù)最后都是最終一致。
水平擴(kuò)展
ZK的一個(gè)缺點(diǎn)就是無(wú)法進(jìn)行水平擴(kuò)展,這個(gè)是CP的一大問(wèn)題,隨著公司的發(fā)展,規(guī)模變大之后,你很難在撐起現(xiàn)在的業(yè)務(wù)了。在Distro中沒(méi)有Leader這個(gè)角色,每個(gè)節(jié)點(diǎn)都可以處理讀寫,通過(guò)這樣的方式,我們可以任意的水平擴(kuò)展Nacos的節(jié)點(diǎn)來(lái)完成我們的需要。
在Distro中并不是每個(gè)節(jié)點(diǎn)都可以處理所有的讀請(qǐng)求,但是寫請(qǐng)求并不是每個(gè)節(jié)點(diǎn)都可以處理的,每個(gè)節(jié)點(diǎn)會(huì)根據(jù)key的hash值來(lái)判斷是否應(yīng)該是自己處理。寫請(qǐng)求訪問(wèn)的是域名這個(gè)是會(huì)隨機(jī)打到每個(gè)節(jié)點(diǎn)上的,Nacos是怎么做到讓這些寫請(qǐng)求打到對(duì)應(yīng)的機(jī)器上呢,這個(gè)答案就在DistroFilter中:

這個(gè)ServletFilter會(huì)對(duì)每個(gè)請(qǐng)求做一些過(guò)濾,如果發(fā)現(xiàn)這個(gè)請(qǐng)求不是自己的,那么就會(huì)轉(zhuǎn)發(fā)這個(gè)請(qǐng)求到對(duì)應(yīng)的服務(wù)器進(jìn)行處理,收到結(jié)果之后再返回給用戶。
這里Nacos其實(shí)可以做個(gè)優(yōu)化,我們可以發(fā)現(xiàn)轉(zhuǎn)發(fā)的時(shí)候這個(gè)動(dòng)作是同步的,我們這里可以使用異步發(fā)送,并且開啟serlvet的異步,這個(gè)轉(zhuǎn)發(fā)節(jié)點(diǎn)就可以類似網(wǎng)關(guān)一樣不需要同步的等待,可以增加Nacos集群的吞吐量
Distro的這些優(yōu)勢(shì)對(duì)比其他的CP協(xié)議來(lái)說(shuō)在注冊(cè)中心這方面非常大,并且整個(gè)協(xié)議的實(shí)現(xiàn)也比他們簡(jiǎn)單很多,非常容易理解,如果以后大家涉及注冊(cè)中心協(xié)議的一些涉及,可以參照這種思路。
Raft
在Nacos中其實(shí)也有強(qiáng)一致性的協(xié)議,使用的是Raft,有兩個(gè)地方使用了Raft:
注冊(cè)中心中,Nacos中有一些數(shù)據(jù)需要持久化存儲(chǔ)的,我們會(huì)使用Raft去進(jìn)行數(shù)據(jù)的一致性同步存儲(chǔ),比如Service,命名空間的一些數(shù)據(jù),Nacos認(rèn)為實(shí)例是一個(gè)變化比較快的,臨時(shí)的數(shù)據(jù),不需要Raft這種一致性較高的協(xié)議,但是Service和命名空間是一個(gè)變化比較少的數(shù)據(jù),適合做持久化存儲(chǔ)。注冊(cè)中心的Raft實(shí)現(xiàn)。目前是使用的自己寫的一套R(shí)aft,閱讀了一下細(xì)節(jié)和標(biāo)準(zhǔn)的Raft還是有一些差別的,比如日志的連續(xù)性在Nacos的Raft協(xié)議沒(méi)有進(jìn)行保證。
Nacos在1.3.0之后為了開始逐漸的使用標(biāo)準(zhǔn)的raft協(xié)議的實(shí)現(xiàn)sofa-jraft,暫時(shí)只在配置中心中進(jìn)行了使用,配置中心之前只能使用mysql存儲(chǔ),1.3.0之后Nacos借鑒了 Etcd 的通過(guò)Raft協(xié)議將單機(jī)KV存儲(chǔ)轉(zhuǎn)變?yōu)榉植际降腒V存儲(chǔ)的設(shè)計(jì)思想,基于SOFA-JRaft以及Apache Derby構(gòu)建了一個(gè)輕量級(jí)的分布式關(guān)系型數(shù)據(jù)庫(kù),同時(shí)保留了使用外置數(shù)據(jù)源的能力,用戶可以根據(jù)自己的實(shí)際業(yè)務(wù)情況選擇其中一種數(shù)據(jù)存儲(chǔ)方案。
具體的Raft協(xié)議細(xì)節(jié),這里就不展開細(xì)說(shuō)了,有興趣的可以看一下翻譯的論文:
https://www.infoq.cn/article/raft-paper
節(jié)點(diǎn)的注冊(cè)和訂閱
在注冊(cè)中心中另一個(gè)比較重要的就是我們的節(jié)點(diǎn)如何進(jìn)行注冊(cè)和訂閱,如何進(jìn)行心跳檢測(cè)防止節(jié)點(diǎn)宕機(jī),訂閱的節(jié)點(diǎn)如何能實(shí)時(shí)收到更新。
節(jié)點(diǎn)的注冊(cè)
naming.registerInstance("microservice-mmp-marketing",?"11.11.11.11",?8888,?"TEST1");
我們只需要簡(jiǎn)單的寫上面這行代碼就可以完成我們節(jié)點(diǎn)的注冊(cè)了。上面代碼對(duì)ServiceName為microservice-mmp-marketing,在TEST1Clsuter下,注冊(cè)了一個(gè)Ip為11.11.11.11,端口為8888的實(shí)例,在注冊(cè)的同時(shí)會(huì)添加一個(gè)心跳任務(wù)

如上圖紅色的部分,向線程池中添加了一個(gè)延遲心跳任務(wù),默認(rèn)是5s執(zhí)行,

在
BeatTask中,首先會(huì)向Nacos-Server發(fā)送心跳,如果Nacos-Server定義了心跳的間隔接下來(lái)就會(huì)根據(jù)返回的結(jié)果修改下一次執(zhí)行的時(shí)間,如果我們的服務(wù)不存在,那么說(shuō)明我們的機(jī)器因?yàn)槟撤N原因沒(méi)有同步心跳,導(dǎo)致已經(jīng)被摘除了,這里需要再次注冊(cè)這個(gè)節(jié)點(diǎn)。在Nacos-Server的ClientBeatCheckTask中,我們會(huì)根據(jù)Service維度,定時(shí)去掃描Service下是否有長(zhǎng)時(shí)間沒(méi)有同步的實(shí)例,默認(rèn)是15s,如下面的紅框中的代碼:

節(jié)點(diǎn)的訂閱
節(jié)點(diǎn)的訂閱在不同的注冊(cè)中心中都有不同的實(shí)現(xiàn),一般的套路分為兩種輪訓(xùn)和推送。
推送是指當(dāng)訂閱的節(jié)點(diǎn)發(fā)生更新的時(shí)候會(huì)主動(dòng)向訂閱方進(jìn)行推送,我們的ZK就是推送的實(shí)現(xiàn)方式,客戶端和服務(wù)端會(huì)建立一個(gè)TCP長(zhǎng)連接,客戶端會(huì)注冊(cè)一個(gè)watcher,然后當(dāng)有數(shù)據(jù)更新的時(shí)候,服務(wù)端會(huì)通過(guò)長(zhǎng)連接進(jìn)行推送。通過(guò)這種建立長(zhǎng)連接的模式,會(huì)嚴(yán)重消耗服務(wù)端的資源,所以當(dāng)watcher比較多,并且當(dāng)更新頻繁的時(shí)候,Zookeeper的性能會(huì)非常低,甚至掛掉。
輪訓(xùn)是指我們訂閱的節(jié)點(diǎn)主動(dòng)定時(shí)獲取服務(wù)端節(jié)點(diǎn)的信息,然后再本地去做一個(gè)比對(duì),如果有改變就會(huì)做一些更新。在Consul中也有一個(gè)watcher機(jī)制,但和ZK不一樣的是,他是通過(guò)Http長(zhǎng)輪詢?nèi)?shí)現(xiàn)的,Consul服務(wù)端會(huì)對(duì)請(qǐng)求的url中是否包含wait參數(shù)進(jìn)行立即返回,還是先掛起等待指定wait時(shí)間內(nèi)如果服務(wù)有變化在返回。使用輪訓(xùn)的性能可能較高但是實(shí)時(shí)性就可能不是太好。
在Nacos中,結(jié)合了這兩個(gè)思想,既提供了輪訓(xùn)又提供了主動(dòng)推送,我們先來(lái)看看輪訓(xùn)的部分,再UpdateTask類中的run方法中:

注意看上面的紅框中,我們會(huì)根據(jù)Service維度去定時(shí)輪訓(xùn),我們的ServiceInfo,然后去更新。
我們?cè)賮?lái)看下,Nacos是如何實(shí)現(xiàn)推送功能的,Nacos會(huì)記錄上面我們的訂閱者到我們的PushService, 下面是我們的PushService中的推送核心代碼:

Step 1:通過(guò)ServiceChangeEvent事件觸發(fā)我們的推送,這里要注意的是因?yàn)槲覀兊墓?jié)點(diǎn)都是通過(guò)distro進(jìn)行更新,當(dāng)我們distroT同步到其他機(jī)器上時(shí),同樣也會(huì)觸發(fā)這個(gè)事件。
Step 2:獲取本機(jī)上維護(hù)的訂閱者,因?yàn)橛嗛喺呤歉鶕?jù)是否查詢過(guò)服務(wù)節(jié)點(diǎn)來(lái)定義的,查詢過(guò)服務(wù)節(jié)點(diǎn)這個(gè)動(dòng)作會(huì)被隨機(jī)的打到不同的Nacos-Server上,所以我們每個(gè)節(jié)點(diǎn)都會(huì)維護(hù)一部分訂閱者,并且維護(hù)的訂閱者之間還會(huì)有重復(fù),由于后續(xù)是UDP發(fā)送,重復(fù)維護(hù)訂閱者的成本不是很高。
Step3:生成ackEntry,也就是我們發(fā)送的內(nèi)容并且將其緩存起來(lái),這里緩存主要是防止重復(fù)做壓縮的過(guò)程。
Step4: 最后進(jìn)行udp的發(fā)送
Nacos這種推送模式,對(duì)于Zookeeper那種通過(guò)tcp長(zhǎng)連接來(lái)說(shuō)會(huì)節(jié)約很多資源,就算大量的節(jié)點(diǎn)更新也不會(huì)讓Nacos出現(xiàn)太多的性能瓶頸,在Nacos中客戶端如果接受到了udp消息會(huì)返回一個(gè)ACK,如果一定時(shí)間Nacos-Server沒(méi)有收到ACK,那么還會(huì)進(jìn)行重發(fā),當(dāng)超過(guò)一定重發(fā)時(shí)間之后,就不在重發(fā)了,雖然通過(guò)udp并不能保證能真正的送到訂閱者,但是Nacos還有定時(shí)輪訓(xùn)作為兜底,不需要擔(dān)心數(shù)據(jù)不會(huì)更新的情況。
Nacos通過(guò)這兩種手段,既保證了實(shí)時(shí)性,又保證了數(shù)據(jù)更新不會(huì)漏掉。
總結(jié)
Nacos雖然是一個(gè)新開源的項(xiàng)目,但是其架構(gòu),源碼設(shè)計(jì)都是非常的精巧,比如在內(nèi)部源碼中使用了很多EventBus這種架構(gòu),將很多流程都解耦開來(lái),并且其Distro協(xié)議思路也是非常值得我們學(xué)習(xí)的。
在Nacos的注冊(cè)中心中還有一些其他的細(xì)節(jié),比如根據(jù)標(biāo)簽進(jìn)行Selector等等。這些有興趣的可以下來(lái)再去Nacos文檔中了解一下。
推薦閱讀:
喜歡我可以給我設(shè)為星標(biāo)哦


