1. 程式人生 > 程式設計 >如果有人問你 Dubbo 中註冊中心工作原理,就把這篇文章給他

如果有人問你 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