Dubbo架構理解
架構整體設計
Dubbo調用關係說明
在這裏主要由四部分組成:
- Provider: 暴露服務的服務提供方
Protocol 負責提供者和消費者之間協議交互數據
Service 真實的業務服務信息 可以理解成接口 和 實現
Container Dubbo的運行環境
- Consumer: 調用遠程服務的服務消費方
Protocol 負責提供者和消費者之間協議交互數據
Cluster 感知提供者端的列表信息
Proxy 可以理解成 提供者的服務調用代理類,由它接管 Consumer 中的接口調用邏輯
- Registry: 註冊中心,用於作爲服務發現和路由配置等工作,提供者和消費者都會在這裏進行註冊
- Monitor: 用於提供者和消費者中的數據統計,比如調用頻次,成功失敗次數等信息。
啓動和執行流程說明:
- 提供者端啓動 容器負責把Service信息加載 並通過Protocol 註冊到註冊中心
- 消費者端啓動 通過監聽提供者列表來感知提供者信息 並在提供者發生改變時 通過註冊中心及時,通知消費端
- 消費方發起請求通過Proxy模塊
- 利用Cluster模塊 來選擇真實的要發送給的提供者信息
- 交由Consumer中的Protocol 把信息發送給提供者
- 提供者同樣需要通過 Protocol 模塊來處理消費者的信息
- 最後由真正的服務提供者 Service 來進行處理
整體的調用鏈路
業務邏輯層 RPC層(遠程過程調用) Remoting (遠程數據傳輸)
整體鏈路調用的流程:
- 消費者通過Interface進行方法調用 統一交由消費者端的 Proxy 通過ProxyFactory 來進行代理對象的創建 使用到了 jdk javassist技術
2.交給Filter 這個模塊 做一個統一的過濾請求 在SPI案例中涉及過
3.接下來會進入最主要的Invoker調用邏輯
通過Directory 去配置中心讀取信息 最終通過list方法獲取所有的Invoker
通過Cluster模塊 根據選擇的具體路由規則 來選取Invoker列表
通過LoadBalance模塊 根據負載均衡策略 選擇一個具體的Invoker 來處理請求
如果執行中出現錯誤 並且Consumer階段配置了重試機制 則會重新嘗試執行 - 繼續經過Filter 進行執行功能的前後封裝 Invoker 選擇具體的執行協議
- 客戶端 進行編碼和序列化 然後發送數據
- 到達Provider中的 Server 在這裏進行 反編碼 和 反序列化的接收數據
- 使用Exporter選擇執行器
- 交給Filter 進行一個提供者端的過濾 到達 Invoker 執行器
- 通過Invoker 調用接口的具體實現 然後返回
Dubbo源碼整體設計
右邊的黑色箭頭代表層之間的依賴關係,每一層都可以剝離上層被複用,其中,Service 和 Config 層爲 API,其它各層均爲 SPI。
分層介紹:
Business 業務邏輯層
- service 業務層 包括業務代碼 比如 接口 實現類 直接面向開發者
RPC層 遠程過程調用層
- config 配置層 對外提供配置 以ServiceConfig ReferenceConfig 爲核心 可以直接初始化配置類 也可以解析配置文件生成
- proxy 服務代理層 無論是生產者 還是消費者 框架都會產生一個代理類 整個過程對上層透明 就是業務層對遠程調用無感
- registry 註冊中心層 封裝服務地址的註冊與發現 以服務的URL爲中心
- cluster 路由層 (集羣容錯層) 提供了多個提供者的路由和負載均衡 並且它橋接註冊中心 以Invoker爲核心
- monitor 監控層 RPC調用相關的信息 如 調用次數 成功失敗的情況 調用時間等 在這一層完成
- protocol 遠程調用層 封裝RPC調用 無論是服務的暴露 還是 服務的引用 都是在Protocol中作爲主功能入口 負責Invoker的整個生命週期 Dubbo中所有的模型都向Invoker靠攏
Remoting層 遠程數據傳輸層
- exchange 信息交換層 封裝請求和響應的模式 如把請求由同步 轉換成異步
- transport 網絡傳輸層 統一網絡傳輸的接口 比如 netty 和 mina 統一爲一個網絡傳輸接口
- serialize 數據序列化層 負責管理整個框架中的數據傳輸的序列化 和反序列化
服務註冊與消費源碼剖析
註冊中心Zookeeper目錄結構
例如:只有一個提供者和消費者。 com.lagou.service.HelloService 爲我們所提供的服務。
Zookeeper的目錄結構如下:
- consumers: 當前服務下面所有的消費者列表(URL)
- providers: 當前服務下面所有的提供者列表(URL)
- configuration: 當前服務下面的配置信息信息,provider或者consumer會通過讀取這裏的配置信息來獲取配置
- routers: 當消費者在進行獲取提供者的時,會通過這裏配置好的路由來進行適配匹配規則。
- 提供者會在 providers 目錄下進行自身的進行註冊。
- 消費者會在 consumers 目錄下進行自身註冊,並且監聽 provider 目錄,以此通過監聽提供者增加或者減少,實現服務發現。
- Monitor模塊會對整個服務級別做監聽,用來得知整體的服務情況。以此就能更多的對整體情況做監控。
服務的註冊過程分析
服務註冊(暴露)過程
首先 ServiceConfig 類拿到對外提供服務的實際類 ref(具體的接口實現類),然後通過 ProxyFactory 接口實現類中的 getInvoker 方法使用 ref 生成一個 AbstractProxyInvoker 實例,到這一步就完成具體服務到 Invoker 的轉化。接下來就是 Invoker 轉換到 Exporter 的過程。
查看 ServiceConfifig 類,重點查看 ProxyFactory 和 Protocol 類型的屬性 以及 ref
服務註冊暴露時序圖:
Registry中的類目錄結構
- 每個層級代表繼承自父級
- 這裏面 RegistryService 就是對外提供註冊機制的接口。
- 其下面 Registry 也同樣是一個接口,是對 RegistryService 的集成,並且繼承了 Node 接口,說明註冊中心也是基於URL去做的。
- AbstractRegistry 是對註冊中心的封裝,其主要會對本地註冊地址的封裝,主要功能在於遠程註冊中心不可用的時候,可以採用本地的註冊中心來使用。
- FailbackRegistry 從名字中可以看出來,失敗自動恢復,後臺記錄失敗請求,定時重發功能。
- 最深的一層則更多是真實的第三方渠道實現。
URL規則詳解 和 服務本地緩存
URL規則詳解
服務本地緩存
dubbo調用者需要通過註冊中心(例如:ZK)註冊信息,獲取提供者,但是如果頻繁往從ZK獲取信息,肯定會存在單點故障問題,所以dubbo提供了將提供者信息緩存在本地的方法。
Dubbo在訂閱註冊中心的回調處理邏輯當中會保存服務提供者信息到本地緩存文件當中(同步/異步兩種方式),以URL緯度進行全量保存。
Dubbo在服務引用過程中會創建registry對象並加載本地緩存文件,會優先訂閱註冊中心,訂閱註冊中心失敗後會訪問本地緩存文件內容獲取服務提供信息。
構建流程:
- 初始化AbstractRegistry構造函數,並且從系統中讀取已有的配置信息。
- 方法notify(url.getBackupUrls()) ->notify(url, listener, filterEmpty(url, urls)) -> saveProperties(url),通過尋找,這個屬性的設置值只有在一個地方: saveProperties ,這裏也有基於版本號的的更改
- doSaveProperties(long version),最終緩存的保存方法。
Dubbo 消費過程分析
首先 ReferenceConfig 類的 init 方法調用 createProxy() ,期間 使用 Protocol 調用 refer 方法生 成 Invoker 實例(如上圖中的紅色部分),這是服務消費的關鍵。接下來使用ProxyFactory把 Invoker 轉換爲客戶端需要的接口
Dubbo擴展SPI源碼剖析
getExtensionLoader 加載過程
注:
- cachedAdaptiveClass: 當前Extension類型對應的AdaptiveExtension類型(只能一個)
- cachedWrapperClasses: 當前Extension類型對應的所有Wrapper實現類型(無順序)
- cachedActivates: 當前Extension實現自動激活實現緩存(map,無序)
- cachedNames: 擴展點實現類對應的名稱(如配置多個名稱則值爲第一個)
- **getExtension獲取擴展點的方法 **
- injectExtension注入其他擴展點的實體,用於擴展點和其他的擴展點相互打通(set的方式注入)
Adaptive功能實現原理
Adaptive的主要功能是對所有的擴展點進行封裝爲一個類,通過URL傳入參數的時動態選擇需要使用的擴展點。其底層的實現原理就是動態代理。
createAdaptiveExtensionClass()方法生成具體的Adaptive代理對象,通過AdaptiveClassCodeGenerator下的generate()方法做將具體的接口實現類重新做字符串拼接插入需要提前調用的方法和URL參數判斷,最後通過org.apache.dubbo.common.compiler.Compiler 將字符串編譯成class文件。
private Class<?> createAdaptiveExtensionClass() {
String code = new AdaptiveClassCodeGenerator(type, cachedDefaultName).generate();
ClassLoader classLoader = findClassLoader();
org.apache.dubbo.common.compiler.Compiler compiler =
ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.compiler.Compiler.class)
.getAdaptiveExtension();
return compiler.compile(code, classLoader);
}
**
集羣容錯源碼剖析
集羣容錯的組件包含 Cluster、 Cluster Invoker、Directory、Router 和 LoadBalance 等。
集羣工作過程可分爲兩個階段:
- 第一個階段是在服務消費者初始化期間,集羣 Cluster 實現類爲服務消 費者創建 Cluster Invoker 實例,即上圖中的 merge 操作。
- 第二個階段是在服務消費者進行遠程調用時。以FailoverClusterInvoker爲例:
- 該類型ClusterInvoker首先會調用Directory的list方法列舉Invoker列表(可將Invoker簡單理解爲服務提供者)。Directory的用途是保存Invoker列表,可簡單類比爲List。其實現類RegistryDirectory是一個動態服務目錄,可感知註冊中心配置的變化,它所持有的Invoker列表會隨着註冊中心內容的變化而變化。
- 每次變化後,RegistryDirectory會動態增刪Invoker,並調用Router的route方法進行路由,過濾掉不符合路由規則的Invoker。
- 當FailoverClusterInvoker拿到Directory返回的Invoker列表後,它會通過LoadBalance從Invoker列表中選擇一個Invoker。
- 最後FailoverClusterInvoker會將參數傳給LoadBalance選擇出的Invoker實例的invoker方法,進行真正的遠程調用。
Dubbo 主要提供了這樣幾種容錯方式:
- Failover Cluster - 失敗自動切換 失敗時會重試其它服務器
- Failfast Cluster - 快速失敗 請求失敗後快速返回異常結果 不重試
- Failsafe Cluster - 失敗安全 出現異常 直接忽略 會對請求做負載均衡
- Failback Cluster - 失敗自動恢復 請求失敗後 會自動記錄請求到失敗隊列中
- Forking Cluster - 並行調用多個服務提供者 其中有一個返回 則立即返回結果
信息緩存接口Directory
Directory是Dubbo中的一個接口,主要用於緩存當前可以被調用的提供者列表信息。我們在消費者進 行調用時都會通過這個接口來獲取所有的提供者列表,再進行後續處理。
public interface Directory<T> extends Node {
//獲取服務類型
Class<T> getInterface();
//獲取本次調用可以使用的提供者信息
List<Invoker<T>> list(Invocation invocation) throws RpcException;
//獲取所有提供者信息
List<Invoker<T>> getAllInvokers();
URL getConsumerUrl();
}
RouterChain#route 方法。這裏所做的就是依次遍歷所有的路由,然後分別執行並返回。這 也就是整體的路由規則的實現。
路由規則實現原理
RouterChain 中的 Router下的子類 ConditionRouter 爲例
這兩個屬性比較關鍵,這兩個屬性也是判斷的關鍵。
init方法做路由規則的初始化,也對上面的whenCondition與thenCondition做了初始化,其中paseRule方法是解析路由規則的方法並返回Map其值MatchPair存儲了匹配條件和不匹配條件的Set集合
MatchPair
route方法則是篩選出符合條件的invoke並進行返回(List)
Cluster組件
Cluster 主要用於代理真正的Invoker執行時做處理,提供了多種容錯方案。
這裏以默認的FailoverCluster爲例
通過 AbstractClusterInvoker#invoke 調用 FailoverClusterInvoker#doInvoke
負載均衡實現原理
Cluster 中經過負載選擇真正 Invoker 的代碼
以RandomLoadBalance爲例,doSelect負責處理具體的負載均衡操作。
Invoker執行邏輯
Invoker是真實執行請求的組件。
以DubboInvoker調用爲例
ExchangeClient爲主要數據傳輸的客戶端,其繼承了HeaderExchangeChannel接口
doInvoke方法中根據isOneway判斷是否執行異步請求
ExchangeClient 接口下的 **request **方法爲真實發送消息的方法