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