如果有人問你 Dubbo 中注冊中心工作原理,就把這篇文章給他


注冊中心作用

開篇首先想思考一個問題,沒有注冊中心 Dubbo 還能玩下去嗎?

當然可以,只要知道服務提供者地址相關信息,消費者配置之后就可以調用。如果只有幾個服務,這么玩當然沒問題。但是生產服務動輒成千上百,如果每個服務都需要手寫配置信息,想象一下是多么麻煩。

好吧,如果上面的問題都不是事的話,試想一下如果一個服務提供者在運行過程中宕機,消費者怎么辦?消費者不知情,所以它還會不斷把請求發往服務提供者,然后不斷失敗。這個時候唯一的辦法就是修改服務地址信息,然后重啟服務。

可以看到如果沒有注冊中心,分布式環境中服務查找發現將會非常麻煩,一切需要手工配置,無法完成自動化。所以這里就需要一個第三者,協調服務提供者與消費者之間關系,這就是注冊中心。

注冊中心主要作用如下:

  1. 動態加入,服務提供者通過注冊中心動態的把自己暴露給消費者,無需消費者逐個更新配置文件。
  2. 動態發現服務,消費者可以動態發現新的服務,無需重啟生效。
  3. 統一配置,避免本地配置導致每個服務配置不一致。
  4. 動態調整,注冊中心支持參數動態調整,新參數自動更新到所有相關的服務節點。
  5. 統一管理,依靠注冊中心數據,可以統一管理配置服務節點。

注冊中心工作流程

注冊中心工作流程總體比較簡單,流程圖大致如下:

dubbo注冊中心.png

主要工作流程可以分為如下幾步:

  1. 服務提供者啟動之后,會將服務注冊到注冊中心。
  2. 消費者啟動之后主動訂閱注冊中心上提供者服務,從而獲取到當前所有可用服務,同時留下一個回調函數。
  3. 若服務提供者新增或下線,注冊中心將通過第二步的注冊的回調函數通知消費者。
  4. dubbo-admin(服務治理中心)將會會訂閱服務提供者以及消費者,從而可以在控制台管理所有服務提供者以及消費者。

Dubbo 之前版本主要可以使用 ZooKeeper,Redis 作為注冊中心 ,而隨着 Dubbo 版本不斷更新,目前還支持 nacos,consul,etcd 等做為注冊中心。

Dubbo 注冊中心核心源碼

ps: 以下源碼基於 dubbo 2.7.3 版本

注冊中心實現使用模板模式,源碼位於 dubbo-registry 模塊,類關系如下圖:

image.png

最上層的 RegistryService 接口定義了核心方法,分別為注冊,取消注冊,訂閱,取消訂閱以及查詢。

image.png

中間層抽象類主要實現通用邏輯,如:AbstractRegistry 實現緩存機制,FailbackRegistry 實現失敗重試功能。

底層 ZookeeperRegistry等為具體實現類,實現與 ZooKeeper 等注冊中心交互的邏輯。

接下去我們具體分析 AbstractRegistryFailbackRegistry 邏輯。

AbstractRegistry 緩存實現的原理

如果每次服務調用都需要調用注冊中心實時查詢可用服務列表,不但會讓注冊中心承受巨大的流量壓力,還會產生額外的網絡請求,導致系統性能下降。

其次注冊中心需要非強依賴,其宕機不能影響正常的服務調用。

基於以上幾點,注冊中心模塊在 AbstractRegistry 類中實現通用的緩存機制。這里的緩存可以分為兩類,內存服務緩存以及磁盤文件緩存。

內存服務緩存

內存服務緩存很好理解也最容易實現,AbstractRegistry使用一個 ConcurrentMap保存相關信息。

 private final ConcurrentMap<URL, Map<String, List<URL>>> notified = new ConcurrentHashMap<>();

這個集合中 key 為消費者的 URL,而 value 為一個 Map 集合。這個內層 Map 集合使用服務目錄作為 key,分別為 providers,routers,configurators,consumers 四類,value 則是對應服務列表集合。

磁盤文件緩存

由於服務重啟就會導致內存緩存消失,所以額外增加磁盤文件緩存。

文件緩存默認位置位於 ${user.home}/.dubbo/文件夾,文件名為dubbo-registry-${application.name}-${register_address}.cache。可以設置 dubbo.registry.file 配置信息從而修改默認配置,實現源碼如下:

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:\Users\xxx/.dubbo/dubbo-registry-dubbo-auto-configure-consumer-sample-127.0.0.1:2181.cache

緩存文件內容使用 properties 配置文件格式,即 key=value 格式。key為服務接口名稱,value 為服務列表,由於服務可能存在多個,將會使用空格分隔。

緩存文件的加載

dubbo 程序初始化的時候,AbstractRegistry 構造函數將會從本地磁盤文件中將數據讀取到 Properties 對象實例中,后續都將會先寫入 Properties,最后再將里面信息再寫入文件。

緩存初始化的源碼為下圖。

loadProperties.png

緩存文件的保存與更新

緩存文件將會通過 AbstractRegistry#notify 方法保存或更新。客戶端第一次訂閱服務獲取的全量數據,或者后續回調中獲取到新數據,都將會調用 AbstractRegistry#notify 方法,用來更新內存緩存以及文件緩存。

notify 方法源碼如下圖:

notify.png

在保存文件緩存方法中,首先把根據 URL 取出的數據,拼接成字符串,然后寫入上面提到過的 properties 對象中,最后輸出到文件中。

這里可以選擇兩種保存方式,同步或異步。由於 notify 可能被多次調用,為了提高系統能,系統默認使用異步方式保存。

saveProperties 方法源碼如下:

saveProperties.png

doSaveProperties 方法最終將會將信息寫入緩存。考慮到保存方法可能會被多個線程同時調用,這里使用 CAS 方法,首先比較版本大小,若小於,代表有新線程正在寫入信息,本次更新直接丟棄。

其次考慮到多個 dubbo 應用可能共用一份緩存文件,所以這里使用文件排他鎖當做分布式鎖,防止多個應用並發操作同一份文件。

一旦文件寫入異常或者獲取鎖失敗,保存操作將會不斷重試,直到超過最大次數。

ps: dubbo 2.7.2 之前重試沒有設置最大次數,如果文件沒有權限保存,保存將會一直失敗,異步線程將會陷入死循環。

doSaveProperties 方法源碼如下:

doSaveProperties.png

FailbackRegistry 重試機制

FailbackRegistry 繼承 AbstractRegistry,實現了 registersubscribe等通用法,並增加 doRegisterdoSubscribe 等模板方法,交由子類實現。

如果 doRegister 等模板方法發生異常,會將失敗任務放入集合,然后定時再次調用模板方法。

FailbackRegistry 失敗重試集合分別為:

retrytaskmap.png

subscribe 方法為例,這里將會調用這些 doSubscribe 的模板方法。如果發生異常將會讀取緩存文件中內容,然后加載服務。最后新建異步定時任務加入重試集合中,然后由定時器去重試這些任務。

FailbackRegistry#subscribe 方法源碼:

subscribe.png

addFailedSubscribed 中將會新建定時任務,然后交由定時器執行。定時任務默認最大重試次數為 3 次,調用時間間隔默認為 5 s。

addFailedSubscribed 源碼如下:

addFailedSubscribed.png

其他失敗重試任務都比較類似,全都繼承自 AbstractRetryTask 父類,類關系如下圖。

image.png

總結

本文主要講述注冊中心作用,工作流程,通用緩存機制以及失敗重試機制。從中可以學到模板模式,以及多線程並發技巧。

這里沒有涉及到具體注冊中心實現,由於目前最主要使用 ZooKeeper 作為注冊中心,所以下篇將會聊聊 ZooKeeper 注冊中心原理,敬請期待。

幫助書籍

『深入理解 Apache Dubbo 與實戰』

其他平台.png


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM