1. 程式人生 > >【2020-03-28】Dubbo原始碼雜談

【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