最近我拜讀了mindwind的一片博客文章深入淺出 RPC - 深入篇,希望通過Dubbo深入學習RPC架構設計,在此結合RPC架構的原理,解析Dubbo是如何實現RPC架構的。
RPC架構模型
RPC架構的主要目的是在構建分布式系統時,調用遠程方法就如同調用本地方法一樣方便快捷,簡化開發,提高效率。
我們看看下面這張圖,了解一下RPC架構的主要組成部分及調用關系:
以上圖片引自mindfloating的博客
上圖左側是調用者,右側是方法提供端。我們分別解釋一下上圖的各模塊的職責:
從左側開始,Caller是調用者;RpcClient是Rpc協議定義的客戶端,包括遠程接口的地址列表及調用方式的信息等,Caller導入RpcClient;RemoteAPI表示遠程接口;RpcProxy是一個對遠程方法調用的代理組件,封裝了遠程過程調用的細節,可以包括加載地址列表,尋址,調用集群模塊實現負載均衡、高可用,查找調用信息,調用RpcInvoker等;RpcInvoker是具體的RemoteAPI的調用者封裝對象,它包含了一個具體接口方法的地址、調用方式等信息,並提供了調用處理方法;RpcConnector負責封裝實現網絡通信細節,如建立會話通道,發送數據請求和接收返回值數據;RpcProtocol是Rpc協議規范,定義了數據序列化和反序列化方法,如何解析地址信息等細節。
右側為接口服務端,RpcAcceptor類似RpcConnector,封裝實現網絡通信細節,接收請求數據,發送本地接口處理后的返回值給客戶端;RpcProcessor負責線程池管理,發起本地方法調用等;服務端RpcInvoker是本地API的調用者,通過反射技術調用指定接口的方法;Callee是服務端的本地接口類,導出RPC服務為RpcServer,客戶端Caller將其作為RpcClient導入.
我們按照以上RPC的調用流程和各組件職責,聊一聊Dubbo是如何實現的。
Dubbo的RPC架構實現
Dubbo的調用關系如下圖所示,以注冊中心Registry為中心,Provider即服務提供者,將服務export出來注冊(1、register)存儲到Registry,Consumer即客戶端調用者,從Registry訂閱所關心的服務(2、subscribe),首次訂閱時拉取訂閱服務所有的地址列表(服務提供者注冊到Registry上的信息),當訂閱服務有更新(地址變更或有新的地址注冊加入),Consumer收到注冊中心的服務更新通知(3、notify),以上是服務端export的過程。
客戶端發起RPC調用時,首先從服務地址列表里refer()指定的服務,這其中有服務尋址和從集群中篩選最終指向一個服務的過程,這就是import的過程。得到某個服務指向,則發起遠程調用(4、invoke)。
以上圖片引自Dubbo官方用戶手冊
那么Dubbo具體是如何實現RPC的整個過程的呢?對應RPC模型,Dubbo相應的組件又是如何實現和交互的?下圖展示了Dubbo整個設計思路。
以上圖片引自Dubbo官方用戶手冊
圖例說明:
- 圖中左邊淡藍背景的為服務消費方使用的接口,右邊淡綠色背景的為服務提供方使用的接口,位於中軸線上的為雙方都用到的接口。
- 圖中從下至上分為十層,各層均為單向依賴,右邊的黑色箭頭代表層之間的依賴關系,每一層都可以剝離上層被復用,其中,Service 和 Config 層為 API,其它各層均為 SPI。
- 圖中綠色小塊的為擴展接口,藍色小塊為實現類,圖中只顯示用於關聯各層的實現類。
- 圖中藍色虛線為初始化過程,即啟動時組裝鏈,紅色實線為方法調用過程,即運行時調時鏈,紫色三角箭頭為繼承,可以把子類看作父類的同一個節點,線上的文字為調用的方法。
接下來我們對應RPC架構模型的組件談談Dubbo的設計。
export和import
Dubbo服務端接口export(導出)是將接口信息注冊到注冊中心Registry的過程。而客戶端import(導入)遠程接口是通過從注冊中心Registry訂閱遠程服務接口,收到通知后拉取到本地的過程。
注冊和訂閱的過程,不需要修改服務端本地的類和方法,只需保證客戶端和服務端共同引用一個包含接口的jar包。服務端和客戶端分別編寫簡單的dubbo接口配置xml文件(或注解的方式),容器啟動時就自動注冊和訂閱了。
客戶端調用的過程
看上圖Config層的橙色小圓點,紅色實線剪頭為調用鏈。客戶端invoke遠程API(Interface),實際是調用了Proxy層的RpcProxy,proxy又調用集群組件Cluster,從集群中篩選出一個Invoker作為調用者發起調用。
我們看到,在Cluster層中篩選的過程調用了Directory(實現類為RegistryDirectory)、Router、LoadBalance,分別通過Directory實現了服務的高可用,通過Router實現了智能路由功能,通過LoadBalance實現了負載均衡。
從集群模塊中篩選出一個Invoker后執行invoke()方法執行方法調用,到了Protocol協議層,經過Filter組件做攔截過濾處理,如用戶名、密碼驗證等可在此處理。過濾通過后,調用Protocol實現類如DubboProtocol或HessianProtocol等的invoke()方法。
具體的協議實現類(如DubboProtocol)會請求ExchangeClient組件,它封裝了具體的數據通訊細節,是底層數據通信的代理層。因此它自然會調用底層的通信組件(默認是Netty)實現Client建立連接、Server綁定端口和數據傳輸(request、return)的功能。
數據傳輸前需要數據序列化,服務端接收到數據需要反序列化,這些都靠序列化組件實現。Codec是序列化組件的代理層,具體序列化協議,默認是Hessian,還可選擇Kryo,Thrift(被Dubbo改造,與原Thrift不兼容),dubbo, hessian2, java, json等,具體參見Dubbo用戶手冊。
服務端調用
服務端接收到客戶端的請求后,反序列化數據,通過DubboHandler協議處理請求,找到注冊的本地Exporter,觸發invoke(),經過過濾器處理后,調用Invoker代理層,觸發真正的本地接口調用,返回數據序列化后發送給客戶端。
以上內容以RPC模型為基礎,分析總結了Dubbo實現RPC的大體流程,后續我還會分別針對某些組件展開討論Dubbo的設計實現。