原始碼解讀Dubbo分層設計思想

vivo網際網路技術發表於2021-09-14

一、Dubbo分層整體設計概述

我們先從下圖開始簡單介紹Dubbo分層設計概念:

(引用自Duboo開發指南-框架設計文件)

如圖描述Dubbo實現的RPC整體分10層:service、config、proxy、registry、cluster、monitor、protocol、exchange、transport、serialize。

service:使用方定義的介面和實現類;

config:負責解析Dubbo定義的配置,比如註解和xml配置,各種引數;

proxy:主要負責生成消費者和提供者的代理物件,載入框架功能,比如提供者過濾器鏈,擴充套件點;

registry:負責註冊服務的定義和實現類的裝載;

cluster:只有消費者有這麼一層,負責包裝多個服務提供者成一個‘大提供者’,載入負載均衡、路有等擴充套件點;

monitor:定義監控服務,載入監控實現提供者;

protocol:封裝RPC呼叫介面,管理呼叫實體的生命週期;

exchange:封裝請求響應模式,同步轉非同步;

transport:抽象傳輸層模型,相容netty、mina、grizzly等通訊框架;

serialize:抽象序列化模型,相容多種序列化框架,包括:fastjson、fst、hessian2、kryo、kryo2、protobuf等,通過序列化支援跨語言的方式,支援跨語言的rpc呼叫;

Dubbo這麼分層的目的在於實現層與層之間的解耦,每一層都定義了介面規範,也可以根據不同的業務需求定製、載入不同的實現,具有極高的擴充套件性。

1.1. RPC呼叫過程

接下來結合上圖簡單描述一次完整的rpc呼叫過程:

從Dubbo分層的角度看,詳細時序圖如下,藍色部分是服務消費端,淺綠色部分是服務提供端,時序圖從消費端一次Dubbo方法呼叫開始,到服務端本地方法執行結束。

從Dubbo核心領域物件的角度看,我們引用Dubbo官方文件說明,如下圖所示。Dubbo核心領域物件是Invoker,消費端代理物件是proxy,包裝了Invoker的呼叫;服務端代理物件是一個Invoker,他通過exporter包裝,當服務端接收到呼叫請求後,通過exporter找到Invoker,Invoker去實際執行使用者的業務邏輯。

(引用自Dubbo官方文件)

1.2 Dubbo服務的註冊和發現流程

下圖出自開發指南-框架設計-引用服務時序,主要流程是:從註冊中心訂閱服務提供者,然後啟動tcp服務連線遠端提供者,將多個服務提供者合併成一個Invoker,用這個Invoker建立代理物件。

下圖出自開發指南-框架設計-暴露服務時序,主要流程是:建立本地服務的代理Invoker,啟動tcp服務暴露服務,然後將服務註冊到註冊中心。

接下來我們結合Dubbo服務的註冊和發現,從配置層開始解釋每一層的作用和原理。

示例服務介面定義如下:

public interface CouponServiceViewFacade {
 
    /**
     * 查詢單張優惠券
     */
    CouponViewDTO query(String code);
}

二、配置層

2.1. 做什麼

配置層提供配置處理工具類,在容器啟動的時候,通過ServiceConfig.export例項化服務提供者,ReferenceConfig.get例項化服務消費者物件。

Dubbo應用使用spring容器啟動時,Dubbo服務提供者配置處理器通過ServiceConfig.export啟動Dubbo遠端服務暴露本地服務。Dubbo服務消費者配置處理器通過ReferenceConfig.get例項化一個代理物件,並通過註冊中心服務發現,連線遠端服務提供者。

Dubbo配置可以使用註解和xml兩種形式,本文采用註解的形式進行說明。

2.2. 怎麼做

2.2.1 服務消費端的解析

Spring容器啟動過程中,填充bean屬性時,對含有Dubbo引用註解的屬性使用org.apache.dubbo.config.spring.beans.factory.annotation.ReferenceAnnotationBeanPostProcessor進行初始化。如下是ReferenceAnnotationBeanPostProcessor的構造方法,Dubbo服務消費者註解處理器處理以下三個註解:DubboReference.class、Reference.class、com.alibaba.dubbo.config.annotation.Reference.class修飾的類。

ReferenceAnnotationBeanPostProcessor類定義:

public class ReferenceAnnotationBeanPostProcessor extends AbstractAnnotationBeanPostProcessor implements
        ApplicationContextAware {
 
    public ReferenceAnnotationBeanPostProcessor() {
        super(DubboReference.class, Reference.class, com.alibaba.dubbo.config.annotation.Reference.class);
    }
}

Dubbo服務發現到這一層,Dubbo即將開始構建服務消費者的代理物件,CouponServiceViewFacade介面的代理實現類。

2.2.2 服務提供端的解析

Spring容器啟動的時候,載入註解@org.apache.dubbo.config.spring.context.annotation.DubboComponentScan指定範圍的類,並初始化;初始化使用dubbo實現的擴充套件點org.apache.dubbo.config.spring.beans.factory.annotation.ServiceClassPostProcessor。

ServiceClassPostProcessor處理的註解類有DubboService.class,Service.class,com.alibaba.dubbo.config.annotation.Service.class。

如下是ServiceClassPostProcessor類定義:

public class ServiceClassPostProcessor implements BeanDefinitionRegistryPostProcessor, EnvironmentAware,
        ResourceLoaderAware, BeanClassLoaderAware {
 
    private final static List<Class<? extends Annotation>> serviceAnnotationTypes = asList(
            DubboService.class,Service.class,com.alibaba.dubbo.config.annotation.Service.class
    );
。。。
}

等待Spring容器ContextRefreshedEvent事件,啟動Dubbo應用服務監聽埠,暴露本地服務。

Dubbo服務註冊到這一層,Dubbo即將開始構建服務提供者的代理物件,CouponServiceViewFacade實現類的反射代理類。

三、 代理層

3.1 做什麼

為服務消費者生成代理實現例項,為服務提供者生成反射代理例項。

CouponServiceViewFacade的代理實現例項,消費端在呼叫query方法的時候,實際上是呼叫代理實現例項的query方法,通過他呼叫遠端服務。

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
 
package org.apache.dubbo.common.bytecode;
 
public class proxy1 implements DC, Destroyable, CouponServiceViewFacade, EchoService {
    public static Method[] methods;
    private InvocationHandler handler;
 
    public proxy1(InvocationHandler var1) {
        this.handler = var1;
    }
 
    public proxy1() {
    }
 
    public CouponViewDTO query(String var1) {
        Object[] var2 = new Object[]{var1};
        Object var3 = this.handler.invoke(this, methods[0], var2);
        return (CouponViewDTO)var3;
    }
}

CouponServiceViewFacade的反射代理例項,服務端接收到請求後,通過該例項的Invoke方法最終執行本地方法query。

/**
 * InvokerWrapper
 */
public class AbstractProxyInvoker<CouponServiceViewFacade> implements Invoker<CouponServiceViewFacade> {
        // 。。。
 
    public AbstractProxyInvoker(CouponServiceViewFacade proxy, Class<CouponServiceViewFacade> type, URL url) {
        //。。。
        this.proxy = proxy;
        this.type = type;
        this.url = url;
    }
 
    @Override
    public Result invoke(Invocation invocation) throws RpcException {
        //。。。
        Object value = doInvoke(proxy, invocation.getMethodName(), invocation.getParameterTypes(), invocation.getArguments());
        //。。。
    }
 
    protected Object doInvoke(CouponServiceViewFacade proxy, String methodName, Class<?>[] parameterTypes, Object[] arguments) throws Throwable{
        //。。。
        return wrapper.invokeMethod(proxy, methodName, parameterTypes, arguments);
    }
 
}

3.2 怎麼做

Dubbo代理工廠介面定義如下,定義了服務提供者和服務消費者的代理物件工廠方法。服務提供者代理物件和服務消費者代理物件都是通過工廠方法建立,工廠實現類可以通過SPI自定義擴充套件。

@SPI("javassist")
public interface ProxyFactory {
 
    // 生成服務消費者代理物件
    @Adaptive({PROXY_KEY})
    <T> T getProxy(Invoker<T> invoker) throws RpcException;
 
    // 生成服務消費者代理物件
    @Adaptive({PROXY_KEY})
    <T> T getProxy(Invoker<T> invoker, boolean generic) throws RpcException;
 
     
    // 生成服務提供者代理物件
    @Adaptive({PROXY_KEY})
    <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) throws RpcException;
 
}

3.2.1 服務消費者

3.2.1.1 建立服務消費者代理類

預設採用Javaassist代理工廠實現,Proxy.getProxy(interfaces)建立代理工廠類,newInstance建立具體代理物件。

public class JavassistProxyFactory extends AbstractProxyFactory {
 
    @Override
    @SuppressWarnings("unchecked")
    public <T> T getProxy(Invoker<T> invoker, Class<?>[] interfaces) {
        return (T) Proxy.getProxy(interfaces).newInstance(new InvokerInvocationHandler(invoker));
    }
 
    。。。
 
}

3.2.1.2 服務消費者代理

Dubbo為每個服務消費者生成兩個代理類:代理工廠類,介面代理類。

CouponServiceViewFacade代理工廠類:

public class Proxy1 extends Proxy implements DC {
    public Proxy1() {
    }
 
    public Object newInstance(InvocationHandler var1) {
        return new proxy1(var1);
    }
}

最終生成的CouponServiceViewFacade的代理物件如下,其中handler的實現類是InvokerInvocationHandler,this.handler.invoke方法發起Dubbo呼叫。

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
 
package org.apache.dubbo.common.bytecode;
 
public class proxy1 implements DC, Destroyable, CouponServiceViewFacade, EchoService {
    public static Method[] methods;
    private InvocationHandler handler;
 
    public proxy1(InvocationHandler var1) {
        this.handler = var1;
    }
 
    public proxy1() {
    }
 
    public CouponViewDTO query(String var1) {
        Object[] var2 = new Object[]{var1};
        Object var3 = this.handler.invoke(this, methods[0], var2);
        return (CouponViewDTO)var3;
    }
}

3.2.2 服務提供者

3.2.2.1 建立服務提供者代理類

預設Javaassist代理工廠實現,使用Wrapper包裝本地服務提供者。proxy是實際的服務提供者例項,即CouponServiceViewFacade的本地實現類,type是介面類定義,URL是injvm協議URL。

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 {
                return wrapper.invokeMethod(proxy, methodName, parameterTypes, arguments);
            }
        };
    }
 
}

3.2.2.2 Wrapper包裝類

Dubbo為每個服務提供者的本地實現生成一個Wrapper代理類,抽象Wrapper類定義如下:

public abstract class Wrapper {
    。。。
 
    abstract public Object invokeMethod(Object instance, String mn, Class<?>[] types, Object[] args) throws NoSuchMethodException, InvocationTargetException;
}

具體Wrapper代理類使用位元組碼技術動態生成,本地服務CouponServiceViewFacade的代理包裝類舉例:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
 
package org.apache.dubbo.common.bytecode;
 
import com.xxx.CouponServiceViewFacade;
import java.lang.reflect.InvocationTargetException;
import java.util.Map;
import org.apache.dubbo.common.bytecode.ClassGenerator.DC;
 
public class Wrapper25 extends Wrapper implements DC {
  。。。
 
    public Wrapper25() {
    }
 
    public Object invokeMethod(Object var1, String var2, Class[] var3, Object[] var4) throws InvocationTargetException {
        CouponServiceViewFacade var5;
        try {
            var5 = (CouponServiceViewFacade)var1;
        } catch (Throwable var8) {
            throw new IllegalArgumentException(var8);
        }
 
        try {
            if ("query".equals(var2) && var3.length == 1) {
                return var5.query((String)var4[0]);
            }
        } catch (Throwable var9) {
            throw new InvocationTargetException(var9);
        }
 
        throw new NoSuchMethodException("Not found method \"" + var2 + "\" in class com.xxx.CouponServiceViewFacade.");
    }


。。。
 
}

在服務初始化流程中,服務消費者代理物件生成後初始化就完成了,服務消費端的初始化順序:ReferenceConfig.get->從註冊中心訂閱服務->啟動客戶端->建立DubboInvoker->構建ClusterInvoker→建立服務代理物件;

而服務提供端的初始化才剛開始,服務提供端的初始化順序:ServiceConfig.export->建立AbstractProxyInvoker,通過Injvm協議關聯本地服務->啟動服務端→註冊服務到註冊中心。

接下來我們講註冊層。

四、註冊層

4.1 做什麼

封裝服務地址的註冊與發現,以服務 URL 為配置中心。服務提供者本地服務啟動成功後,監聽Dubbo埠成功後,通過註冊協議釋出到註冊中心;服務消費者通過註冊協議訂閱服務,啟動本地應用連線遠端服務。

註冊協議URL舉例:

zookeeper://xxx/org.apache.dubbo.registry.RegistryService?application=xxx&...

4.2 怎麼做

註冊服務工廠介面定義如下,註冊服務實現通過SPI擴充套件,預設是zk作為註冊中心。

@SPI("dubbo")
public interface RegistryFactory {
 
    @Adaptive({"protocol"})
    Registry getRegistry(URL url);
 
}

註冊服務介面定義;

public interface RegistryService {
 
    
    void register(URL url);
 
    
    void unregister(URL url);
 
    
    void subscribe(URL url, NotifyListener listener);
 
    
    void unsubscribe(URL url, NotifyListener listener);
 
    
    List<URL> lookup(URL url);
 
}

五、叢集層

5.1 做什麼

服務消費方從註冊中心訂閱服務提供者後,將多個提供者包裝成一個提供者,並且封裝路由及負載均衡策略;並橋接註冊中心,以 Invoker 為中心,擴充套件介面為 Cluster, Directory, Router, LoadBalance;

服務提供端不存在叢集層。

5.2 怎麼做

5.2.1 Cluster

叢集領域主要負責將多個服務提供者包裝成一個ClusterInvoker,注入路由處理器鏈和負載均衡策略。主要策略有:failover、failfast、failsafe、failback、forking、available、mergeable、broadcast、zone-aware。

叢集介面定義如下,只有一個方法:從服務目錄中的多個服務提供者構建一個ClusterInvoker。

作用是對上層-代理層遮蔽叢集層的邏輯;代理層呼叫服務方法只需執行Invoker.invoke,然後通過ClusterInvoker內部的路由策略和負載均衡策略計算具體執行哪個遠端服務提供者。

@SPI(Cluster.DEFAULT)
public interface Cluster {
    String DEFAULT = FailoverCluster.NAME;
 
    @Adaptive
    <T> Invoker<T> join(Directory<T> directory) throws RpcException;
 
  。。。
}

ClusterInvoker執行邏輯,先路由策略過濾,然後負載均衡策略選擇最終的遠端服務提供者。示例代理如下:

   public abstract class AbstractClusterInvoker<T> implements ClusterInvoker<T> {
 
。。。
    @Override
    public Result invoke(final Invocation invocation) throws RpcException {
        checkWhetherDestroyed();
 
        // binding attachments into invocation.
        Map<String, Object> contextAttachments = RpcContext.getContext().getObjectAttachments();
        if (contextAttachments != null && contextAttachments.size() != 0) {
            ((RpcInvocation) invocation).addObjectAttachments(contextAttachments);
        }
 
        // 叢集invoker執行時,先使用路由鏈過濾服務提供者
        List<Invoker<T>> invokers = list(invocation);
        LoadBalance loadbalance = initLoadBalance(invokers, invocation);
        RpcUtils.attachInvocationIdIfAsync(getUrl(), invocation);
        return doInvoke(invocation, invokers, loadbalance);
    }
。。。
 
}

5.2.2 Directory

服務目錄介面定義如下,Dubbo方法介面呼叫時,將方法資訊包裝成invocation,通過Directory.list過濾可執行的遠端服務。

通過org.apache.dubbo.registry.integration.RegistryDirectory橋接註冊中心,監聽註冊中心的路由配置修改、服務治理等事件。

public interface Directory<T> extends Node {
 
    
    Class<T> getInterface();
 
    List<Invoker<T>> list(Invocation invocation) throws RpcException;
 
    List<Invoker<T>> getAllInvokers();
 
    URL getConsumerUrl();
 
}

5.2.3 Router

從已知的所有服務提供者中根據路由規則刷選服務提供者。

服務訂閱的時候初始化路由處理器鏈,呼叫遠端服務的時候先使用路由鏈過濾服務提供者,再通過負載均衡選擇具體的服務節點。

路由處理器鏈工具類,提供路由篩選服務,監聽更新服務提供者。

public class RouterChain<T> {
 
。。。
     
    public List<Invoker<T>> route(URL url, Invocation invocation) {
        List<Invoker<T>> finalInvokers = invokers;
        for (Router router : routers) {
            finalInvokers = router.route(finalInvokers, url, invocation);
        }
        return finalInvokers;
    }
 
    /**
     * Notify router chain of the initial addresses from registry at the first time.
     * Notify whenever addresses in registry change.
     */
    public void setInvokers(List<Invoker<T>> invokers) {
        //路由鏈監聽更新服務提供者
        this.invokers = (invokers == null ? Collections.emptyList() : invokers);
        routers.forEach(router -> router.notify(this.invokers));
    }
 
}

訂閱服務的時候,將路由鏈注入到RegistryDirectory中;

public class RegistryProtocol implements Protocol {
    。。。
 
    private <T> Invoker<T> doRefer(Cluster cluster, Registry registry, Class<T> type, URL url) {
        。。。
        // 服務目錄初始化路由鏈
        directory.buildRouterChain(subscribeUrl);
        directory.subscribe(toSubscribeUrl(subscribeUrl));
         。。。
        return registryInvokerWrapper;
    }
 
    。。。
 
}

5.2.4 LoadBalance

根據不同的負載均衡策略從可使用的遠端服務例項中選擇一個,負責均衡介面定義如下:

@SPI(RandomLoadBalance.NAME)
public interface LoadBalance {
 
    @Adaptive("loadbalance")
    <T> Invoker<T> select(List<Invoker<T>> invokers, URL url, Invocation invocation) throws RpcException;
 
}

六、監控層

6.1 做什麼

監控RPC呼叫次數和呼叫時間,以Statistics為中心,擴充套件介面為 MonitorFactory, Monitor, MonitorService。

6.2 怎麼做

監控工廠介面定義,通過SPI方式進行擴充套件;

@SPI("dubbo")
public interface MonitorFactory {
 
    
    @Adaptive("protocol")
    Monitor getMonitor(URL url);
 
}

@Adaptive("protocol")
Monitor getMonitor(URL url);

監控服務介面定義如下,定義了一些預設的監控維度和指標項;

public interface MonitorService {
 
    // 監控維度
 
    String APPLICATION = "application";
 
    String INTERFACE = "interface";
 
    String METHOD = "method";
 
    String GROUP = "group";
 
    String VERSION = "version";
 
    String CONSUMER = "consumer";
 
    String PROVIDER = "provider";
 
    String TIMESTAMP = "timestamp";
 
    //監控指標項
 
    String SUCCESS = "success";
 
    String FAILURE = "failure";
 
    String INPUT = INPUT_KEY;
 
    String OUTPUT = OUTPUT_KEY;
 
    String ELAPSED = "elapsed";
 
    String CONCURRENT = "concurrent";
 
    String MAX_INPUT = "max.input";
 
    String MAX_OUTPUT = "max.output";
 
    String MAX_ELAPSED = "max.elapsed";
 
    String MAX_CONCURRENT = "max.concurrent";

    void collect(URL statistics);
 
    List<URL> lookup(URL query);
 
}

6.2.1 MonitorFilter

通過過濾器的方式收集服務的呼叫次數和呼叫時間,預設實現:

org.apache.dubbo.monitor.dubbo.DubboMonitor。

七、協議層

7.1 做什麼

封裝 RPC 呼叫,以 Invocation, Result 為中心,擴充套件介面為 Protocol, Invoker, Exporter。

接下來介紹Dubbo RPC過程中的常用概念:

1)Invocation是請求會話領域模型,每次請求有相應的Invocation例項,負責包裝dubbo方法資訊為請求引數;

2)Result是請求結果領域模型,每次請求都有相應的Result例項,負責包裝dubbo方法響應;

3)Invoker是實體域,代表一個可執行實體,有本地、遠端、叢集三類;

4)Exporter服務提供者Invoker管理實體;

5)Protocol是服務域,管理Invoker的生命週期,提供服務的暴露和引用入口;

服務初始化流程中,從這一層開始進行遠端服務的暴露和連線引用。

對於CouponServiceViewFacade服務來說,服務提供端會監聽Dubbo埠啟動tcp服務;服務消費端通過註冊中心發現服務提供者資訊,啟動tcp服務連線遠端提供者。

7.2 怎麼做

協議介面定義如下,統一抽象了不同協議的服務暴露和引用模型,比如InjvmProtocol只需將Exporter,Invoker關聯本地實現。DubboProtocol暴露服務的時候,需要監控本地埠啟動服務;引用服務的時候,需要連線遠端服務。

@SPI("dubbo")
public interface Protocol {
 
    
    int getDefaultPort();
 
    
    @Adaptive
    <T> Exporter<T> export(Invoker<T> invoker) throws RpcException;
 
    
    @Adaptive
    <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException;
 
    
    void destroy();
 
    
    default List<ProtocolServer> getServers() {
        return Collections.emptyList();
    }
 
}

Invoker介面定義

Invocation是RPC呼叫的會話物件,負責包裝請求引數;Result是RPC呼叫的結果物件,負責包裝RPC呼叫的結果物件,包括異常類資訊;

public interface Invoker<T> extends Node {
 
    
    Class<T> getInterface();
 
    
    Result invoke(Invocation invocation) throws RpcException;
 
}

7.2.1 服務的暴露和引用

服務暴露的時候,開啟RPC服務端;引用服務的時候,開啟RPC客戶端。

public class DubboProtocol extends AbstractProtocol {
 
。。。
 
    @Override
    public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
        。。。
        // 開啟rpc服務端
        openServer(url);
        optimizeSerialization(url);
 
        return exporter;
    }
 
    @Override
    public <T> Invoker<T> protocolBindingRefer(Class<T> serviceType, URL url) throws RpcException {
        optimizeSerialization(url);
 
        // 建立dubbo invoker,開啟rpc客戶端
        DubboInvoker<T> invoker = new DubboInvoker<T>(serviceType, url, getClients(url), invokers);
        invokers.add(invoker);
 
        return invoker;
    }
 。。。
 
}

7.2.2 服務端響應請求

接收響應請求;

private ExchangeHandler requestHandler = new ExchangeHandlerAdapter() {
 
        @Override
        public CompletableFuture<Object> reply(ExchangeChannel channel, Object message) throws RemotingException {
                           。。。
            Invocation inv = (Invocation) message;
            Invoker<?> invoker = getInvoker(channel, inv);

            RpcContext.getContext().setRemoteAddress(channel.getRemoteAddress());
            //呼叫本地服務
            Result result = invoker.invoke(inv);
            return result.thenApply(Function.identity());
        }
 
        。。。
    };

7.2.3 客戶端傳送請求

呼叫遠端服務;

public class DubboInvoker<T> extends AbstractInvoker<T> {
 
    。。。
 
    @Override
    protected Result doInvoke(final Invocation invocation) throws Throwable {
        。。。
            boolean isOneway = RpcUtils.isOneway(getUrl(), invocation);
            int timeout = calculateTimeout(invocation, methodName);
            if (isOneway) {
                boolean isSent = getUrl().getMethodParameter(methodName, Constants.SENT_KEY, false);
                currentClient.send(inv, isSent);
                return AsyncRpcResult.newDefaultAsyncResult(invocation);
            } else {
                ExecutorService executor = getCallbackExecutor(getUrl(), inv);
                CompletableFuture<AppResponse> appResponseFuture =
                        currentClient.request(inv, timeout, executor).thenApply(obj -> (AppResponse) obj);
                FutureContext.getContext().setCompatibleFuture(appResponseFuture);
                AsyncRpcResult result = new AsyncRpcResult(appResponseFuture, inv);
                result.setExecutor(executor);
                return result;
            }

    }
 
}

八、交換層

8.1 做什麼

封裝請求響應模式,同步轉非同步,以 Request, Response 為中心,擴充套件介面為 Exchanger, ExchangeChannel, ExchangeClient, ExchangeServer。

使用request包裝Invocation作為完整的請求物件,使用response包裝result作為完整的響應物件;Request、Response相比Invocation、Result新增了Dubbo的協議頭。

8.2 怎麼做

交換器物件介面定義,定義了遠端服務的繫結和連線,使用SPI方式進行擴充套件;

@SPI(HeaderExchanger.NAME)
public interface Exchanger {
 
    
    @Adaptive({Constants.EXCHANGER_KEY})
    ExchangeServer bind(URL url, ExchangeHandler handler) throws RemotingException;
 
    
    @Adaptive({Constants.EXCHANGER_KEY})
    ExchangeClient connect(URL url, ExchangeHandler handler) throws RemotingException;
 
}

@Adaptive({Constants.EXCHANGER_KEY})
ExchangeServer bind(URL url, ExchangeHandler handler) throws RemotingException;


@Adaptive({Constants.EXCHANGER_KEY})
ExchangeClient connect(URL url, ExchangeHandler handler) throws RemotingException;

交換層模型類圖:

8.2.1 服務提供者

服務提供端接收到請求後,本地執行,傳送響應結果;

public class HeaderExchangeHandler implements ChannelHandlerDelegate {
 

   。。。


    void handleRequest(final ExchangeChannel channel, Request req) throws RemotingException {
       //封裝響應
        Response res = new Response(req.getId(), req.getVersion());
   。。。
        Object msg = req.getData();
        try {
            CompletionStage<Object> future = handler.reply(channel, msg);
            future.whenComplete((appResult, t) -> {
                try {
                    if (t == null) {
                        res.setStatus(Response.OK);
                        res.setResult(appResult);
                    } else {
                        res.setStatus(Response.SERVICE_ERROR);
                        res.setErrorMessage(StringUtils.toString(t));
                    }
                    channel.send(res);
                } catch (RemotingException e) {
                    logger.warn("Send result to consumer failed, channel is " + channel + ", msg is " + e);
                }
            });
        } catch (Throwable e) {
            res.setStatus(Response.SERVICE_ERROR);
            res.setErrorMessage(StringUtils.toString(e));
            channel.send(res);
        }
    }
。。。
}

8.2.2 服務消費者

服務消費端發起請求的封裝,方法執行成功後,返回一個future;

final class HeaderExchangeChannel implements ExchangeChannel {
 
。。。
 
   //封裝請求實體
    @Override
    public CompletableFuture<Object> request(Object request, int timeout, ExecutorService executor) throws RemotingException {
       。。。


        // create request.
        Request req = new Request();
        req.setVersion(Version.getProtocolVersion());
        req.setTwoWay(true);
        //RpcInvocation
        req.setData(request);
        DefaultFuture future = DefaultFuture.newFuture(channel, req, timeout, executor);
        try {
            channel.send(req);
        } catch (RemotingException e) {
            future.cancel();
            throw e;
        }
        return future;
    }
。。。
 
}

九、傳輸層

9.1 做什麼

抽象傳輸層模型,相容netty、mina、grizzly等通訊框架。

9.2 怎麼做

傳輸器介面定義如下,它與交換器Exchanger介面定義相似,區別在於Exchanger是圍繞Dubbo的Request和Response封裝的操作門面介面,而Transporter更加的底層,Exchanger用於隔離Dubbo協議層和通訊層。

@SPI("netty")
public interface Transporter {
 
    
    @Adaptive({Constants.SERVER_KEY, Constants.TRANSPORTER_KEY})
    RemotingServer bind(URL url, ChannelHandler handler) throws RemotingException;
 
    
    @Adaptive({Constants.CLIENT_KEY, Constants.TRANSPORTER_KEY})
    Client connect(URL url, ChannelHandler handler) throws RemotingException;
 
}

自定義傳輸層模型

通過SPI的方式,動態選擇具體的傳輸框架,預設是netty;

public class Transporters {
 
    。。。
 
    public static RemotingServer bind(URL url, ChannelHandler... handlers) throws RemotingException {
        。。。

        return getTransporter().bind(url, handler);
    }
 
 
    public static Client connect(URL url, ChannelHandler... handlers) throws RemotingException {
        。。。
        return getTransporter().connect(url, handler);
    }
 
    public static Transporter getTransporter() {
        return ExtensionLoader.getExtensionLoader(Transporter.class).getAdaptiveExtension();
    }
 
}

netty框架的channel適配如下,採用裝飾模式,使用netty框架的channel作為Dubbo自定義的channel做實現;

final class NettyChannel extends AbstractChannel {
 
    private NettyChannel(Channel channel, URL url, ChannelHandler handler) {
        super(url, handler);
        if (channel == null) {
            throw new IllegalArgumentException("netty channel == null;");
        }
        this.channel = channel;
    }
 
}

十、序列化

10.1 做什麼

抽象序列化模型,相容多種序列化框架,包括:fastjson、fst、hessian2、kryo、kryo2、protobuf等,通過序列化支援跨語言的方式,支援跨語言的RPC呼叫。

10.2 怎麼做

定義Serialization擴充套件點,預設hessian2,支援跨語言。Serialization介面實際是一個工廠介面,通過SPI擴充套件;實際序列化和反序列化工作由ObjectOutput,ObjectInput完成,通過裝飾模式讓hessian2完成實際工作。

@SPI("hessian2")
public interface Serialization {
 
    
    byte getContentTypeId();
 
    
    String getContentType();
 
 
    @Adaptive
    ObjectOutput serialize(URL url, OutputStream output) throws IOException;
 
    
    @Adaptive
    ObjectInput deserialize(URL url, InputStream input) throws IOException;
 
}

10.2.1 通訊協議設計

下圖出自開發指南-實現細節-遠端通訊細節,描述Dubbo協議頭設計;

  • 0-15bit表示Dubbo協議魔法數字,值:0xdabb;

  • 16bit請求響應標記,Request - 1; Response - 0;

  • 17bit請求模式標記,只有請求訊息才會有,1表示需要服務端返回響應;

  • 18bit是事件訊息標記,1表示該訊息是事件訊息,比如心跳訊息;

  • 19-23bit是序列化型別標記,hessian序列化id是2,fastjson是6,詳見org.apache.dubbo.common.serialize.Constants;

  • 24-31bit表示狀態,只有響應訊息才有用;

  • 32-64bit是RPC請求ID;

  • 96-128bit是會話資料長度;

  • 128是訊息體位元組序列;

十一、總結

Dubbo將RPC整個過程分成核心的代理層、註冊層、叢集層、協議層、傳輸層等,層與層之間的職責邊界明確;核心層都通過介面定義,不依賴具體實現,這些介面串聯起來形成了Dubbo的骨架;這個骨架也可以看作是Dubbo的核心,核心使用SPI 機制載入外掛(擴充套件點),達到高度可擴充套件。

vivo網際網路伺服器團隊-Wang Genfu

相關文章