前言html
本週空閒時間利用了百分之六七十的樣子。主要將Dubbo官網文檔和本地代碼debug結合起來學習,基本看完了服務導出、服務引入以及服務調用的過程,暫未涉及路由、字典等功能。下面對這一週的收穫進行一下總結梳理。java
1、基於事件驅動的服務導出算法
提起服務導出,不要被它的名字誤導了,通俗點說就是服務的暴露和註冊。服務的暴露是指將服務端的端口開放,等待消費端來鏈接。服務的註冊即將服務信息註冊到註冊中心。針對服務暴露和註冊的具體流程,可參見博主以前的一篇文章 https://www.cnblogs.com/zzq6032010/p/11275478.html ,講述的比較詳細,暫不贅述。apache
注重提一下的是Dubbo啓動服務暴露和註冊的時機,是採用的事件驅動來觸發的,跟SpringBoot有點神似。這種經過事件驅動來觸發特定邏輯的方式,在實際開發工做中也能夠靈活使用。app
2、服務引入及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自適應擴展的精髓所在。url
除此以外,在經過getExtension方法獲取最終實現類時,還要通過wrapper類的包裝。詳見ExtensionLoader類中的以下方法:spa
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調試。
3、服務調用的疑問
以前未看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的四大負載均衡算法作一下研究,淺嘗輒止,不求甚解!