輕鬆讀懂 Dubbo,手寫2W+文字 dubbo 原理解析(下篇)

轉載:http://blog.itpub.net/70000181/viewspace-2776151/

01 服務發現

1.1 服務發現流程

整體duubo的服務消費原理

Dubbo 框架做服務消費也分爲兩大部分 , 第一步通過持有遠程服務實例生成Invoker,這個Invoker 在客戶端是核心的遠程代理對象 。 第二步會把Invoker 通過動態代理轉換成實現用戶接口的動態代理引用 。

服務消費方引用服務的藍色初始化鏈,時序圖

1.2 源碼分析應用

引用入口:ReferenceBean 的getObject 方法,該方法定義在Spring 的FactoryBean 接口中,ReferenceBean 實現了這個方法。

public Object getObject() throws Exception {
   return get();
}
public synchronized T get() {
   // 檢測 ref 是否爲空,爲空則通過 init 方法創建
   if (ref == null) {
       // init 方法主要用於處理配置,以及調用 createProxy 生成代理類
       init();
   }
   return ref;
}

Dubbo 提供了豐富的配置,用於調整和優化框架行爲,性能等。Dubbo 在引用或導出服務時,首先會對這些配置進行檢查和處理,以保證配置的正確性。

private void init() {
   // 創建代理類
   ref = createProxy(map);
}

此方法代碼很長,主要完成的配置加載,檢查,以及創建引用的代理對象。這裏要從createProxy 開始看起。從字面意思上來看,createProxy 似乎只是用於創建代理對象的。但實際上並非如此,該方法還會調用其他方法構建以及合併Invoker 實例。具體細節如下。

private T createProxy(Map<String, String> map) {
   URL tmpUrl = new URL("temp", "localhost", 0, map);
...........
isDvmRefer = InjvmProtocol . getlnjvmProtocol( ) . islnjvmRefer(tmpUrl)
   // 本地引用略
   if (isJvmRefer) {
   } else {
       // 點對點調用略
       if (url != null && url.length() > 0) {
           
       } else {
           // 加載註冊中心 url
           List<URL> us = loadRegistries(false);
           if (us != null && !us.isEmpty()) {
               for (URL u : us) {
                   URL monitorUrl = loadMonitor(u);
                   if (monitorUrl != null) {
                       map.put(Constants.MONITOR_KEY,
URL.encode(monitorUrl.toFullString()));
                   }
                   // 添加 refer 參數到 url 中,並將 url 添加到 urls 中
                   urls.add(u.addParameterAndEncoded(Constants.REFER_KEY,
StringUtils.toQueryString(map)));
               }
           }
       }
       // 單個註冊中心或服務提供者(服務直連,下同)
       if (urls.size() == 1) {
           // 調用 RegistryProtocol 的 refer 構建 Invoker 實例
           invoker = refprotocol.refer(interfaceClass, urls.get(0));
       // 多個註冊中心或多個服務提供者,或者兩者混合
       } else {
           List<Invoker<?>> invokers = new ArrayList<Invoker<?>>();
           URL registryURL = null;
           // 獲取所有的 Invoker
           for (URL url : urls) {
               // 通過 refprotocol 調用 refer 構建 Invoker,refprotocol 會在運行時
               // 根據 url 協議頭加載指定的 Protocol 實例,並調用實例的 refer 方法
               invokers.add(refprotocol.refer(interfaceClass, url));
               if (Constants.REGISTRY_PROTOCOL.equals(url.getProtocol())) {
                   registryURL = url;
               }
           }
           if (registryURL != null) {
               // 如果註冊中心鏈接不爲空,則將使用 AvailableCluster
               URL u = registryURL.addParameter(Constants.CLUSTER_KEY,
AvailableCluster.NAME);
               // 創建 StaticDirectory 實例,並由 Cluster 對多個 Invoker 進行合併
               invoker = cluster.join(new StaticDirectory(u, invokers));
           } else {
               invoker = cluster.join(new StaticDirectory(invokers));
           }
       }
   }
    //省略無關代碼...
    // 生成代理類
   return (T) proxyFactory.getProxy(invoker);
}

上面代碼很多,不過邏輯比較清晰。
1、如果是本地調用,直接jvm 協議從內存中獲取實例
2、如果只有一個註冊中心,直接通過Protocol 自適應拓展類構建Invoker 實例接口
3、如果有多個註冊中心,此時先根據url 構建Invoker。然後再通過Cluster 合併多個Invoker,最後調用ProxyFactory 生成代理類

(1)創建客戶端

在服務消費方,Invoker 用於執行遠程調用。Invoker 是由Protocol 實現類構建而來。Protocol 實現類有很多,這裏分析DubboProtocol

public <T> Invoker<T> refer(Class<T> serviceType, URL url) throws RpcException {
   optimizeSerialization(url);
   // 創建 DubboInvoker
   DubboInvoker<T> invoker = new DubboInvoker<T>(serviceType, url,
getClients(url), invokers);
   invokers.add(invoker);
   return invoker;
}

上面方法看起來比較簡單,創建一個DubboInvoker。通過構造方法傳入遠程調用的client對象。默認情況下,Dubbo 使用NettyClient 進行通信。接下來,我們簡單看一下getClients 方法的邏輯。

private ExchangeClient[] getClients(URL url) {
   // 是否共享連接
   boolean service_share_connect = false;
// 獲取連接數,默認爲0,表示未配置
   int connections = url.getParameter(Constants.CONNECTIONS_KEY, 0);
   // 如果未配置 connections,則共享連接
   if (connections == 0) {
       service_share_connect = true;
       connections = 1;
   }
   ExchangeClient[] clients = new ExchangeClient[connections];
   for (int i = 0; i < clients.length; i++) {
       if (service_share_connect) {
           // 獲取共享客戶端
           clients[i] = getSharedClient(url);
       } else {
           // 初始化新的客戶端
           clients[i] = initClient(url);
       }
   }
   return clients;
}

這裏根據connections 數量決定是獲取共享客戶端還是創建新的客戶端實例,getSharedClient 方法中也會調用initClient 方法,因此下面我們一起看一下這個方法。

private ExchangeClient initClient(URL url) {
   // 獲取客戶端類型,默認爲 netty
   String str = url.getParameter(Constants.CLIENT_KEY,
url.getParameter(Constants.SERVER_KEY, Constants.DEFAULT_REMOTING_CLIENT));
   //省略無關代碼
   ExchangeClient client;
   try {
       // 獲取 lazy 配置,並根據配置值決定創建的客戶端類型
       if (url.getParameter(Constants.LAZY_CONNECT_KEY, false)) {
           // 創建懶加載 ExchangeClient 實例
           client = new LazyConnectExchangeClient(url, requestHandler);
       } else {
           // 創建普通 ExchangeClient 實例
           client = Exchangers.connect(url, requestHandler);
       }
   } catch (RemotingException e) {
       throw new RpcException("Fail to create remoting client for service...");
   }
   return client;
}

initClient 方法首先獲取用戶配置的客戶端類型,默認爲netty。下面我們分析一下Exchangers 的connect 方法。

public static ExchangeClient connect(URL url, ExchangeHandler handler) throws
RemotingException {
   // 獲取 Exchanger 實例,默認爲 HeaderExchangeClient
   return getExchanger(url).connect(url, handler);
}

如上,getExchanger 會通過SPI 加載HeaderExchangeClient 實例,這個方法比較簡單,大家自己看一下吧。接下來分析HeaderExchangeClient 的實現。

public ExchangeClient connect(URL url, ExchangeHandler handler) throws
RemotingException {
   // 這裏包含了多個調用,分別如下:
   // 1. 創建 HeaderExchangeHandler 對象
   // 2. 創建 DecodeHandler 對象
   // 3. 通過 Transporters 構建 Client 實例
   // 4. 創建 HeaderExchangeClient 對象
   return new HeaderExchangeClient(Transporters.connect(url, new
DecodeHandler(new HeaderExchangeHandler(handler))), true);
}

這裏的調用比較多,我們這裏重點看一下Transporters 的connect 方法。如下:

public static Client connect(URL url, ChannelHandler... handlers) throws
RemotingException {
   if (url == null) {
       throw new IllegalArgumentException("url == null");
       }
   ChannelHandler handler;
   if (handlers == null || handlers.length == 0) {
       handler = new ChannelHandlerAdapter();
   } else if (handlers.length == 1) {
       handler = handlers[0];
   } else {
       // 如果 handler 數量大於1,則創建一個 ChannelHandler 分發器
       handler = new ChannelHandlerDispatcher(handlers);
   }
   
   // 獲取 Transporter 自適應拓展類,並調用 connect 方法生成 Client 實例
   return getTransporter().connect(url, handler);
}

如上,getTransporter 方法返回的是自適應拓展類,該類會在運行時根據客戶端類型加載指定的Transporter 實現類。若用戶未配置客戶端類型,則默認加載NettyTransporter,並調用該類的connect 方法。如下:

public Client connect(URL url, ChannelHandler listener) throws RemotingException
{
   // 創建 NettyClient 對象
   return new NettyClient(url, listener);
}

(2)註冊

這裏就已經創建好了NettyClient對象。關於DubboProtocol 的refer 方法就分析完了。接下來,繼續分析RegistryProtocol 的refer 方法邏輯。

public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
   // 取 registry 參數值,並將其設置爲協議頭
   url = url.setProtocol(url.getParameter(Constants.REGISTRY_KEY,
Constants.DEFAULT_REGISTRY)).removeParameter(Constants.REGISTRY_KEY);
   // 獲取註冊中心實例
   Registry registry = registryFactory.getRegistry(url);
   if (RegistryService.class.equals(type)) {
       return proxyFactory.getInvoker((T) registry, type, url);
   }
   // 將 url 查詢字符串轉爲 Map
   Map<String, String> qs =
StringUtils.parseQueryString(url.getParameterAndDecoded(Constants.REFER_KEY));
   // 獲取 group 配置
   String group = qs.get(Constants.GROUP_KEY);
   if (group != null && group.length() > 0) {
       if ((Constants.COMMA_SPLIT_PATTERN.split(group)).length > 1
               || "*".equals(group)) {
           // 通過 SPI 加載 MergeableCluster 實例,並調用 doRefer 繼續執行服務引用邏輯
           return doRefer(getMergeableCluster(), registry, type, url);
       }
   }
   
   // 調用 doRefer 繼續執行服務引用邏輯
   return doRefer(cluster, registry, type, url);
}

上面代碼首先爲url 設置協議頭,然後根據url 參數加載註冊中心實例。然後獲取group 配置,根據group 配置決定doRefer 第一個參數的類型。這裏的重點是doRefer 方法,如下:

private <T> Invoker<T> doRefer(Cluster cluster, Registry registry, Class<T>
type, URL url) {
   // 創建 RegistryDirectory 實例
   RegistryDirectory<T> directory = new RegistryDirectory<T>(type, url);
   // 設置註冊中心和協議
   directory.setRegistry(registry);
   directory.setProtocol(protocol);
   Map<String, String> parameters = new HashMap<String, String>
(directory.getUrl().getParameters());
   // 生成服務消費者鏈接
   URL subscribeUrl = new URL(Constants.CONSUMER_PROTOCOL,
parameters.remove(Constants.REGISTER_IP_KEY), 0, type.getName(), parameters);
   // 註冊服務消費者,在 consumers 目錄下新節點
   if (!Constants.ANY_VALUE.equals(url.getServiceInterface())
           && url.getParameter(Constants.REGISTER_KEY, true)) {
       registry.register(subscribeUrl.addParameters(Constants.CATEGORY_KEY,
Constants.CONSUMERS_CATEGORY,
               Constants.CHECK_KEY, String.valueOf(false)));
   }
   // 訂閱 providers、configurators、routers 等節點數據
   directory.subscribe(subscribeUrl.addParameter(Constants.CATEGORY_KEY,
           Constants.PROVIDERS_CATEGORY
                   + "," + Constants.CONFIGURATORS_CATEGORY
                   + "," + Constants.ROUTERS_CATEGORY));
   // 一個註冊中心可能有多個服務提供者,因此這裏需要將多個服務提供者合併爲一個
   Invoker invoker = cluster.join(directory);
   ProviderConsumerRegTable.registerConsumer(invoker, url, subscribeUrl,
directory);
   return invoker;
}

如上,doRefer 方法創建一個RegistryDirectory 實例,然後生成服務者消費者鏈接,並向註冊中心進行註冊。註冊完畢後,緊接着訂閱providers、configurators、routers 等節點下的數據。完成訂閱後,RegistryDirectory 會收到這幾個節點下的子節點信息。由於一個服務可能部署在多臺服務器上,這樣就會在providers 產生多個節點,這個時候就需要Cluster 將多個服務節點合併爲一個,並生成一個Invoker。

(3)創建代理對象

Invoker 創建完畢後,接下來要做的事情是爲服務接口生成代理對象。有了代理對象,即可進行遠程調用。代理對象生成的入口方法爲ProxyFactory 的getProxy,接下來進行分析。

public <T> T getProxy(Invoker<T> invoker) throws RpcException {
   // 調用重載方法
   return getProxy(invoker, false);
}
public <T> T getProxy(Invoker<T> invoker, boolean generic) throws RpcException {
   Class<?>[] interfaces = null;
   // 獲取接口列表
    String config = invoker.getUrl().getParameter("interfaces");
   if (config != null && config.length() > 0) {
       // 切分接口列表
       String[] types = Constants.COMMA_SPLIT_PATTERN.split(config);
       if (types != null && types.length > 0) {
           interfaces = new Class<?>[types.length + 2];
           // 設置服務接口類和 EchoService.class 到 interfaces 中
           interfaces[0] = invoker.getInterface();
           interfaces[1] = EchoService.class;
           for (int i = 0; i < types.length; i++) {
               // 加載接口類
               interfaces[i + 1] = ReflectUtils.forName(types[i]);
           }
       }
   }
   if (interfaces == null) {
       interfaces = new Class<?>[]{invoker.getInterface(), EchoService.class};
   }
   // 爲 http 和 hessian 協議提供泛化調用支持,參考 pull request #1827
   if (!invoker.getInterface().equals(GenericService.class) && generic) {
       int len = interfaces.length;
       Class<?>[] temp = interfaces;
       // 創建新的 interfaces 數組
       interfaces = new Class<?>[len + 1];
       System.arraycopy(temp, 0, interfaces, 0, len);
       // 設置 GenericService.class 到數組中
       interfaces[len] = GenericService.class;
   }
   // 調用重載方法
   return getProxy(invoker, interfaces);
}
public abstract <T> T getProxy(Invoker<T> invoker, Class<?>[] types);

如上,上面大段代碼都是用來獲取interfaces 數組的,我們繼續往下看。getProxy(Invoker, Class<?>[]) 這個方法是一個抽象方法,下面我們到JavassistProxyFactory 類中看一下該方法的實現代碼。

public <T> T getProxy(Invoker<T> invoker, Class<?>[] interfaces) {
   // 生成 Proxy 子類(Proxy 是抽象類)。並調用 Proxy 子類的 newInstance 方法創建Proxy 實例
   return (T) Proxy.getProxy(interfaces).newInstance(new
InvokerInvocationHandler(invoker));
}

上面代碼並不多,首先是通過Proxy 的getProxy 方法獲取Proxy 子類,然後創建InvokerInvocationHandler 對象,並將該對象傳給newInstance 生成Proxy 實例。InvokerInvocationHandler 實現JDK 的InvocationHandler 接口,具體的用途是攔截接口類調用。下面以org.apache.dubbo.demo.DemoService 這個接口爲例,來看一下該接口代理類代碼大致是怎樣的(忽略EchoService 接口)。

package org.apache.dubbo.common.bytecode;
public class proxy0 implements org.apache.dubbo.demo.DemoService {
   public static java.lang.reflect.Method[] methods;
   private java.lang.reflect.InvocationHandler handler;
   public proxy0() {
   }
   public proxy0(java.lang.reflect.InvocationHandler arg0) {
       handler = $1;
   }
   public java.lang.String sayHello(java.lang.String arg0) {
       Object[] args = new Object[1];
       args[0] = ($w) $1;
       Object ret = handler.invoke(this, methods[0], args);
       return (java.lang.String) ret;
   }
}

好了,到這裏代理類生成邏輯就分析完了。整個過程比較複雜,大家需要耐心看一下。

1.3 總結

  1. 從註冊中心發現引用服務:在有註冊中心,通過註冊中心發現提供者地址的情況下,ReferenceConfig 解析出的URL 格式爲: registry://registryhost:/org.apache.registry.RegistryService?refer=URL.encode("conumerhost/com.foo.FooService?version=1.0.0") 。
  2. 通過URL 的registry://協議頭識別,就會調用RegistryProtocol#refer()方法
  3. 查詢提供者URL,如 dubbo://service-host/com.foo.FooService?version=1.0.0 ,來獲取註冊中心
  4. 創建一個RegistryDirectory 實例並設置註冊中心和協議
  5. 生成conusmer 連接,在consumer 目錄下創建節點,向註冊中心註冊
  6. 註冊完畢後,訂閱providers,configurators,routers 等節點的數據
  7. 通過URL 的 dubbo:// 協議頭識別,調用 DubboProtocol#refer() 方法,創建一個
    ExchangeClient 客戶端並返回DubboInvoker 實例
  8. 由於一個服務可能會部署在多臺服務器上,這樣就會在providers 產生多個節點,這樣也就會得到多個DubboInvoker 實例,就需要RegistryProtocol 調用Cluster 將多個服務提供者節點僞裝成一個節點,並返回一個Invoker
  9. Invoker 創建完畢後,調用ProxyFactory 爲服務接口生成代理對象,返回提供者引用

02 網絡通信

在之前的內容中,我們分析了消費者端服務發現與提供者端服務暴露的相關內容,同時也知道消費者端通過內置的負載均衡算法獲取合適的調用invoker進行遠程調用。接下來我們再研究下遠程調用過程即網絡通信。

網絡通信位於Remoting模塊:

  • Remoting 實現是Dubbo 協議的實現,如果你選擇RMI 協議,整個Remoting 都不會用上;
  • Remoting 內部再劃爲 Transport 傳輸層 和 Exchange 信息交換層 ;
  • Transport 層只負責單向消息傳輸,是對Mina, Netty, Grizzly 的抽象,它也可以擴展UDP 傳輸;
  • Exchange 層是在傳輸層之上封裝了Request-Response 語義;

網絡通信的問題:

  • 客戶端與服務端連通性問題
  • 粘包拆包問題
  • 異步多線程數據一致問題

2.1 通信協議

dubbo內置,dubbo協議 ,rmi協議,hessian協議,http協議,webservice協議,thrift協議,rest協議,grpc協議,memcached協議,redis協議等10種通訊協議。各個協議特點如下

dubbo協議

Dubbo 缺省協議採用單一長連接和NIO 異步通訊,適合於小數據量大併發的服務調用,以及服務消費者機器數遠大於服務提供者機器數的情況。

  • 缺省協議,使用基於mina 1.1.7 和hessian 3.2.1 的tbremoting 交互。
  • 連接個數:單連接
  • 連接方式:長連接
  • 傳輸協議:TCP
  • 傳輸方式:NIO 異步傳輸
  • 序列化:Hessian 二進制序列化
  • 適用範圍:傳入傳出參數數據包較小(建議小於100K),消費者比提供者個數多,單一消費者無法壓滿提供者,儘量不要用dubbo 協議傳輸大文件或超大字符串。
  • 適用場景:常規遠程服務方法調用

rmi協議

RMI 協議採用JDK 標準的 java.rmi.* 實現,採用阻塞式短連接和JDK 標準序列化方式。

  • 連接個數:多連接
  • 連接方式:短連接
  • 傳輸協議:TCP
  • 傳輸方式:同步傳輸
  • 序列化:Java 標準二進制序列化
  • 適用範圍:傳入傳出參數數據包大小混合,消費者與提供者個數差不多,可傳文件。
  • 適用場景:常規遠程服務方法調用,與原生RMI服務互操作

hessian協議

Hessian 協議用於集成Hessian 的服務,Hessian 底層採用Http 通訊,採用Servlet 暴露服務,

Dubbo 缺省內嵌Jetty 作爲服務器實現。

Dubbo 的Hessian 協議可以和原生Hessian 服務互操作,即:提供者用Dubbo 的Hessian 協議暴露服務,消費者直接用標準Hessian 接口調用或者提供方用標準Hessian 暴露服務,消費方用Dubbo 的Hessian 協議調用。

  • 連接個數:多連接
  • 連接方式:短連接
  • 傳輸協議:HTTP
  • 傳輸方式:同步傳輸
  • 序列化:Hessian二進制序列化
  • 適用範圍:傳入傳出參數數據包較大,提供者比消費者個數多,提供者壓力較大,可傳文件。
  • 適用場景:頁面傳輸,文件傳輸,或與原生hessian服務互操作

http協議

基於HTTP 表單的遠程調用協議,採用Spring 的HttpInvoker 實現

  • 連接個數:多連接
  • 連接方式:短連接
  • 傳輸協議:HTTP
  • 傳輸方式:同步傳輸
  • 序列化:表單序列化
  • 適用範圍:傳入傳出參數數據包大小混合,提供者比消費者個數多,可用瀏覽器查看,可用表單或URL傳入參數,暫不支持傳文件。
  • 適用場景:需同時給應用程序和瀏覽器JS 使用的服務。

webservice協議
基於WebService 的遠程調用協議,基於Apache CXF 實現]()。

可以和原生WebService 服務互操作,即:提供者用Dubbo 的WebService 協議暴露服務,消費者直接用標準WebService 接口調用,或者提供方用標準WebService 暴露服務,消費方用Dubbo 的WebService 協議調用。

  • 連接個數:多連接
  • 連接方式:短連接
  • 傳輸協議:HTTP
  • 傳輸方式:同步傳輸
  • 序列化:SOAP 文本序列化(http + xml)
  • 適用場景:系統集成,跨語言調用

thrift協議

當前dubbo 支持[1]的thrift 協議是對thrift 原生協議[2] 的擴展,在原生協議的基礎上添加了一些額外的頭信息,比如service name,magic number 等。

rest協議

基於標準的Java REST API——JAX-RS 2.0(Java API for RESTful Web Services的簡寫)實現的REST調用支持

grpc協議

Dubbo 自2.7.5 版本開始支持gRPC 協議,對於計劃使用HTTP/2 通信,或者想利用gRPC 帶來的Stream、反壓、Reactive 編程等能力的開發者來說, 都可以考慮啓用gRPC 協議。

爲期望使用gRPC 協議的用戶帶來服務治理能力,方便接入Dubbo 體系用戶可以使用Dubbo 風格的,基於接口的編程風格來定義和使用遠程服務

memcached協議

基於memcached實現的RPC 協議

redis協議

基於Redis 實現的RPC 協議

2.2 序列化

序列化就是將對象轉成字節流,用於網絡傳輸,以及將字節流轉爲對象,用於在收到字節流數據後還原成對象。序列化的優勢有很多,例如安全性更好、可跨平臺等。我們知道dubbo基於netty進行網絡通訊,在NettyClient.doOpen() 方法中可以看到Netty的相關類

bootstrap.setPipelineFactory(new ChannelPipelineFactory() {
   public ChannelPipeline getPipeline() {
       NettyCodecAdapter adapter = new NettyCodecAdapter(getCodec(), getUrl(),
NettyClient.this);
       ChannelPipeline pipeline = Channels.pipeline();
       pipeline.addLast("decoder", adapter.getDecoder());
       pipeline.addLast("encoder", adapter.getEncoder());
       pipeline.addLast("handler", nettyHandler);
       return pipeline;
   }
});

然後去看NettyCodecAdapter 類最後進入ExchangeCodec類的encodeRequest方法,如下:

protected void encodeRequest(Channel channel, ChannelBuffer buffer, Request
req) throws IOException {
       Serialization serialization = getSerialization(channel);
       // header.
       byte[] header = new byte[HEADER_LENGTH];

是的,就是Serialization接口,默認是Hessian2Serialization序列化接口。

Dubbo序列化支持java、compactedjava、nativejava、fastjson、dubbo、fst、hessian2、kryo,protostuff其中默認hessian2。其中java、compactedjava、nativejava屬於原生java的序列化。

dubbo序列化:阿里尚未開發成熟的高效java序列化實現,阿里不建議在生產環境使用它。
hessian2序列化:hessian是一種跨語言的高效二進制序列化方式。但這裏實際不是原生的hessian2序列化,而是阿里修改過的,它是dubbo RPC默認啓用的序列化方式。
json序列化:目前有兩種實現,一種是採用的阿里的fastjson庫,另一種是採用dubbo中自己實現的簡單json庫,但其實現都不是特別成熟,而且json這種文本序列化性能一般不如上面兩種二進制序列化。
java序列化:主要是採用JDK自帶的Java序列化實現,性能很不理想。

2.3 網絡通信

(1)Dubbo中的數據格式

解決socket中數據粘包拆包問題,一般有三種方式

定長協議(數據包長度一致)

定長的協議是指協議內容的長度是固定的,比如協議byte長度是50,當從網絡上讀取50個byte後,就進行decode解碼操作。定長協議在讀取或者寫入時,效率比較高,因爲數據緩存的大小基本都確定了,就好比數組一樣,缺陷就是適應性不足,以RPC場景爲例,很難估計出定長的長度是多少。

特殊結束符(數據尾:通過特殊的字符標識#)

相比定長協議,如果能夠定義一個特殊字符作爲每個協議單元結束的標示,就能夠以變長的方式進行通信,從而在數據傳輸和高效之間取得平衡,比如用特殊字符 \n 。特殊結束符方式的問題是過於簡單的思考了協議傳輸的過程,對於一個協議單元必須要全部讀入才能夠進行處理,除此之外必須要防止用戶傳輸的數據不能同結束符相同,否則就會出現紊亂。

變長協議(協議頭+payload模式)

這種一般是自定義協議,會以定長加不定長的部分組成,其中定長的部分需要描述不定長的內容長度。dubbo就是使用這種形式的數據傳輸格式

Dubbo 數據包分爲消息頭和消息體,消息頭用於存儲一些元信息,比如魔數(Magic),數據包類型(Request/Response),消息體長度(Data Length)等。消息體中用於存儲具體的調用消息,比如方法名稱,參數列表等。下面簡單列舉一下消息頭的內容。

偏移量(Bit) 字段 取值

0 ~ 7 魔數高位 0xda00

8 ~ 15 魔數低位 0xbb

16 數據包類型 0 - Response, 1 - Request

17 調用方式 僅在第16位被設爲1的情況下有效,0 - 單向調用,1 - 雙向調用

18 事件標 識 0 - 當前數據包是請求或響應包,1 - 當前數據包是心跳包

19 ~23 序列化器編號 2 - Hessian2Serialization
3 - JavaSerialization
4 - CompactedJavaSerialization
6 - FastJsonSerialization
7 - NativeJavaSerialization
8 - KryoSerialization
9 - FstSerialization

24 ~31 狀態 20 - OK 30 - CLIENT_TIMEOUT 31 - SERVER_TIMEOUT 40 -BAD_REQUEST 50 - BAD_RESPONSE ......

32 ~95 請求編號 共8字節,運行時生成

96 ~127 消息體長度 運行時計算

(2)消費端發送請求

/**
*proxy0#sayHello(String)
*—> InvokerInvocationHandler#invoke(Object, Method, Object[])
*   —> MockClusterInvoker#invoke(Invocation)
*     —> AbstractClusterInvoker#invoke(Invocation)
*       —> FailoverClusterInvoker#doInvoke(Invocation, List<Invoker<T>>,LoadBalance)
*         —> Filter#invoke(Invoker, Invocation)  // 包含多個 Filter 調用
*          —> ListenerInvokerWrapper#invoke(Invocation)
*             —> AbstractInvoker#invoke(Invocation)
*              —> DubboInvoker#doInvoke(Invocation)
*                 —> ReferenceCountExchangeClient#request(Object, int)
*                   —> HeaderExchangeClient#request(Object, int)
*                     —> HeaderExchangeChannel#request(Object, int)
*                       —> AbstractPeer#send(Object)
*                         —> AbstractClient#send(Object, boolean)
*                          —> NettyChannel#send(Object, boolean)
*                             —> NioClientSocketChannel#write(Object)
*/

dubbo消費方,自動生成代碼對象如下

public class proxy0 implements ClassGenerator.DC, EchoService, DemoService {
   private InvocationHandler handler;
   public String sayHello(String string) {
       // 將參數存儲到 Object 數組中
       Object[] arrobject = new Object[]{string};
       // 調用 InvocationHandler 實現類的 invoke 方法得到調用結果
       Object object = this.handler.invoke(this, methods[0], arrobject);
       // 返回調用結果
       return (String)object;
   }
}

InvokerInvocationHandler 中的invoker 成員變量類型爲MockClusterInvoker,MockClusterInvoker內部封裝了服務降級邏輯。下面簡單看一下:

public Result invoke(Invocation invocation) throws RpcException {
       Result result = null;
// 獲取 mock 配置值
       String value =
directory.getUrl().getMethodParameter(invocation.getMethodName(), MOCK_KEY,
Boolean.FALSE.toString()).trim();
       if (value.length() == 0 || value.equalsIgnoreCase("false")) {
            // 無 mock 邏輯,直接調用其他 Invoker 對象的 invoke 方法,
           // 比如 FailoverClusterInvoker
           result = this.invoker.invoke(invocation);
       } else if (value.startsWith("force")) {
           // force:xxx 直接執行 mock 邏輯,不發起遠程調用
           result = doMockInvoke(invocation, null);
       } else {
            // fail:xxx 表示消費方對調用服務失敗後,再執行 mock 邏輯,不拋出異常
           try {
               result = this.invoker.invoke(invocation);
           } catch (RpcException e) {
                // 調用失敗,執行 mock 邏輯
               result = doMockInvoke(invocation, e);
           }
       }
       return result;
   }

考慮到前文已經詳細分析過FailoverClusterInvoker,因此本節略過FailoverClusterInvoker,直接分析DubboInvoker。

public abstract class AbstractInvoker<T> implements Invoker<T> {
   
   public Result invoke(Invocation inv) throws RpcException {
       if (destroyed.get()) {
           throw new RpcException("Rpc invoker for service ...");
            }
       RpcInvocation invocation = (RpcInvocation) inv;
       // 設置 Invoker
       invocation.setInvoker(this);
       if (attachment != null && attachment.size() > 0) {
           // 設置 attachment
           invocation.addAttachmentsIfAbsent(attachment);
       }
       Map<String, String> contextAttachments =
RpcContext.getContext().getAttachments();
       if (contextAttachments != null && contextAttachments.size() != 0) {
           // 添加 contextAttachments 到 RpcInvocation#attachment 變量中
           invocation.addAttachments(contextAttachments);
       }
       if (getUrl().getMethodParameter(invocation.getMethodName(),
Constants.ASYNC_KEY, false)) {
           // 設置異步信息到 RpcInvocation#attachment 中
           invocation.setAttachment(Constants.ASYNC_KEY,
Boolean.TRUE.toString());
       }
       RpcUtils.attachInvocationIdIfAsync(getUrl(), invocation);
       try {
           // 抽象方法,由子類實現
           return doInvoke(invocation);
       } catch (InvocationTargetException e) {
           // ...
       } catch (RpcException e) {
           // ...
       } catch (Throwable e) {
           return new RpcResult(e);
       }
   }
   protected abstract Result doInvoke(Invocation invocation) throws Throwable;
   
   // 省略其他方法
}

上面的代碼來自AbstractInvoker 類,其中大部分代碼用於添加信息到RpcInvocation#attachment 變量中,添加完畢後,調用doInvoke 執行後續的調用。doInvoke 是一個抽象方法,需要由子類實現,下面到DubboInvoker 中看一下。

@Override
   protected Result doInvoke(final Invocation invocation) throws Throwable {
       RpcInvocation inv = (RpcInvocation) invocation;
       final String methodName = RpcUtils.getMethodName(invocation);
       //將目標方法以及版本號作爲參數放入到Invocation中
       inv.setAttachment(PATH_KEY, getUrl().getPath());
       inv.setAttachment(VERSION_KEY, version);
       //獲得客戶端連接
       ExchangeClient currentClient; //初始化invoker的時候,構建的一個遠程通信連接
       if (clients.length == 1) { //默認
           currentClient = clients[0];
       } else {
           //通過取模獲得其中一個連接
            currentClient = clients[index.getAndIncrement() % clients.length];
       }
       try {
           //表示當前的方法是否存在返回值
           boolean isOneway = RpcUtils.isOneway(getUrl(), invocation);
           int timeout = getUrl().getMethodParameter(methodName, TIMEOUT_KEY,
DEFAULT_TIMEOUT);
           //isOneway 爲 true,表示“單向”通信
           if (isOneway) {//異步無返回值
               boolean isSent = getUrl().getMethodParameter(methodName,
Constants.SENT_KEY, false);
               currentClient.send(inv, isSent);
               RpcContext.getContext().setFuture(null);
               return AsyncRpcResult.newDefaultAsyncResult(invocation);
           } else { //存在返回值
               //是否採用異步
               AsyncRpcResult asyncRpcResult = new AsyncRpcResult(inv);
               CompletableFuture<Object> responseFuture =
currentClient.request(inv, timeout);
               responseFuture.whenComplete((obj, t) -> {
                   if (t != null) {
                       asyncRpcResult.completeExceptionally(t);
                   } else {
                       asyncRpcResult.complete((AppResponse) obj);
                   }
               });
               RpcContext.getContext().setFuture(new
FutureAdapter(asyncRpcResult));
               return asyncRpcResult;
           }
       }
       //省略無關代碼
   }

最終進入到HeaderExchangeChannel#request方法,拼裝Request並將請求發送出去

public CompletableFuture<Object> request(Object request, int timeout) throws
RemotingException {
       if (closed) {
           throw new RemotingException(this.getLocalAddress(), null, "Failed
tosend request " + request + ", cause: The channel " + this + " is closed!");
       }
       // 創建請求對象
       Request req = new Request();
       req.setVersion(Version.getProtocolVersion());
       req.setTwoWay(true);
       req.setData(request);
       DefaultFuture future = DefaultFuture.newFuture(channel, req, timeout);
       try {
           //NettyClient
           channel.send(req);
       } catch (RemotingException e) {
           future.cancel();
           throw e;
       }
       return future;
   }

請求編碼如何做的?

在netty啓動時,我們設置了編解碼器,其中通過ExchangeCodec完成編解碼工作如下:

public class ExchangeCodec extends TelnetCodec {
   // 消息頭長度
   protected static final int HEADER_LENGTH = 16;
   // 魔數內容
   protected static final short MAGIC = (short) 0xdabb;
   protected static final byte MAGIC_HIGH = Bytes.short2bytes(MAGIC)[0];
   protected static final byte MAGIC_LOW = Bytes.short2bytes(MAGIC)[1];
   protected static final byte FLAG_REQUEST = (byte) 0x80;
   protected static final byte FLAG_TWOWAY = (byte) 0x40;
   protected static final byte FLAG_EVENT = (byte) 0x20;
   protected static final int SERIALIZATION_MASK = 0x1f;
   private static final Logger logger =
LoggerFactory.getLogger(ExchangeCodec.class);
   public Short getMagicCode() {
       return MAGIC;
   }
   @Override
   public void encode(Channel channel, ChannelBuffer buffer, Object msg) throws
IOException {
       if (msg instanceof Request) {
           // 對 Request 對象進行編碼
           encodeRequest(channel, buffer, (Request) msg);
       } else if (msg instanceof Response) {
           // 對 Response 對象進行編碼,後面分析
           encodeResponse(channel, buffer, (Response) msg);
       } else {
           super.encode(channel, buffer, msg);
       }
   }
   protected void encodeRequest(Channel channel, ChannelBuffer buffer, Request
req) throws IOException {
       Serialization serialization = getSerialization(channel);
       // 創建消息頭字節數組,長度爲 16
       byte[] header = new byte[HEADER_LENGTH];
       // 設置魔數
       Bytes.short2bytes(MAGIC, header);
       // 設置數據包類型(Request/Response)和序列化器編號
       header[2] = (byte) (FLAG_REQUEST | serialization.getContentTypeId());
       // 設置通信方式(單向/雙向)
       if (req.isTwoWay()) {
           header[2] |= FLAG_TWOWAY;
       }
       
       // 設置事件標識
       if (req.isEvent()) { header[2] |= FLAG_EVENT;
       }
       // 設置請求編號,8個字節,從第4個字節開始設置
       Bytes.long2bytes(req.getId(), header, 4);
       // 獲取 buffer 當前的寫位置
       int savedWriteIndex = buffer.writerIndex();
       // 更新 writerIndex,爲消息頭預留 16 個字節的空間
       buffer.writerIndex(savedWriteIndex + HEADER_LENGTH);
       ChannelBufferOutputStream bos = new ChannelBufferOutputStream(buffer);
       // 創建序列化器,比如 Hessian2ObjectOutput
       ObjectOutput out = serialization.serialize(channel.getUrl(), bos);
       if (req.isEvent()) {
           // 對事件數據進行序列化操作
           encodeEventData(channel, out, req.getData());
       } else {
           // 對請求數據進行序列化操作
           encodeRequestData(channel, out, req.getData(), req.getVersion());
       }
       out.flushBuffer();
       if (out instanceof Cleanable) {
           ((Cleanable) out).cleanup();
       }
       bos.flush();
       bos.close();
       
       // 獲取寫入的字節數,也就是消息體長度
       int len = bos.writtenBytes();
       checkPayload(channel, len);
       // 將消息體長度寫入到消息頭中
       Bytes.int2bytes(len, header, 12);
       // 將 buffer 指針移動到 savedWriteIndex,爲寫消息頭做準備
       buffer.writerIndex(savedWriteIndex);
       // 從 savedWriteIndex 下標處寫入消息頭
       buffer.writeBytes(header);
       // 設置新的 writerIndex,writerIndex = 原寫下標 + 消息頭長度 + 消息體長度
       buffer.writerIndex(savedWriteIndex + HEADER_LENGTH + len);
   }
   
   // 省略其他方法
}

以上就是請求對象的編碼過程,該過程首先會通過位運算將消息頭寫入到header 數組中。然後對Request 對象的data 字段執行序列化操作,序列化後的數據最終會存儲到ChannelBuffer 中。序列化操作執行完後,可得到數據序列化後的長度len,緊接着將len 寫入到header 指定位置處。最後再將消息頭字節數組header 寫入到ChannelBuffer 中,整個編碼過程就結束了。本節的最後,我們再來看一下Request 對象的data 字段序列化過程,也就是encodeRequestData 方法的邏輯,如下:

public class DubboCodec extends ExchangeCodec implements Codec2 {
   
protected void encodeRequestData(Channel channel, ObjectOutput out, Object
data, String version) throws IOException {
       RpcInvocation inv = (RpcInvocation) data;
     // 依次序列化 dubbo version、path、version
       out.writeUTF(version);
       out.writeUTF(inv.getAttachment(Constants.PATH_KEY));
       out.writeUTF(inv.getAttachment(Constants.VERSION_KEY));
       // 序列化調用方法名
       out.writeUTF(inv.getMethodName());
       // 將參數類型轉換爲字符串,並進行序列化
       out.writeUTF(ReflectUtils.getDesc(inv.getParameterTypes()));
       Object[] args = inv.getArguments();
       if (args != null)
           for (int i = 0; i < args.length; i++) {
               // 對運行時參數進行序列化
               out.writeObject(encodeInvocationArgument(channel, inv, i));
           }
       
       // 序列化 attachments
       out.writeObject(inv.getAttachments());
   }
}

至此,關於服務消費方發送請求的過程就分析完了,接下來我們來看一下服務提供方是如何接收請求的。

(3)提供方接受請求

請求如何解碼?

這裏直接分析請求數據的解碼邏輯,忽略中間過程,如下:

public class ExchangeCodec extends TelnetCodec {
   
   @Override
   public Object decode(Channel channel, ChannelBuffer buffer) throws
IOException {
       int readable = buffer.readableBytes();
       // 創建消息頭字節數組
       byte[] header = new byte[Math.min(readable, HEADER_LENGTH)];
       // 讀取消息頭數據
       buffer.readBytes(header);
       // 調用重載方法進行後續解碼工作
       return decode(channel, buffer, readable, header);
   }
   @Override
   protected Object decode(Channel channel, ChannelBuffer buffer, int readable,
byte[] header) throws IOException {
       // 檢查魔數是否相等
       if (readable > 0 && header[0] != MAGIC_HIGH
               || readable > 1 && header[1] != MAGIC_LOW) {
           int length = header.length;
           if (header.length < readable) {
               header = Bytes.copyOf(header, readable);
               buffer.readBytes(header, length, readable - length);
           }
           for (int i = 1; i < header.length - 1; i++) {
               if (header[i] == MAGIC_HIGH && header[i + 1] == MAGIC_LOW) {
                    buffer.readerIndex(buffer.readerIndex() - header.length +
i);
                   header = Bytes.copyOf(header, i);
                   break;
               }
           }
           // 通過 telnet 命令行發送的數據包不包含消息頭,所以這裏
           // 調用 TelnetCodec 的 decode 方法對數據包進行解碼
           return super.decode(channel, buffer, readable, header);
       }
       
       // 檢測可讀數據量是否少於消息頭長度,若小於則立即返回
DecodeResult.NEED_MORE_INPUT
       if (readable < HEADER_LENGTH) {
           return DecodeResult.NEED_MORE_INPUT;
       }
       // 從消息頭中獲取消息體長度
       int len = Bytes.bytes2int(header, 12);
       // 檢測消息體長度是否超出限制,超出則拋出異常
       checkPayload(channel, len);
       int tt = len + HEADER_LENGTH;
       // 檢測可讀的字節數是否小於實際的字節數
       if (readable < tt) {
           return DecodeResult.NEED_MORE_INPUT;
       }
       
       ChannelBufferInputStream is = new ChannelBufferInputStream(buffer, len);
       try {
           // 繼續進行解碼工作
           return decodeBody(channel, is, header);
       } finally {
           if (is.available() > 0) {
               try {
                   StreamUtils.skipUnusedStream(is);
               } catch (IOException e) {
                   logger.warn(e.getMessage(), e);
               }
           }
       }
   }
}

上面方法通過檢測消息頭中的魔數是否與規定的魔數相等,提前攔截掉非常規數據包,比如通過telnet命令行發出的數據包。接着再對消息體長度,以及可讀字節數進行檢測。最後調用decodeBody 方法進行後續的解碼工作,ExchangeCodec 中實現了decodeBody 方法,但因其子類DubboCodec 覆寫了該方法,所以在運行時DubboCodec 中的decodeBody 方法會被調用。下面我們來看一下該方法的代碼。

public class DubboCodec extends ExchangeCodec implements Codec2 {
   @Override
   protected Object decodeBody(Channel channel, InputStream is, byte[] header)
throws IOException {
       // 獲取消息頭中的第三個字節,並通過邏輯與運算得到序列化器編號byte flag = header[2], proto = (byte) (flag & SERIALIZATION_MASK);
       Serialization s = CodecSupport.getSerialization(channel.getUrl(),
proto);
       // 獲取調用編號
       long id = Bytes.bytes2long(header, 4);
       // 通過邏輯與運算得到調用類型,0 - Response,1 - Request
       if ((flag & FLAG_REQUEST) == 0) {
           // 對響應結果進行解碼,得到 Response 對象。這個非本節內容,後面再分析
           // ...
       } else {
           // 創建 Request 對象
           Request req = new Request(id);
           req.setVersion(Version.getProtocolVersion());
           // 通過邏輯與運算得到通信方式,並設置到 Request 對象中
           req.setTwoWay((flag & FLAG_TWOWAY) != 0);
           
           // 通過位運算檢測數據包是否爲事件類型
           if ((flag & FLAG_EVENT) != 0) {
               // 設置心跳事件到 Request 對象中
               req.setEvent(Request.HEARTBEAT_EVENT);
           }
           try {
               Object data;
               if (req.isHeartbeat()) {
                   // 對心跳包進行解碼,該方法已被標註爲廢棄
                   data = decodeHeartbeatData(channel, deserialize(s,
channel.getUrl(), is));
               } else if (req.isEvent()) {
                   // 對事件數據進行解碼
                   data = decodeEventData(channel, deserialize(s,
channel.getUrl(), is));
               } else {
                   DecodeableRpcInvocation inv;
                   // 根據 url 參數判斷是否在 IO 線程上對消息體進行解碼
                   if (channel.getUrl().getParameter(
                           Constants.DECODE_IN_IO_THREAD_KEY,
                           Constants.DEFAULT_DECODE_IN_IO_THREAD)) {
                       inv = new DecodeableRpcInvocation(channel, req, is,
proto);
                       // 在當前線程,也就是 IO 線程上進行後續的解碼工作。此工作完成後,
可將
                       // 調用方法名、attachment、以及調用參數解析出來
                       inv.decode();
                   } else {
                       // 僅創建 DecodeableRpcInvocation 對象,但不在當前線程上執行解
碼邏輯
                       inv = new DecodeableRpcInvocation(channel, req,
                               new
UnsafeByteArrayInputStream(readMessageData(is)), proto);
                   }
                   data = inv;
               }
               
               // 設置 data 到 Request 對象中
               req.setData(data);
           } catch (Throwable t) {
               // 若解碼過程中出現異常,則將 broken 字段設爲 true,
               // 並將異常對象設置到 Reqeust 對象中
                req.setBroken(true);
               req.setData(t);
           }
           return req;
       }
   }
}

如上,decodeBody 對部分字段進行了解碼,並將解碼得到的字段封裝到Request 中。隨後會調用DecodeableRpcInvocation 的decode 方法進行後續的解碼工作。此工作完成後,可將調用方法名、attachment、以及調用參數解析出來。

調用服務

解碼器將數據包解析成Request 對象後,NettyHandler 的messageReceived 方法緊接着會收到這個對象,並將這個對象繼續向下傳遞。整個調用棧如下:

NettyServerHandler#channelRead(ChannelHandlerContext, MessageEvent)
 —> AbstractPeer#received(Channel, Object)
   —> MultiMessageHandler#received(Channel, Object)
     —> HeartbeatHandler#received(Channel, Object)
       —> AllChannelHandler#received(Channel, Object)
         —> ExecutorService#execute(Runnable)    // 由線程池執行後續的調用邏輯=

這裏我們直接分析調用棧中的分析第一個和最後一個調用方法邏輯。如下:
考慮到篇幅,以及很多中間調用的邏輯並非十分重要,所以這裏就不對調用棧中的每個方法都進行分析了。這裏我們直接分析最後一個調用方法邏輯。如下:

public class ChannelEventRunnable implements Runnable {
   
   private final ChannelHandler handler;
   private final Channel channel;
   private final ChannelState state;
   private final Throwable exception;
   private final Object message;
   
   @Override
   public void run() {
       // 檢測通道狀態,對於請求或響應消息,此時 state = RECEIVED
       if (state == ChannelState.RECEIVED) {
           try {
               // 將 channel 和 message 傳給 ChannelHandler 對象,進行後續的調用
               handler.received(channel, message);
           } catch (Exception e) {
               logger.warn("... operation error, channel is ... message is
...");
           }
       }
       
       // 其他消息類型通過 switch 進行處理
       else {
           switch (state) {
           case CONNECTED:
               try {
                   handler.connected(channel);
               } catch (Exception e) {
                   logger.warn("... operation error, channel is ...");
               }
               break;
           case DISCONNECTED:
               // ...
           case SENT:
               // ...
           case CAUGHT:
               // ...
           default:
               logger.warn("unknown state: " + state + ", message is " +
message);
           }
       }
   }
}

如上,請求和響應消息出現頻率明顯比其他類型消息高,所以這裏對該類型的消息進行了針對性判斷ChannelEventRunnable 僅是一箇中轉站,它的run 方法中並不包含具體的調用邏輯,僅用於將參數傳給其他ChannelHandler 對象進行處理,該對象類型爲DecodeHandler

public class DecodeHandler extends AbstractChannelHandlerDelegate {
   public DecodeHandler(ChannelHandler handler) {
       super(handler);
   }
   @Override
   public void received(Channel channel, Object message) throws
RemotingException {
       if (message instanceof Decodeable) {
           // 對 Decodeable 接口實現類對象進行解碼
           decode(message);
       }
       if (message instanceof Request) {
           // 對 Request 的 data 字段進行解碼
           decode(((Request) message).getData());
       }
       if (message instanceof Response) {
           // 對 Request 的 result 字段進行解碼
           decode(((Response) message).getResult());
       }
       // 執行後續邏輯
       handler.received(channel, message);
   }
   private void decode(Object message) {
       // Decodeable 接口目前有兩個實現類,
       // 分別爲 DecodeableRpcInvocation 和 DecodeableRpcResult
       if (message != null && message instanceof Decodeable) {
           try {
               // 執行解碼邏輯
               ((Decodeable) message).decode();
               } catch (Throwable e) {
               if (log.isWarnEnabled()) {
                   log.warn("Call Decodeable.decode failed: " + e.getMessage(),
e);
               }
           }
       }
   }
}

DecodeHandler 主要是包含了一些解碼邏輯,完全解碼後的Request 對象會繼續向後傳遞

public class DubboProtocol extends AbstractProtocol {
   public static final String NAME = "dubbo";
   
   private ExchangeHandler requestHandler = new ExchangeHandlerAdapter() {
       @Override
       public Object reply(ExchangeChannel channel, Object message) throws
RemotingException {
           if (message instanceof Invocation) {
               Invocation inv = (Invocation) message;
               // 獲取 Invoker 實例
               Invoker<?> invoker = getInvoker(channel, inv);
               if
(Boolean.TRUE.toString().equals(inv.getAttachments().get(IS_CALLBACK_SERVICE_INV
OKE))) {
                   // 回調相關,忽略
               }
             
 RpcContext.getContext().setRemoteAddress(channel.getRemoteAddress());
               // 通過 Invoker 調用具體的服務
               return invoker.invoke(inv);
           }
           throw new RemotingException(channel, "Unsupported request: ...");
       }
       
       // 忽略其他方法
   }
   
   Invoker<?> getInvoker(Channel channel, Invocation inv) throws
RemotingException {
       // 忽略回調和本地存根相關邏輯
       // ...
       
       int port = channel.getLocalAddress().getPort();
       
       // 計算 service key,格式爲 groupName/serviceName:serviceVersion:port。比
如:
       //   dubbo/com.alibaba.dubbo.demo.DemoService:1.0.0:20880
       String serviceKey = serviceKey(port, path,
inv.getAttachments().get(Constants.VERSION_KEY),
inv.getAttachments().get(Constants.GROUP_KEY));
       // 從 exporterMap 查找與 serviceKey 相對應的 DubboExporter 對象,
       // 服務導出過程中會將 <serviceKey, DubboExporter> 映射關係存儲到 exporterMap
集合中
       DubboExporter<?> exporter = (DubboExporter<?>)
exporterMap.get(serviceKey);
       if (exporter == null)
           throw new RemotingException(channel, "Not found exported service
...");
       // 獲取 Invoker 對象,並返回
       return exporter.getInvoker();
   }
   
   // 忽略其他方法
}

在之前課程中介紹過,服務全部暴露完成之後保存到exporterMap中。這裏就是通過serviceKey獲取exporter之後獲取Invoker,並通過Invoker 的invoke 方法調用服務邏輯

public abstract class AbstractProxyInvoker<T> implements Invoker<T> {
   @Override
   public Result invoke(Invocation invocation) throws RpcException {
       try {
           // 調用 doInvoke 執行後續的調用,並將調用結果封裝到 RpcResult 中,並
           return new RpcResult(doInvoke(proxy, invocation.getMethodName(),
invocation.getParameterTypes(), invocation.getArguments()));
       } catch (InvocationTargetException e) {
           return new RpcResult(e.getTargetException());
       } catch (Throwable e) {
           throw new RpcException("Failed to invoke remote proxy method ...");
       }
   }
   
   protected abstract Object doInvoke(T proxy, String methodName, Class<?>[]
parameterTypes, Object[] arguments) throws Throwable;
}

如上,doInvoke 是一個抽象方法,這個需要由具體的Invoker 實例實現。Invoker 實例是在運行時通過JavassistProxyFactory 創建的,創建邏輯如下:

public class JavassistProxyFactory extends AbstractProxyFactory {
   
   // 省略其他方法
   @Override
   public <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) {
       final Wrapper wrapper =
Wrapper.getWrapper(proxy.getClass().getName().indexOf('$') < 0 ?
proxy.getClass() : type);
       // 創建匿名類對象
       return new AbstractProxyInvoker<T>(proxy, type, url) {
           @Override
           protected Object doInvoke(T proxy, String methodName,
                                     Class<?>[] parameterTypes,
                                     Object[] arguments) throws Throwable {
               // 調用 invokeMethod 方法進行後續的調用
               return wrapper.invokeMethod(proxy, methodName, parameterTypes,
arguments);
           }
       };
   }
}

Wrapper 是一個抽象類,其中invokeMethod 是一個抽象方法。Dubbo 會在運行時通過Javassist 框架爲Wrapper 生成實現類,並實現invokeMethod 方法,該方法最終會根據調用信息調用具體的服務。以DemoServiceImpl 爲例,Javassist 爲其生成的代理類如下。

/** Wrapper0 是在運行時生成的,大家可使用 Arthas 進行反編譯 */
public class Wrapper0 extends Wrapper implements ClassGenerator.DC {
   public static String[] pns;
   public static Map pts;
   public static String[] mns;
   public static String[] dmns;
   public static Class[] mts0;
   // 省略其他方法
   public Object invokeMethod(Object object, String string, Class[] arrclass,
Object[] arrobject) throws InvocationTargetException {
       DemoService demoService;
       try {
           // 類型轉換
           demoService = (DemoService)object;
       }
       catch (Throwable throwable) {
           throw new IllegalArgumentException(throwable);
       }
       try {
           // 根據方法名調用指定的方法
           if ("sayHello".equals(string) && arrclass.length == 1) {
               return demoService.sayHello((String)arrobject[0]);
           }
       }
       catch (Throwable throwable) {
           throw new InvocationTargetException(throwable);
       }
       throw new NoSuchMethodException(new StringBuffer().append("Not found
method \"").append(string).append("\" in class
com.alibaba.dubbo.demo.DemoService.").toString());
   }
}

到這裏,整個服務調用過程就分析完了。最後把調用過程貼出來,如下:

ChannelEventRunnable#run()
 —> DecodeHandler#received(Channel, Object)
   —> HeaderExchangeHandler#received(Channel, Object)
     —> HeaderExchangeHandler#handleRequest(ExchangeChannel, Request)
       —> DubboProtocol.requestHandler#reply(ExchangeChannel, Object)
         —> Filter#invoke(Invoker, Invocation)
           —> AbstractProxyInvoker#invoke(Invocation)
             —> Wrapper0#invokeMethod(Object, String, Class[], Object[])
               —> DemoServiceImpl#sayHello(String)

(4)提供方返回調用結果

服務提供方調用指定服務後,會將調用結果封裝到Response 對象中,並將該對象返回給服務消費方。服務提供方也是通過NettyChannel 的send 方法將Response 對象返回,這裏就不在重複分析了。

(5)消費方接收調用結果

服務消費方在收到響應數據後,首先要做的事情是對響應數據進行解碼,得到Response 對象。然後再將該對象傳遞給下一個入站處理器,這個入站處理器就是NettyHandler。接下來NettyHandler 會將這個對象繼續向下傳遞,最後AllChannelHandler 的received 方法會收到這個對象,並將這個對象派發到線程池中。這個過程和服務提供方接收請求的過程是一樣的,因此這裏就不重複分析了

03 小結

至此整個dubbo的核心流程原理及其源碼,我們就分析完畢了,整體流程思路不復雜,但是細節很多,要先理解其思想,還是得多花時間再仔細擼一遍。

 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章