【2020-03-28】Dubbo源碼雜談

前言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的四大負載均衡算法作一下研究,淺嘗輒止,不求甚解!

相關文章
相關標籤/搜索