【2020-03-28】Dubbo原始碼雜談
前言
本週空閒時間利用了百分之六七十的樣子。主要將Dubbo官網文件和原生代碼debug結合起來學習,基本看完了服務匯出、服務引入以及服務呼叫的過程,暫未涉及路由、字典等功能。下面對這一週的收穫進行一下總結梳理。
一、基於事件驅動的服務匯出
提起服務匯出,不要被它的名字誤導了,通俗點說就是服務的暴露和註冊。服務的暴露是指將服務端的埠開放,等待消費端來連線。服務的註冊即將服務資訊註冊到註冊中心。針對服務暴露和註冊的具體流程,可參見博主之前的一篇文章 https://www.cnblogs.com/zzq6032010/p/11275478.html ,講述的比較詳細,暫不贅述。
注重提一下的是Dubbo啟動服務暴露和註冊的時機,是採用的事件驅動來觸發的,跟SpringBoot有點神似。這種通過事件驅動來觸發特定邏輯的方式,在實際開發工作中也可以靈活使用。
二、服務引入及SPI
對於Dubbo的SPI自適應擴充套件,可參見博主之前的一篇文章 https://www.cnblogs.com/zzq6032010/p/11219611.html,但此篇文章當時寫的比較淺顯,還未悟得全部。
下面以Protocol類為例,看一下在ServiceConfig類中的成員變數 Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension() 是什麼樣子。
1 package org.apache.dubbo.rpc; 2 import org.apache.dubbo.common.extension.ExtensionLoader; 3 public class Protocol$Adaptive implements org.apache.dubbo.rpc.Protocol { 4 5 public void destroy() { 6 throw new UnsupportedOperationException("The method public abstract void org.apache.dubbo.rpc.Protocol.destroy() of interface org.apache.dubbo.rpc.Protocol is not adaptive method!"); 7 } 8 9 public int getDefaultPort() { 10 throw new UnsupportedOperationException("The method public abstract int org.apache.dubbo.rpc.Protocol.getDefaultPort() of interface org.apache.dubbo.rpc.Protocol is not adaptive method!"); 11 } 12 13 public org.apache.dubbo.rpc.Exporter export(org.apache.dubbo.rpc.Invoker arg0) throws org.apache.dubbo.rpc.RpcException { 14 if (arg0 == null) throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument == null"); 15 if (arg0.getUrl() == null) throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument getUrl() == null"); 16 org.apache.dubbo.common.URL url = arg0.getUrl(); 17 String extName = ( url.getProtocol() == null ? "dubbo" : url.getProtocol() ); 18 if(extName == null) throw new IllegalStateException("Failed to get extension (org.apache.dubbo.rpc.Protocol) name from url (" + url.toString() + ") use keys([protocol])"); 19 org.apache.dubbo.rpc.Protocol extension = (org.apache.dubbo.rpc.Protocol)ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.Protocol.class).getExtension(extName); 20 return extension.export(arg0); 21 } 22 23 public org.apache.dubbo.rpc.Invoker refer(java.lang.Class arg0, org.apache.dubbo.common.URL arg1) throws org.apache.dubbo.rpc.RpcException { 24 if (arg1 == null) throw new IllegalArgumentException("url == null"); 25 org.apache.dubbo.common.URL url = arg1; 26 String extName = ( url.getProtocol() == null ? "dubbo" : url.getProtocol() ); 27 if(extName == null) throw new IllegalStateException("Failed to get extension (org.apache.dubbo.rpc.Protocol) name from url (" + url.toString() + ") use keys([protocol])"); 28 org.apache.dubbo.rpc.Protocol extension = (org.apache.dubbo.rpc.Protocol)ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.Protocol.class).getExtension(extName); 29 return extension.refer(arg0, arg1); 30 } 31 32 public java.util.List getServers() { 33 throw new UnsupportedOperationException("The method public default java.util.List org.apache.dubbo.rpc.Protocol.getServers() of interface org.apache.dubbo.rpc.Protocol is not adaptive method!"); 34 } 35 }
這就是getAdaptiveExtension()之後得到的代理類,可見在初始化ServiceConfig時先獲取的protocol只是一個代理Protocol類,程式執行時再通過傳入的Url來判斷具體使用哪個Protocol實現類。這才是SPI自適應擴充套件的精髓所在。
除此之外,在通過getExtension方法獲取最終實現類時,還要經過wrapper類的包裝。詳見ExtensionLoader類中的如下方法:
1 private T createExtension(String name) { 2 Class<?> clazz = getExtensionClasses().get(name); 3 if (clazz == null) { 4 throw findException(name); 5 } 6 try { 7 T instance = (T) EXTENSION_INSTANCES.get(clazz); 8 if (instance == null) { 9 EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance()); 10 instance = (T) EXTENSION_INSTANCES.get(clazz); 11 } 12 injectExtension(instance); 13 Set<Class<?>> wrapperClasses = cachedWrapperClasses; 14 if (CollectionUtils.isNotEmpty(wrapperClasses)) { 15 for (Class<?> wrapperClass : wrapperClasses) { 16 instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance)); 17 } 18 } 19 initExtension(instance); 20 return instance; 21 } catch (Throwable t) { 22 throw new IllegalStateException("Extension instance (name: " + name + ", class: " + 23 type + ") couldn't be instantiated: " + t.getMessage(), t); 24 } 25 }
如果介面存在包裝類,則在第16行進行wrapper類的處理,將當前instance封裝進包裝類中,再返回包裝類的例項,即通過這一行程式碼實現了擴充套件類的裝飾器模式改造。
此處同樣以Protocol類為例,Url中的協議是registry,那麼我最終執行到RegistryProtocol的export方法時棧呼叫路徑是這樣的:
即中間經過了三層Wrapper的封裝,每層都有自己特定的功能,且各層之間互不影響。Dubbo在很多自適應擴充套件介面處加了類似這樣的裝飾擴充套件,程式的可擴充套件設計還可以這樣玩,Interesting!
服務引入的流程大體是這樣的:消費端從註冊中心獲取服務端資訊,封裝成Invoker,再封裝成代理類注入消費端Spring容器。流程比較簡單,可自行根據上一節的內容debug除錯。
三、服務呼叫的疑問
之前未看Dubbo原始碼時一直有一個疑問:dubbo的消費端代理類呼叫服務端介面進行消費時,是通過netty將訊息傳送過去的,服務端在接收到訊息後,是如何呼叫的服務端目標類中的方法?反射嗎?反射可以呼叫到方法,但是沒法解決依賴的問題,而且正常情況服務端呼叫應該也是Spring容器中已經例項化好的的服務物件,那是如何通過netty的訊息找到Spring中的物件的?
實際dubbo處理的很簡單,只要在服務暴露的時候將暴露的服務自己存起來就好了,等消費端傳過來訊息的時候,直接去map裡面取,取到的就是Spring中封裝的那個服務物件,very easy。
服務呼叫的流程大體是這樣的:呼叫之後通過client遠端連線到server,在server端維護了暴露服務的一個map,服務端接收到請求後去map獲取Exporter,exporter中有服務端封裝好的Invoker,持有Spring中的服務bean,最終完成呼叫。中間還涉及很多細節,比如netty的封裝與呼叫,序列化反序列化,負載均衡和容錯處理等。
小結
Dubbo作為一個優秀的rpc服務框架,其優勢不止在於它的rpc過程,還在於更多細節模組的實現以及可擴充套件的設計,比如序列化處理、負載均衡、容錯、netty的執行緒排程、路由、字典... 內容挺多的,後面打算針對dubbo的四大負載均衡演算法做一下研究,淺嘗輒止,不求甚解!
&n