注冊(cè)中心作用
開篇首先想思考一個(gè)問題,沒有注冊(cè)中心 Dubbo 還能玩下去嗎?
當(dāng)然可以,只要知道服務(wù)提供者地址相關(guān)信息,消費(fèi)者配置之后就可以調(diào)用。如果只有幾個(gè)服務(wù),這么玩當(dāng)然沒問題。但是生產(chǎn)服務(wù)動(dòng)輒成千上百,如果每個(gè)服務(wù)都需要手寫配置信息,想象一下是多么麻煩。
好吧,如果上面的問題都不是事的話,試想一下如果一個(gè)服務(wù)提供者在運(yùn)行過程中宕機(jī),消費(fèi)者怎么辦?消費(fèi)者不知情,所以它還會(huì)不斷把請(qǐng)求發(fā)往服務(wù)提供者,然后不斷失敗。這個(gè)時(shí)候唯一的辦法就是修改服務(wù)地址信息,然后重啟服務(wù)。
可以看到如果沒有注冊(cè)中心,分布式環(huán)境中服務(wù)查找發(fā)現(xiàn)將會(huì)非常麻煩,一切需要手工配置,無法完成自動(dòng)化。所以這里就需要一個(gè)第三者,協(xié)調(diào)服務(wù)提供者與消費(fèi)者之間關(guān)系,這就是注冊(cè)中心。
注冊(cè)中心主要作用如下:
* 動(dòng)態(tài)加入,服務(wù)提供者通過注冊(cè)中心動(dòng)態(tài)的把自己暴露給消費(fèi)者,無需消費(fèi)者逐個(gè)更新配置文件。
* 動(dòng)態(tài)發(fā)現(xiàn)服務(wù),消費(fèi)者可以動(dòng)態(tài)發(fā)現(xiàn)新的服務(wù),無需重啟生效。
* 統(tǒng)一配置,避免本地配置導(dǎo)致每個(gè)服務(wù)配置不一致。
* 動(dòng)態(tài)調(diào)整,注冊(cè)中心支持參數(shù)動(dòng)態(tài)調(diào)整,新參數(shù)自動(dòng)更新到所有相關(guān)的服務(wù)節(jié)點(diǎn)。
* 統(tǒng)一管理,依靠注冊(cè)中心數(shù)據(jù),可以統(tǒng)一管理配置服務(wù)節(jié)點(diǎn)。
注冊(cè)中心工作流程
注冊(cè)中心工作流程總體比較簡(jiǎn)單,流程圖大致如下:
主要工作流程可以分為如下幾步:
* 服務(wù)提供者啟動(dòng)之后,會(huì)將服務(wù)注冊(cè)到注冊(cè)中心。
* 消費(fèi)者啟動(dòng)之后主動(dòng)訂閱注冊(cè)中心上提供者服務(wù),從而獲取到當(dāng)前所有可用服務(wù),同時(shí)留下一個(gè)回調(diào)函數(shù)。
* 若服務(wù)提供者新增或下線,注冊(cè)中心將通過第二步的注冊(cè)的回調(diào)函數(shù)通知消費(fèi)者。
* dubbo-admin(服務(wù)治理中心)將會(huì)會(huì)訂閱服務(wù)提供者以及消費(fèi)者,從而可以在控制臺(tái)管理所有服務(wù)提供者以及消費(fèi)者。
Dubbo 之前版本主要可以使用 ZooKeeper,Redis 作為注冊(cè)中心 ,而隨著 Dubbo 版本不斷更新,目前還支持
nacos,consul,etcd 等做為注冊(cè)中心。
Dubbo 注冊(cè)中心核心源碼
ps: 以下源碼基于 dubbo 2.7.3 版本
注冊(cè)中心實(shí)現(xiàn)使用模板模式,源碼位于 dubbo-registry 模塊,類關(guān)系如下圖:
最上層的 RegistryService 接口定義了核心方法,分別為注冊(cè),取消注冊(cè),訂閱,取消訂閱以及查詢。
中間層抽象類主要實(shí)現(xiàn)通用邏輯,如:AbstractRegistry 實(shí)現(xiàn)緩存機(jī)制,F(xiàn)ailbackRegistry 實(shí)現(xiàn)失敗重試功能。
底層 ZookeeperRegistry等為具體實(shí)現(xiàn)類,實(shí)現(xiàn)與 ZooKeeper 等注冊(cè)中心交互的邏輯。
接下去我們具體分析 AbstractRegistry 與 FailbackRegistry 邏輯。
AbstractRegistry 緩存實(shí)現(xiàn)的原理
如果每次服務(wù)調(diào)用都需要調(diào)用注冊(cè)中心實(shí)時(shí)查詢可用服務(wù)列表,不但會(huì)讓注冊(cè)中心承受巨大的流量壓力,還會(huì)產(chǎn)生額外的網(wǎng)絡(luò)請(qǐng)求,導(dǎo)致系統(tǒng)性能下降。
其次注冊(cè)中心需要非強(qiáng)依賴,其宕機(jī)不能影響正常的服務(wù)調(diào)用。
基于以上幾點(diǎn),注冊(cè)中心模塊在 AbstractRegistry 類中實(shí)現(xiàn)通用的緩存機(jī)制。這里的緩存可以分為兩類,內(nèi)存服務(wù)緩存以及磁盤文件緩存。
內(nèi)存服務(wù)緩存
內(nèi)存服務(wù)緩存很好理解也最容易實(shí)現(xiàn),AbstractRegistry使用一個(gè) ConcurrentMap保存相關(guān)信息。
private final ConcurrentMap<URL, Map<String, List<URL>>> notified = new
ConcurrentHashMap<>();
這個(gè)集合中 key 為消費(fèi)者的 URL,而 value 為一個(gè) Map 集合。這個(gè)內(nèi)層 Map 集合使用服務(wù)目錄作為 key,分別為
providers,routers,configurators,consumers 四類,value 則是對(duì)應(yīng)服務(wù)列表集合。
磁盤文件緩存
由于服務(wù)重啟就會(huì)導(dǎo)致內(nèi)存緩存消失,所以額外增加磁盤文件緩存。
文件緩存默認(rèn)位置位于 ${user.home}/.dubbo/文件夾,文件名為
dubbo-registry-${application.name}-${register_address}.cache。可以設(shè)置
dubbo.registry.file 配置信息從而修改默認(rèn)配置,實(shí)現(xiàn)源碼如下:
String filename = url.getParameter(Constants.FILE_KEY,
System.getProperty("user.home") + "/.dubbo/dubbo-registry-" +
url.getParameter(Constants.APPLICATION_KEY) + "-" + url.getAddress() +
".cache");
ps: ${application.name} 取自 dubbo.application.name 信息,${register_address}
取值注冊(cè)中心地址信息。緩存文件完整名稱為:
C:\Users\xxx/.dubbo/dubbo-registry-dubbo-auto-configure-consumer-sample-127.0.0.1:2181.cache
緩存文件內(nèi)容使用 properties 配置文件格式,即 key=value 格式。key為服務(wù)接口名稱,value
為服務(wù)列表,由于服務(wù)可能存在多個(gè),將會(huì)使用空格分隔。
緩存文件的加載
dubbo 程序初始化的時(shí)候,AbstractRegistry 構(gòu)造函數(shù)將會(huì)從本地磁盤文件中將數(shù)據(jù)讀取到 Properties 對(duì)象實(shí)例中,后續(xù)都將會(huì)先寫入
Properties,最后再將里面信息再寫入文件。
緩存初始化的源碼為下圖。
緩存文件的保存與更新
緩存文件將會(huì)通過 AbstractRegistry#notify 方法保存或更新。客戶端第一次訂閱服務(wù)獲取的全量數(shù)據(jù),或者后續(xù)回調(diào)中獲取到新數(shù)據(jù),都將會(huì)調(diào)用
AbstractRegistry#notify 方法,用來更新內(nèi)存緩存以及文件緩存。
notify 方法源碼如下圖:
在保存文件緩存方法中,首先把根據(jù) URL 取出的數(shù)據(jù),拼接成字符串,然后寫入上面提到過的 properties 對(duì)象中,最后輸出到文件中。
這里可以選擇兩種保存方式,同步或異步。由于 notify 可能被多次調(diào)用,為了提高系統(tǒng)能,系統(tǒng)默認(rèn)使用異步方式保存。
saveProperties 方法源碼如下:
doSaveProperties 方法最終將會(huì)將信息寫入緩存??紤]到保存方法可能會(huì)被多個(gè)線程同時(shí)調(diào)用,這里使用 CAS
方法,首先比較版本大小,若小于,代表有新線程正在寫入信息,本次更新直接丟棄。
其次考慮到多個(gè) dubbo 應(yīng)用可能共用一份緩存文件,所以這里使用文件排他鎖當(dāng)做分布式鎖,防止多個(gè)應(yīng)用并發(fā)操作同一份文件。
一旦文件寫入異?;蛘攉@取鎖失敗,保存操作將會(huì)不斷重試,直到超過最大次數(shù)。
ps: dubbo 2.7.2 之前重試沒有設(shè)置最大次數(shù),如果文件沒有權(quán)限保存,保存將會(huì)一直失敗,異步線程將會(huì)陷入死循環(huán)。
doSaveProperties 方法源碼如下:
FailbackRegistry 重試機(jī)制
FailbackRegistry 繼承 AbstractRegistry,實(shí)現(xiàn)了 register,subscribe等通用法,并增加 doRegister,
doSubscribe 等模板方法,交由子類實(shí)現(xiàn)。
如果 doRegister 等模板方法發(fā)生異常,會(huì)將失敗任務(wù)放入集合,然后定時(shí)再次調(diào)用模板方法。
FailbackRegistry 失敗重試集合分別為:
以 subscribe 方法為例,這里將會(huì)調(diào)用這些 doSubscribe
的模板方法。如果發(fā)生異常將會(huì)讀取緩存文件中內(nèi)容,然后加載服務(wù)。最后新建異步定時(shí)任務(wù)加入重試集合中,然后由定時(shí)器去重試這些任務(wù)。
FailbackRegistry#subscribe 方法源碼:
在 addFailedSubscribed 中將會(huì)新建定時(shí)任務(wù),然后交由定時(shí)器執(zhí)行。定時(shí)任務(wù)默認(rèn)最大重試次數(shù)為 3 次,調(diào)用時(shí)間間隔默認(rèn)為 5 s。
addFailedSubscribed 源碼如下:
其他失敗重試任務(wù)都比較類似,全都繼承自 AbstractRetryTask 父類,類關(guān)系如下圖。
總結(jié)
本文主要講述注冊(cè)中心作用,工作流程,通用緩存機(jī)制以及失敗重試機(jī)制。從中可以學(xué)到模板模式,以及多線程并發(fā)技巧。
這里沒有涉及到具體注冊(cè)中心實(shí)現(xiàn),由于目前最主要使用 ZooKeeper 作為注冊(cè)中心,所以下篇將會(huì)聊聊 ZooKeeper 注冊(cè)中心原理,敬請(qǐng)期待。
幫助書籍
『深入理解 Apache Dubbo 與實(shí)戰(zhàn)』
熱門工具 換一換