這篇文章主要介紹“Dubbo如何實(shí)現(xiàn)服務(wù)的動(dòng)態(tài)發(fā)現(xiàn)”,在日常操作中,相信很多人在Dubbo如何實(shí)現(xiàn)服務(wù)的動(dòng)態(tài)發(fā)現(xiàn)問(wèn)題上存在疑惑,小編查閱了各式資料,整理出簡(jiǎn)單好用的操作方法,希望對(duì)大家解答”Dubbo如何實(shí)現(xiàn)服務(wù)的動(dòng)態(tài)發(fā)現(xiàn)”的疑惑有所幫助!接下來(lái),請(qǐng)跟著小編一起來(lái)學(xué)習(xí)吧!
成都創(chuàng)新互聯(lián)公司專注于企業(yè)全網(wǎng)營(yíng)銷推廣、網(wǎng)站重做改版、濱城網(wǎng)站定制設(shè)計(jì)、自適應(yīng)品牌網(wǎng)站建設(shè)、H5建站、商城網(wǎng)站建設(shè)、集團(tuán)公司官網(wǎng)建設(shè)、成都外貿(mào)網(wǎng)站建設(shè)公司、高端網(wǎng)站制作、響應(yīng)式網(wǎng)頁(yè)設(shè)計(jì)等建站業(yè)務(wù),價(jià)格優(yōu)惠性價(jià)比高,為濱城等各大城市提供網(wǎng)站開(kāi)發(fā)制作服務(wù)。
ps: 以下將 ZooKeeper 縮寫(xiě)為 zk。
在 ZooKeeper 基本概念分享一文講道,ZK 內(nèi)部是一種樹(shù)形層次結(jié)構(gòu),節(jié)點(diǎn)存在多種類型。而 Dubbo 只會(huì)創(chuàng)建持久節(jié)點(diǎn)和臨時(shí)節(jié)點(diǎn)。
若服務(wù)提供者服務(wù)接口為 com.service.FooService
,將會(huì)在 ZK 中創(chuàng)建創(chuàng)建如下路徑 /dubbo/com.service.FooService/providers/providerURL
。
服務(wù)路徑分為四層,根節(jié)點(diǎn)默認(rèn)為 dubbo,可以在 dubbo-registry 設(shè)置 group 屬性改變?cè)撝怠?/p>
ps: 若無(wú)注冊(cè)中心隔離需求,不要隨便修改。
第二層節(jié)點(diǎn)為服務(wù)節(jié)點(diǎn)全名稱,如 com.service.FooService
。
第三層節(jié)點(diǎn)為服務(wù)目錄,如 providers。另外還存在其他目錄節(jié)點(diǎn),分別為 consumers(消費(fèi)者目錄),configurators(配置目錄),routers(路由目錄)。下面服務(wù)訂閱主要針對(duì)這一層節(jié)點(diǎn)。
第四個(gè)節(jié)點(diǎn)為具體服務(wù)節(jié)點(diǎn),節(jié)點(diǎn)名為具體的 URL 字符串,如 dubbo://2.0.1.13:12345/com.dubbo.example.DemoService?xx=xx
,該節(jié)點(diǎn)默認(rèn)為臨時(shí)節(jié)點(diǎn)。 dubbo ZK 樹(shù)形內(nèi)部結(jié)構(gòu)示例為:
ZK 內(nèi)部服務(wù)具體示例如下:
Dubbo 可以在配置文件中指定使用注冊(cè)中心,可以使用 dubbo.registry.protocol
指定具體注冊(cè)中心類型,也可以設(shè)置 dubbo.registry.address
指定。注冊(cè)中心相關(guān)實(shí)現(xiàn)將會(huì)使用 RegistryFactory
工廠類創(chuàng)建。
RegistryFactory
接口源碼如下:
@SPI("dubbo") public interface RegistryFactory { @Adaptive({"protocol"}) Registry getRegistry(URL url); }
RegistryFactory
接口方法使用 @Adaptive
注解,這里將會(huì)使用 Dubbo SPI 機(jī)制,自動(dòng)生成代碼的一些實(shí)現(xiàn)邏輯。這里將會(huì)根據(jù) URL 中 protocol
屬性,去調(diào)用最終實(shí)現(xiàn)子類。
RegistryFactory
實(shí)現(xiàn)子類如圖所示:
AbstractRegistryFactory
將會(huì)實(shí)現(xiàn)接口的 getRegistry
方法,主要完成加鎖,并調(diào)用抽象模板方法 createRegistry
創(chuàng)建具體注冊(cè)中心實(shí)現(xiàn)類,并將其緩存在內(nèi)存中。
AbstractRegistryFactory#getRegistry
源碼如下所示:
public Registry getRegistry(URL url) { url = URLBuilder.from(url) .setPath(RegistryService.class.getName()) .addParameter(Constants.INTERFACE_KEY, RegistryService.class.getName()) .removeParameters(Constants.EXPORT_KEY, Constants.REFER_KEY) .build(); String key = url.toServiceStringWithoutResolving(); // 加鎖,防止并發(fā) LOCK.lock(); try { // 先從緩存中取 Registry registry = REGISTRIES.get(key); if (registry != null) { return registry; } //使用 Dubbo SPI 進(jìn)制創(chuàng)建 registry = createRegistry(url); if (registry == null) { throw new IllegalStateException("Can not create registry " + url); } // 放入緩存 REGISTRIES.put(key, registry); return registry; } finally { // Release the lock LOCK.unlock(); } }
注冊(cè)中心實(shí)例將會(huì)通過(guò)具體工廠類創(chuàng)建,這里我們看下 ZookeeperRegistryFactory
源碼:
public class ZookeeperRegistryFactory extends AbstractRegistryFactory { private ZookeeperTransporter zookeeperTransporter; /** * 通過(guò) Dubbo SPI 進(jìn)制注入 * @param zookeeperTransporter */ public void setZookeeperTransporter(ZookeeperTransporter zookeeperTransporter) { this.zookeeperTransporter = zookeeperTransporter; } @Override public Registry createRegistry(URL url) { return new ZookeeperRegistry(url, zookeeperTransporter); } }
ps:Dubbo SPI 機(jī)制還具有 IOC 特性,這里的ZookeeperTransporter
注入可以參考:Dubbo 擴(kuò)展點(diǎn)加載
講完注冊(cè)中心實(shí)例創(chuàng)建過(guò)程,下面深入 ZookeeperRegistry
實(shí)現(xiàn)源碼。
ZookeeperRegistry
繼承 FailbackRegistry
抽象類,所以其需要實(shí)現(xiàn)其父類抽象模板方法,下面主要了解 doRegister
與 doSubscribe
源碼 。
服務(wù)提供者需要將服務(wù)注冊(cè)到注冊(cè)中心,注冊(cè)的目的是為了讓消費(fèi)者感知到服務(wù)的存在,從而發(fā)起遠(yuǎn)程調(diào)用,另一方面也讓服務(wù)治理中心感知新的服務(wù)提供者上線。zk 模塊服務(wù)注冊(cè)代碼比較簡(jiǎn)單,直接使用 zk 客戶端在注冊(cè)中心創(chuàng)建節(jié)點(diǎn)。
ZookeeperRegistry#doRegister
實(shí)現(xiàn)源碼如下:
public void doRegister(URL url) { try { zkClient.create(toUrlPath(url), url.getParameter(Constants.DYNAMIC_KEY, true)); } catch (Throwable e) { throw new RpcException("Failed to register " + url + " to zookeeper " + getUrl() + ", cause: " + e.getMessage(), e); } }
zkClient.create
方法需要傳入兩個(gè)參數(shù)。
void create(String path, boolean ephemeral);
第一個(gè)參數(shù)為節(jié)點(diǎn)路徑,將會(huì)通過(guò) toUrlPath
將 URL 實(shí)例轉(zhuǎn)化成 ZK 中路徑格式,轉(zhuǎn)化結(jié)果如下:
## 轉(zhuǎn)化前 URL 如下: dubbo://10.20.82.31:12345/com.dubbo.example.DemoService ## 調(diào)用 `toUrlPath` 轉(zhuǎn)換之后 /dubbo/com.dubbo.example.DemoService/providers/dubbo%3A%2F%2F10.20.82.31%3A12345%2Fcom.dubbo.example.DemoService
第二個(gè)參數(shù)主要決定 ZK 節(jié)點(diǎn)類型主要取自 URL 實(shí)例對(duì)象中 dynamic
參數(shù)值,若不存在,默認(rèn)為 true
,也就是默認(rèn)將會(huì)創(chuàng)建臨時(shí)節(jié)點(diǎn)。
zkClient.create
方法里將會(huì)遞歸調(diào)用,首先父節(jié)點(diǎn)是否存在,不存在就會(huì)創(chuàng)建,直到最后一個(gè)節(jié)點(diǎn)跳出遞歸方法。
public void create(String path, boolean ephemeral) { // 創(chuàng)建永久節(jié)點(diǎn)之前需要判斷是否已存在 if (!ephemeral) { if (checkExists(path)) { return; } } // 判斷是否存在父節(jié)點(diǎn) int i = path.lastIndexOf('/'); if (i > 0) { // 遞歸創(chuàng)建父節(jié)點(diǎn) create(path.substring(0, i), false); } if (ephemeral) { // 創(chuàng)建臨時(shí)節(jié)點(diǎn) createEphemeral(path); } else { // 創(chuàng)建永久節(jié)點(diǎn) createPersistent(path); } }
最后 createEphemeral
與 createPersistent
實(shí)際創(chuàng)建節(jié)點(diǎn)操作將會(huì)交給 ZK 客戶端類,這里實(shí)現(xiàn)比較簡(jiǎn)單,可以自行參考源碼。
ps: dubbo 在 2.6.1 起將 zk 客戶端默認(rèn)使用 Curator,之前版本使用 zkclient。dubbo 2.7.1 開(kāi)始去除 zkclient 實(shí)現(xiàn),也就是說(shuō)只能使用 Curator 。
zk 臨時(shí)節(jié)點(diǎn)將會(huì)在 zk 客戶端斷開(kāi)后,自動(dòng)刪除。dubbo 服務(wù)提供者正常下線,其會(huì)主動(dòng)刪除 zk 服務(wù)節(jié)點(diǎn)。
如果服務(wù)異常宕機(jī),zk 服務(wù)節(jié)點(diǎn)就不能正常刪除,這就導(dǎo)致失效的服務(wù)一直存在 ZK 上,消費(fèi)者還會(huì)調(diào)用該失效節(jié)點(diǎn),導(dǎo)致消費(fèi)者報(bào)錯(cuò)。通過(guò) zk 臨時(shí)節(jié)點(diǎn)特性,讓 zk 服務(wù)端主動(dòng)刪除失效節(jié)點(diǎn),從而下線失效服務(wù)。
服務(wù)訂閱通常有 pull 和 push 兩種方式。pull 模式需要客戶端定時(shí)向注冊(cè)中心拉取配置,而 push 模式采用注冊(cè)中心主動(dòng)推送數(shù)據(jù)給客戶端。
dubbo zk 注冊(cè)中心采用是事件通知與客戶端拉取方式。服務(wù)第一次訂閱的時(shí)候?qū)?huì)拉取對(duì)應(yīng)目錄下全量數(shù)據(jù),然后在訂閱的節(jié)點(diǎn)注冊(cè)一個(gè) watcher。一旦目錄節(jié)點(diǎn)下發(fā)生任何數(shù)據(jù)變化,zk 將會(huì)通過(guò) watcher 通知客戶端??蛻舳私拥酵ㄖ?,將會(huì)重新拉取該目錄下全量數(shù)據(jù),并重新注冊(cè) watcher。利用這個(gè)模式,dubbo 服務(wù)就可以就做到服務(wù)的動(dòng)態(tài)發(fā)現(xiàn)。
講完訂閱的基本原理,接著深入源碼。
doSubscribe
方法需要傳入兩個(gè)參數(shù),一個(gè)為 URL 實(shí)例,另一個(gè)為 NotifyListener
,變更事件的監(jiān)聽(tīng)器。 方法內(nèi)部會(huì)根據(jù) URL 接口類型分成兩部分邏輯,全量訂閱服務(wù)與部分類別訂閱服務(wù)。
doSubscribe
方法整體源碼邏輯:
public void doSubscribe(final URL url, final NotifyListener listener) { if (Constants.ANY_VALUE.equals(url.getServiceInterface())) { // 全量訂閱邏輯 } else { // 部分類別訂閱邏輯 } }
服務(wù)治理中心(dubbo-admin),需要訂閱 service 全量接口,用以感知每個(gè)服務(wù)的狀態(tài),所以訂閱之前將會(huì)把 service 設(shè)置成 *,處理所有service。
服務(wù)消費(fèi)者或服務(wù)提供者將會(huì)走部分類別訂閱服務(wù),下面我們以消費(fèi)者視角,深入后續(xù)源碼。
文章剛開(kāi)頭講道了 zk 目錄節(jié)點(diǎn)存在四種類型,這里將會(huì)根據(jù) 根據(jù) URL 中 category
值,決定訂閱節(jié)點(diǎn)路徑。
服務(wù)提供者 URL 中 category
值默認(rèn)為 configurators
,而消費(fèi)者 URL 中category
值默認(rèn)為 providers,configurators,routers
。如果 category
類別值為 *
,將會(huì)訂閱四種類別路徑,否則將會(huì)只訂閱 providers
類型的路徑。
toCategoriesPath
源碼如下:
private String[] toCategoriesPath(URL url) { String[] categories; // 如果類別為 *,訂閱四種類型的全量數(shù)據(jù) if (Constants.ANY_VALUE.equals(url.getParameter(Constants.CATEGORY_KEY))) { categories = new String[]{Constants.PROVIDERS_CATEGORY, Constants.CONSUMERS_CATEGORY, Constants.ROUTERS_CATEGORY, Constants.CONFIGURATORS_CATEGORY}; } else { categories = url.getParameter(Constants.CATEGORY_KEY, new String[]{Constants.DEFAULT_CATEGORY}); } // 返回路徑數(shù)組 String[] paths = new String[categories.length]; for (int i = 0; i < categories.length; i++) { paths[i] = toServicePath(url) + Constants.PATH_SEPARATOR + categories[i]; } return paths; }
接著循環(huán)路徑數(shù)組,循環(huán)內(nèi)將會(huì)緩存節(jié)點(diǎn)監(jiān)聽(tīng)器,用以提高性能。
// 循環(huán)路徑數(shù)組 for (String path : toCategoriesPath(url)) { ConcurrentMap<NotifyListener, ChildListener> listeners = zkListeners.get(url); // listeners 緩存為空,創(chuàng)建緩存 if (listeners == null) { zkListeners.putIfAbsent(url, new ConcurrentHashMap<>()); listeners = zkListeners.get(url); } ChildListener zkListener = listeners.get(listener); // zkListener 緩存為空則創(chuàng)建緩存 if (zkListener == null) { listeners.putIfAbsent(listener, (parentPath, currentChilds) -> ZookeeperRegistry.this.notify(url, listener, toUrlsWithEmpty(url, parentPath, currentChilds))); zkListener = listeners.get(listener); } // 創(chuàng)建訂閱節(jié)點(diǎn) zkClient.create(path, false); // 使用 ZK 客戶端訂閱節(jié)點(diǎn) List<String> children = zkClient.addChildListener(path, zkListener); if (children != null) { // 存儲(chǔ)全量需要通知的 URL urls.addAll(toUrlsWithEmpty(url, path, children)); } } // 回調(diào) NotifyListener notify(url, listener, urls);
最終將會(huì)使用 CuratorClient.getChildren().usingWatcher(listener).forPath(path)
在 ZK 節(jié)點(diǎn)注冊(cè) watcher,并獲取目錄節(jié)點(diǎn)下所有子節(jié)點(diǎn)數(shù)據(jù)。
這里 watcher 使用 Curator 接口 CuratorWatcher
,一旦 ZK 節(jié)點(diǎn)發(fā)生會(huì)變化,將會(huì)回調(diào) CuratorWatcher#process
方法。
CuratorWatcher#process
方法源碼如下:
public void process(WatchedEvent event) throws Exception { if (childListener != null) { String path = event.getPath() == null ? "" : event.getPath(); childListener.childChanged(path, // 重新設(shè)置 watcher,并獲取節(jié)點(diǎn)下所有子節(jié)點(diǎn) StringUtils.isNotEmpty(path) ? client.getChildren().usingWatcher(this).forPath(path) : Collections.<String>emptyList()); } }
消費(fèi)者訂閱時(shí)序圖如下:
訂閱方法中我們碰到了多個(gè) listener
類,剛開(kāi)始理解時(shí)候可能有點(diǎn)亂??梢詤⒖枷旅骊P(guān)系圖理清楚這其中的關(guān)系。
listener
關(guān)系圖如下:
回調(diào)關(guān)系如圖所示:
ZK 第一次訂閱將會(huì)獲得目錄節(jié)點(diǎn)下所有子節(jié)點(diǎn),后續(xù)任意子節(jié)點(diǎn)變更,將會(huì)通過(guò) watcher 進(jìn)制回調(diào)通知。回調(diào)通知將會(huì)再次全量拉取節(jié)點(diǎn)目錄下所有子節(jié)點(diǎn)。這樣全量拉取將會(huì)有個(gè)局限,當(dāng)服務(wù)節(jié)點(diǎn)較多時(shí)將會(huì)對(duì)網(wǎng)絡(luò)造成很大的壓力。
Dubbo 2.7 之后版本引入元數(shù)據(jù)中心解決該問(wèn)題,詳情可參考,阿里技術(shù)專家詳解 Dubbo 實(shí)踐,演進(jìn)及未來(lái)規(guī)劃。
引用文中一種解決方案如下圖:
到此,關(guān)于“Dubbo如何實(shí)現(xiàn)服務(wù)的動(dòng)態(tài)發(fā)現(xiàn)”的學(xué)習(xí)就結(jié)束了,希望能夠解決大家的疑惑。理論與實(shí)踐的搭配能更好的幫助大家學(xué)習(xí),快去試試吧!若想繼續(xù)學(xué)習(xí)更多相關(guān)知識(shí),請(qǐng)繼續(xù)關(guān)注創(chuàng)新互聯(lián)網(wǎng)站,小編會(huì)繼續(xù)努力為大家?guī)?lái)更多實(shí)用的文章!
網(wǎng)站名稱:Dubbo如何實(shí)現(xiàn)服務(wù)的動(dòng)態(tài)發(fā)現(xiàn)
轉(zhuǎn)載注明:http://aaarwkj.com/article46/gpjjhg.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供企業(yè)建站、移動(dòng)網(wǎng)站建設(shè)、動(dòng)態(tài)網(wǎng)站、做網(wǎng)站、標(biāo)簽優(yōu)化、網(wǎng)站設(shè)計(jì)公司
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)