天天看點

【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方法時棧調用路徑是這樣的:

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

 即中間經過了三層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的四大負載均衡算法做一下研究,淺嘗辄止,不求甚解!