1

1. Overview of Dubbo's layered overall design

Let's start with the following picture to briefly introduce the concept of Dubbo's layered design:

(Quoted from Duboo Development Guide-Framework Design Document)

As shown in the figure, the overall RPC implemented by Dubbo is divided into 10 layers: service, config, proxy, registry, cluster, monitor, protocol, exchange, transport, serialize.

service : interface and implementation class defined by the user;

config : Responsible for parsing the configuration defined by Dubbo, such as annotations and xml configuration, various parameters;

proxy : Mainly responsible for generating proxy objects for consumers and providers, loading framework functions, such as provider filter chain, extension points;

registry : responsible for the definition of registration services and the loading of implementation classes;

cluster : Only consumers have such a layer, responsible for packaging multiple service providers into a "big provider", loading load balancing, routing and other extension points;

monitor : Define the monitoring service and load the monitoring implementation provider;

protocol : Encapsulate the RPC call interface and manage the life cycle of the calling entity;

exchange : encapsulation request response mode, synchronous to asynchronous;

transport : abstract transport layer model, compatible with communication frameworks such as netty, mina, grizzly;

serialize : Abstract serialization model, compatible with a variety of serialization frameworks, including: fastjson, fst, hessian2, kryo, kryo2, protobuf, etc., through serialization to support cross-language methods, support cross-language rpc calls;

The purpose of Dubbo's layering is to achieve decoupling between layers. Each layer defines interface specifications. It can also customize and load different implementations according to different business requirements, which has extremely high scalability.

1.1. RPC call process

Next, a simple description of a complete rpc call process is combined with the above figure:

From the perspective of Dubbo layering, the detailed sequence diagram is as follows, the blue part is the service consumer side, and the light green part is the service provider side. The sequence diagram starts from a Dubbo method call on the consumer side and ends with the execution of the local method on the server side.

From the perspective of Dubbo core domain objects, we quote Dubbo official document description , as shown in the figure below. Dubbo's core domain object is Invoker, and the consumer proxy object is proxy, which wraps the call of Invoker; the server proxy object is an Invoker, which is packaged by exporter. When the server receives the call request, it finds the Invoker through the exporter, and the Invoker goes to the actual Execute the user's business logic.

(Quoted from Dubbo official document)

1.2 Dubbo service registration and discovery process

The following figure is from the Development Guide-Framework Design-Reference Service Timing . The main process is: subscribe to the service provider from the registry, then start the tcp service to connect to the remote provider, merge multiple service providers into one Invoker, use this Invoker creates a proxy object.

The following figure is from the Development Guide-Framework Design-Exposing Service Timing . The main process is: create a proxy Invoker for the local service, start the tcp service exposure service, and then register the service to the registry.

Next, we combine the registration and discovery of Dubbo services, starting with the configuration layer to explain the role and principle of each layer.

The sample service interface is defined as follows:

public interface CouponServiceViewFacade {
 
    /**
     * 查询单张优惠券
     */
    CouponViewDTO query(String code);
}

Second, the configuration layer

2.1. What to do

The configuration layer provides configuration processing tool classes. When the container is started, the service provider is instantiated through ServiceConfig.export, and the service consumer object is instantiated by ReferenceConfig.get.

When the Dubbo application is started using the spring container, the Dubbo service provider configuration processor starts the Dubbo remote service through ServiceConfig.export to expose the local service. The Dubbo service consumer configuration processor instantiates a proxy object through ReferenceConfig.get, discovers it through the registry service, and connects to the remote service provider.

Dubbo configuration can use two forms: annotation and xml. This article uses the form of annotation to explain.

2.2. How to do it

2.2.1 Analysis of Service Consumer

During the startup of the Spring container, when filling the bean properties, use org.apache.dubbo.config.spring.beans.factory.annotation.ReferenceAnnotationBeanPostProcessor to initialize the properties with Dubbo reference annotations. The following is the construction method of ReferenceAnnotationBeanPostProcessor. The Dubbo service consumer annotation processor processes the following three annotations: DubboReference.class, Reference.class, com.alibaba.dubbo.config.annotation.Reference.class modified class.

ReferenceAnnotationBeanPostProcessor class definition:

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

Dubbo service discovers this layer, Dubbo is about to start building proxy objects for service consumers, the proxy implementation class of the CouponServiceViewFacade interface.

2.2.2 Analysis of the service provider

When the Spring container starts, it loads and initializes the annotation @org.apache.dubbo.config.spring.context.annotation.DubboComponentScan the specified scope of the class, and initializes the extension point org.apache.dubbo.config.spring.beans implemented by dubbo. .factory.annotation.ServiceClassPostProcessor.

The annotation classes processed by ServiceClassPostProcessor are DubboService.class, Service.class, com.alibaba.dubbo.config.annotation.Service.class.

The following is the ServiceClassPostProcessor class definition:

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
    );
。。。
}

Wait for the ContextRefreshedEvent event of the Spring container, start the Dubbo application service monitoring port, and expose the local service.

The Dubbo service is registered to this layer, and Dubbo is about to build the proxy object of the service provider, the reflection proxy class of the CouponServiceViewFacade implementation class.

Third, the agent layer

3.1 What to do

Generate proxy implementation instances for service consumers and reflection proxy instances for service providers.

The proxy implementation instance of CouponServiceViewFacade, when the consumer side calls the query method, it actually calls the query method of the proxy implementation instance through which it calls the remote service.

//
// 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;
    }
}

The reflection proxy instance of CouponServiceViewFacade. After the server receives the request, it finally executes the local method query through the Invoke method of the instance.

/**
 * 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 How to do it

The Dubbo proxy factory interface is defined as follows, which defines the proxy object factory methods for service providers and service consumers. Both service provider proxy objects and service consumer proxy objects are created through factory methods, and the factory implementation class can be customized and extended through 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 Service Consumer

3.2.1.1 Create a service consumer proxy class

The Javaassist proxy factory is implemented by default, Proxy.getProxy(interfaces) creates a proxy factory class, and newInstance creates a specific proxy object.

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 Service Consumer Agent

Dubbo generates two proxy classes for each service consumer: proxy factory class and interface proxy class.

CouponServiceViewFacade proxy factory class:

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

finally generated by 16147e4227db6c is as follows, where the implementation class of the handler is InvokerInvocationHandler, and the this.handler.invoke method initiates a Dubbo call.

//
// 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 Service provider

3.2.2.1 Create a service provider proxy class

The default Javaassist proxy factory implementation uses Wrapper to wrap the local service provider. The proxy is the actual service provider instance, that is, the local implementation class of CouponServiceViewFacade, the type is the interface class definition, and the URL is the injvm protocol 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 packaging class

Dubbo generates a Wrapper proxy class for the local implementation of each service provider. The abstract Wrapper class is defined as follows:

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

The specific Wrapper proxy class is dynamically generated using bytecode technology. Examples of proxy wrapper classes for the local service 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.");
    }


。。。
 
}

In the service initialization process, the initialization is completed after the service consumer proxy object is generated. The initialization sequence of the service consumer end: ReferenceConfig.get->Subscribe service from the registry ->Start the client->Create DubboInvoker->Build ClusterInvoker→Create service Proxy object

The initialization of the service provider has just begun. The initialization sequence of the : ServiceConfig.export->Create AbstractProxyInvoker, associate the local service through the Injvm protocol->Start the server->register the service to the registry.

Next we talk about the registration layer.

Fourth, the registration layer

4.1 What to do

Encapsulates the registration and discovery of the service address, with the service URL as the configuration center. After the service provider's local service is started successfully, and after the successful monitoring of the Dubbo port, it is published to the registration center through the registration agreement; the service consumer subscribes to the service through the registration agreement, and starts the local application to connect to the remote service.

Examples of registration protocol URLs:

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

4.2 How to do

The registration service factory interface is defined as follows. The registration service implementation is extended through SPI. The default is zk as the registration center.

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

Registration service interface definition;

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);
 
}

Five, the cluster layer

5.1 What to do

After subscribing to the service provider from the registry, the service consumer packs multiple providers into one provider, and encapsulates routing and load balancing strategies; and bridges the registry with the Invoker as the center, and the extension interface is Cluster, Directory, Router, LoadBalance;

There is no cluster layer on the service provider side.

5.2 How to do

5.2.1 Cluster

The cluster field is mainly responsible for packaging multiple service providers into a ClusterInvoker, injecting the routing processor chain and load balancing strategies. The main strategies are: failover, failfast, failsafe, failback, forking, available, mergeable, broadcast, zone-aware.

The cluster interface is defined as follows, and there is only one method: build a ClusterInvoker from multiple service providers in the service catalog.

The function is to shield the logic of the cluster layer from the upper-agent layer; the agent layer only needs to execute Invoker.invoke to call the service method, and then calculate which remote service provider to execute through the routing strategy and load balancing strategy inside the ClusterInvoker.

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

ClusterInvoker executes logic, first routing policy filtering, and then load balancing policy selects the final remote service provider. The sample agent is as follows:

   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

The service directory interface is defined as follows. When the Dubbo method interface is called, the method information is packaged into invocation, and the executable remote service is filtered through Directory.list.

Bridging the registry through org.apache.dubbo.registry.integration.RegistryDirectory, monitoring the routing configuration modification, service management and other events of the registry.

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

Select service providers from all known service providers according to routing rules.

The routing processor chain is initialized when the service is subscribed, and the routing chain is used to filter service providers when calling remote services, and then specific service nodes are selected through load balancing.

The route processor chain tool class provides route screening services and monitors update service providers.

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));
    }
 
}

When subscribing to the service, inject the routing chain into 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

According to different load balancing strategies, select one of the available remote service instances, and the interface responsible for balancing is defined as follows:

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

Six, monitoring layer

6.1 What to do

Monitor the number of RPC calls and call time, centered on Statistics, and the extended interfaces are MonitorFactory, Monitor, MonitorService.

6.2 How to do

Monitor factory interface definition, expand through SPI;

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

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

The monitoring service interface is defined as follows, which defines some default monitoring dimensions and index items;

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

Collect service call times and call time by way of filters, the default implementation:

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

Seven, the protocol layer

7.1 What to do

Encapsulate RPC calls, centered on Invocation and Result, and extended interfaces as Protocol, Invoker, and Exporter.

Next, introduce the common concepts in the Dubbo RPC process:

1) Invocation is a request session domain model, each request has a corresponding Invocation instance, responsible for packaging dubbo method information as request parameters;

2) Result is the request result domain model, each request has a corresponding Result instance, responsible for packaging the dubbo method response;

3) Invoker is an entity domain, representing an executable entity, and has three categories: local, remote, and cluster;

4) Exporter service provider Invoker management entity;

5) Protocol is the service domain, manages the life cycle of the Invoker, and provides service exposure and reference entry;

In the service initialization process, remote service exposure and connection reference are carried out from this layer.

For the CouponServiceViewFacade service, the service provider listens to the Dubbo port to start the tcp service; the service consumer discovers the service provider information through the registry and starts the tcp service to connect to the remote provider.

7.2 How to do

The protocol interface is defined as follows, which uniformly abstracts the service exposure and reference models of different protocols. For example, InjvmProtocol only needs to associate Exporter and Invoker with local implementation. When DubboProtocol exposes the service, it needs to monitor the local port to start the service; when referencing the service, it needs to connect to the remote service.

@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 interface definition

Invocation is the session object of the RPC call, responsible for packaging request parameters; Result is the result object of the RPC call, responsible for packaging the result object of the RPC call, including exception information;

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

7.2.1 Service Exposure and Reference

When the service is exposed, the RPC server is turned on; when the service is referenced, the RPC client is turned on.

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 The server responds to the request

Receive response request;

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 Client sends request

Call remote service;

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. Exchange layer

8.1 What to do

Encapsulate the request response mode, synchronous to asynchronous, with Request and Response as the center, and the extension interfaces are Exchanger, ExchangeChannel, ExchangeClient, and ExchangeServer.

Use request to wrap Invocation as a complete request object, and use response to wrap result as a complete response object; Request and Response add Dubbo protocol headers compared to Invocation and Result.

8.2 How to do

The switch object interface definition defines the binding and connection of remote services, and uses SPI to expand;

@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;

Exchange layer model class diagram:

8.2.1 Service provider

After the service provider receives the request, it executes it locally and sends the response result;

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 Service Consumer

The service consumer initiates the encapsulation of the request. After the method is executed successfully, it returns a 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;
    }
。。。
 
}

Nine, the transport layer

9.1 What to do

Abstract transport layer model, compatible with communication frameworks such as netty, mina, grizzly, etc.

9.2 How to do it

The definition of the transport interface is as follows. It is similar to the Exchanger interface definition. The difference is that Exchanger is an operation facade interface encapsulated around Dubbo's Request and Response, while Transporter has a lower level. Exchanger is used to isolate the Dubbo protocol layer and the communication layer.

@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;
 
}

Custom transport layer model

Through SPI, the specific transmission framework is dynamically selected, the default is 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();
    }
 
}

The channel adaptation of the netty framework is as follows. The decoration mode is adopted, and the channel of the netty framework is used as a Dubbo custom channel for implementation;

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;
    }
 
}

Ten, serialization

10.1 What to do

Abstract serialization model, compatible with multiple serialization frameworks, including: fastjson, fst, hessian2, kryo, kryo2, protobuf, etc. It supports cross-language serialization and supports cross-language RPC calls.

10.2 How to do it

Define Serialization extension point, default hessian2, support cross-language. The Serialization interface is actually a factory interface, which is extended through SPI; the actual serialization and deserialization work is completed by ObjectOutput and ObjectInput, and hessian2 can complete the actual work through the decoration mode.

@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 Communication protocol design

The following figure is from the development guide-implementation details-remote communication details, describing the design of the Dubbo protocol header;

  • 0-15bit represents the magic number of Dubbo protocol, value: 0xdabb;
  • 16bit request response mark, Request-1; Response-0;
  • 17bit request mode flag, only the request message will have, 1 means that the server needs to return a response;
  • 18bit is the event message flag, 1 means that the message is an event message, such as a heartbeat message;
  • 19-23bit is the serialization type tag, hessian serialization id is 2, fastjson is 6, see org.apache.dubbo.common.serialize.Constants for details;
  • 24-31bit represents the status, only the response message is useful;
  • 32-64bit is the RPC request ID;
  • 96-128bit is the session data length;
  • 128 is the byte sequence of the message body;

11. Summary

Dubbo divides the entire RPC process into the core proxy layer, registration layer, cluster layer, protocol layer, transport layer, etc. The responsibility boundaries between layers are clear; the core layer is defined by interfaces and does not depend on specific implementations. These interfaces are connected in series. The skeleton of Dubbo is formed; this skeleton can also be regarded as the core of Dubbo, which uses the SPI mechanism to load plug-ins (extension points) to achieve high scalability.

Vivo Internet Server Team-Wang Genfu

vivo互联网技术
3.3k 声望10.2k 粉丝