This debug code uses the Dubbo – Demo of the Dubbo project on Githubdubbo-demo-xmlThe code below. The default Dubbo communication protocol is used as the debug code here. Since this article is the internal implementation of dubbo framework, it is recommended to read a talk on the official website of Dubbo firstDubbo’s design principlesTo help you understand the code, post here firstDubbo websiteThen when we look at the code, compare the architecture diagram with the analysis:

The following is a description of each layer in the architecture diagram:

  • Config configuration layer: external configuration interface, centering on ServiceConfig and ReferenceConfig, can directly initialize configuration classes, or generate configuration classes through Spring configuration parsing
  • Proxy ServiceProxy layer: transparent proxy of service interfaces. The client Stub and server Skeleton of the service are generated. The extension interface is ProxyFactory
  • Registry layer: encapsulates the registration and discovery of service addresses, centering on service URLS and extending interfaces as RegistryFactory, Registry, and RegistryService
  • Cluster routing layer: encapsulates routing and load balancing of multiple providers, Bridges registries, centers on Invoker, and extends interfaces to Cluster, Directory, Router, and LoadBalance
  • Monitor monitoring layer: Monitors the number and time of RPC calls. It centers on Statistics and extends interfaces to MonitorFactory, Monitor, and MonitorService
  • Protocol Remote Invocation layer: Encapsulates RPC Invocation with Protocol, Invoker, and half interface, based on Invocation and Result
  • Exchange information exchange layer: It encapsulates the Request and Response mode, turns synchronous to asynchronous, uses Request and Response as the center, and uses exchange channel, ExchangeClient and ExchangeServer as the expansion interface
  • Transport Network transport layer: Abstract MINA and Netty as the unified interface, Message as the center, extended interfaces are Channel, Transporter, Client, Server, Codec
  • Serialize data Serialization layer: reusable tools with Serialization, ObjectInput, ObjectOutput, and ThreadPool extensions

XML configuration for consumer in demo:

<beans 
       // XMLNS :xsi is the xSI tag namespace
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       // XMLNS :dubbo is the namespace of the dubbo tag
       xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
       // The default namespace for the current XML file
       xmlns="http://www.springframework.org/schema/beans"
       // xsi:schemaLocation configures the configuration specification corresponding to each namespace and is used for format verification
       xsi:schemaLocation="Http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd">

    <dubbo:application name="demo-consumer"/>

    <dubbo:registry address=Zookeeper: / / "127.0.0.1:2181" timeout="6000"/ > <! <dubbo:protocol name="dubbo"/ > <! <dubbo: Reference id="demoService" check="false" interface="org.apache.dubbo.demo.DemoService" timeout="6000" />

</beans>
Copy the code

1, will bedubbo:referenceThe configuration resolves toBeanDefinition

Dubbo’s consumer configuration is<dubbo:reference>In theSpring dubbo tag Configuration processYou can see that the configuration is resolved toBeanDefinitionClass, the sequence diagram is as follows:

2. Instantiate by BeanDefinitionReferenceConfigobject

BeanDefinitionThe BeanClass attribute oforg.apache.dubbo.config.spring.ReferenceConfig.classClass, which implementsFactoryBeanInterface (FactoryBeanThe interface is designed for this scenario where beans are generated using dynamic proxy techniques, and Spring will inject this object when it is injectedgetObject()Method to return a real instance.getBean()For detailed logic, seehereThe debug screenshot is as follows:

3. When dependency injection is triggeredReferenceConfigthisFactoryBeanthegetObject()Call, returns the proxy class

Spring also calls it during dependency injectiongetBean(String beanName)Method to get the bean that has already been instantiated. BeanName gets the bean instantiated in step 2ReferenceConfigObject, which isBeanFactory, so it will eventually callgetObject()Method, the debug screenshot is as follows:

getObject()Screenshot below:

The code for getObject() looks like this, and I’ve omitted some branch code here:

public Object getObject(a) {
        return this.get();
}

public synchronized T get(a) {...this.init(); .return this.ref;
}

public synchronized void init(a) {
    // Initializes the Dubbo startup class
    if (this.bootstrap == null) {
        this.bootstrap = DubboBootstrap.getInstance();
        this.bootstrap.init(); }...// Map holds all the configuration information for creating the Consumer proxy class
    Map<String, String> map = new HashMap();
    map.put("side"."consumer");
    // Insert dubbo version number, timestamp, process PID runtime information into mapReferenceConfigBase.appendRuntimeParameters(map); .// Pass in the map to create the proxy class
    this.ref = this.createProxy(map); . }private T createProxy(Map<String, String> map) {...// Check the registry configuration and generate the registry URL based on the registry configuration
    this.checkRegistry();
    List<URL> us = ConfigValidationUtils.loadRegistries(this.false);
    if (CollectionUtils.isNotEmpty(us)) {
        for(Iterator var3 = us.iterator(); var3.hasNext(); this.urls.add(u.addParameterAndEncoded("refer", StringUtils.toQueryString(map)))) {
            u = (URL)var3.next();
            monitorUrl = ConfigValidationUtils.loadMonitor(this, u);
            if(monitorUrl ! =null) {
                map.put("monitor", URL.encode(monitorUrl.toFullString())); }}}...// Build the invoker chain
    this.invoker = REF_PROTOCOL.refer(this.interfaceClass, (URL)this.urls.get(0)); .// Create an agent using ProxyFactory
    return PROXY_FACTORY.getProxy(this.invoker);
}
Copy the code

Below is a sequence diagram combining the main steps of the Dubbo architecture drawing:

3.1 Initializing Dubbo Start the DubboBootstrap class

DubboBootstrap is a dubbo startup class, similar to springBoot’s SpringApplication class, which stores various configuration information for the entire Dubbo process. Responsible for the provider’s service exposure and the consumer’s service subscription, maintaining the dubbo container lifecycle, we will skip here and continue to explore our consumer instantiation process

3.2 Building a registry object

CreateProxy (Map

Map
,>

this.checkRegistry();
List<URL> us = ConfigValidationUtils.loadRegistries(this.false);
if (CollectionUtils.isNotEmpty(us)) {
// Get the URL of the registry and put the metadata information of the current consumer configuration into the Parameter property of the URL
    for(Iterator var3 = us.iterator(); var3.hasNext(); this.urls.add(u.addParameterAndEncoded("refer", StringUtils.toQueryString(map)))) { ... }}Copy the code

<dubbo:registry>The bean corresponding to the configuration center isRegistryConfig.class, parse the process and<dubbo:reference>Again, parsed configurations are placed in a global fileConfigManagerPhi, this is going to go from thisConfigManagerRead the registry configuration in. Common configurations in Dubbo are cached inDubbo Tag Parsing in Spring XML FilesParses dubbo custom tagsDubboNamespaceHandler, the entity classes corresponding to each configuration are as follows:These classes are inheritedAbstractConfigClass, this abstract class has a quilt@PostConstructannotatedaddIntoConfigManager()Method, which tells the Spring container to execute the annotated method after instantiating the object, caches the current object into a globalConfigManagerObject:

    @PostConstruct
    public void addIntoConfigManager(a) {
        ApplicationModel.getConfigManager().addConfig(this);
    }
Copy the code

UrlIt is a very heavy parameter encapsulation class in the main link of Dubbo. In the main link, the encapsulation parameter is passed all the way. Here, it means that the metadata of the registry and the metadata of the consumer are encapsulated in a URL, which is passed to the downstream method. The address of the registry, the interface of the consumer, and the method of the consumer are encapsulated in the following debug screenshots:

private static final Protocol REF_PROTOCOL = (Protocol)ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();

this.invoker = REF_PROTOCOL.refer(this.interfaceClass, (URL)this.urls.get(0));
Copy the code

Here is the SPI extension technology:

Dubbo extends related components through SPI technology, as shown in the code aboveREF_PROTOCOLObjects are extended through SPI, and the principle is to findMETA-INF/dubbo/internalThe extension class configured below, as shown in my screenshot below:

As you can see from this, there are many extensions to the protocol. class Protocol. Which implementation class is ultimately called to execute the refer method? The key is in getAdaptiveExtension(), which returns an adapter class whose refer method calls getExtension(extName) based on the protol protocol in the URL, For example, a URL starting with Registry :// needs to be executed using the registry’s protocol implementation class. I post the code for this class:

public org.apache.dubbo.rpc.Invoker refer(java.lang.Class arg0, org.apache.dubbo.common.URL arg1) throws org.apache.dubbo.rpc.RpcException {...// Get the protocol in the URL, default dubbo
        org.apache.dubbo.common.URL url = arg1;
        String extName = (url.getProtocol() == null ? "dubbo": url.getProtocol()); .// Get the corresponding plug-in according to the protocol
        org.apache.dubbo.rpc.Protocol extension = (org.apache.dubbo.rpc.Protocol) ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.Protocol.class).getExtension(extName);
        return extension.refer(arg0, arg1);
    }
Copy the code

getExtension(extName)Method returns a wrapper class, as shown in the screenshot below:

You can see that when Dubbo extends through SPI, there are three classes in the configuration file:

  • Adapter class: mainly through the URL information to match the appropriate wrapper class
  • Wrapper class: This wrapper class can be interpreted as a proxy class with filtering and listening logic, such as the ProtocolListenerWrapper and ProtocolFilterWrapper classes shown in the screenshot
  • A real implementation class

The call chain finds the corresponding wrapper class through the adapter class, then forms a call chain through the wrapper class, and finally calls the real implementation class, as shown belowloadClassMethod is responsible for loadingMETA-INF/dubbo/internalYou can see the logic that distinguishes the three types of extension classes. The screenshot is as follows:

Ref_protocol. refer(this.interfaceclass, (URL)this.urls.get(0)), which calls the refer method of the outermost wrapper class ProtocolListenerWrapper:

// ProtocolListenerWrapper class refer method
public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
        return (Invoker)(UrlUtils.isRegistry(url) ? this.protocol.refer(type, url) : new ListenerInvokerWrapper(this.protocol.refer(type, url), Collections.unmodifiableList(ExtensionLoader.getExtensionLoader(InvokerListener.class).getActivateExtension(url, "invoker.listener"))));
    }
    
    // ProtocolFilterWrapper class refer method
    public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
        return UrlUtils.isRegistry(url) ? this.protocol.refer(type, url) : buildInvokerChain(this.protocol.refer(type, url), "reference.filter"."consumer");
    }
Copy the code

Here, as it is the Registry protocol, the refer method of the RegistryProtocol class is directly called. The main code is as follows:

public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
        url = this.getRegistryUrl(url);
        // Get the registry object
        Registry registry = this.registryFactory.getRegistry(url); .return this.doRefer(this.cluster, registry, type, url);
    }
Copy the code

The registryFactory here is also extended via SPI, since the registry uses ZooKeeper, so the registryFactory hereregistryThe object isZookeeperRegistry, the stack screenshot is as follows:

3.3 Building a Service Discovery Object

Each configured Consumer corresponds to a service discovery object. The main function of service discovery is to monitor the node changes under the interface of the registry, including the service provider and route configuration. When the node changes, the registry is notified to update the local configuration information. The trunk code is as follows:

private <T> Invoker<T> doRefer(Cluster cluster, Registry registry, Class<T> type, URL url) {
        // Build a service discovery object
        RegistryDirectory<T> directory = new RegistryDirectory(type, url);
        directory.setRegistry(registry);
        directory.setProtocol(this.protocol); .// Set the default configured routing chain for service discovery through the SPI extension mechanismdirectory.buildRouterChain(subscribeUrl); . }Copy the code

Build the routing chain with the SPI extension configuration and save it to the service discovery object:

public void buildRouterChain(URL url) {
        this.setRouterChain(RouterChain.buildChain(url));
    }
private RouterChain(URL url) {
        List<RouterFactory> extensionFactories = ExtensionLoader.getExtensionLoader(RouterFactory.class).getActivateExtension(url, "router");
        List<Router> routers = (List)extensionFactories.stream().map((factory) -> {
            return factory.getRouter(url);
        }).collect(Collectors.toList());
        this.initWithRouters(routers);
    }
Copy the code

3.4 Publish Consumer information to the registry

Again in the doRefer method, delete the secondary code as follows:

private <T> Invoker<T> doRefer(Cluster cluster, Registry registry, Class<T> type, URL url) {...if (!"*".equals(url.getServiceInterface()) && url.getParameter("register".true)) {
            directory.setRegisteredConsumerUrl(this.getRegisteredConsumerUrl(subscribeUrl, url));
            // Publish Consumer information to the registryregistry.register(directory.getRegisteredConsumerUrl()); }... }Copy the code

registry.register(directory.getRegisteredConsumerUrl());The ZooKeeper client framework is calledCuratorPut consumser information to the zookeeper, in zookeeper/dubbo/org. Apache. Dubbo. Demo. DemoService/write information consumers directory:

3.5 Subscribe to and obtain the service provider, configuration, and routing information in the registry through service discovery objects, update the local configuration of service discovery objects, and build the Invoker chain

Again in the doRefer method, delete the secondary code as follows:


private <T> Invoker<T> doRefer(Cluster cluster, Registry registry, Class<T> type, URL url) {
        RegistryDirectory<T> directory = new RegistryDirectory(type, url);
        directory.setRegistry(registry);
        directory.setProtocol(this.protocol); .// Subscribe to the node information and update the configuration of the service discovery directory object based on the configuration information under the node in the registry
        directory.subscribe(subscribeUrl.addParameter("category"."providers,configurators,routers")); . }public void subscribe(URL url) {
        this.setConsumerUrl(url);
        CONSUMER_CONFIGURATION_LISTENER.addNotifyListener(this);
        this.serviceConfigurationListener = new RegistryDirectory.ReferenceConfigurationListener(this, url);
        // Use the current registryDirectory object as a Listener to listen for node changes
        this.registry.subscribe(url, this);
}
Copy the code

3.5.1 Subscribe to the registryproviders,configurators,routersnode

This is the core of the service discovery process by zookeeper client framework of Curator to watch on the zookeeper will, configurators, routers nodes information, and to the zookeeper node information changes, will inform to the listener, RegistryDirectory class implements the NotifyListener class, you will be as listeners to monitor the change of the service provider node, when the registry will, configurators, these three node routers changes, The notify(List urls) method of the NotifyListener class calls back to this listener.

3.5.2 Obtaining node configuration, updating the locally cached Provider list, and establishing a long-term connection with the provider

When the service provider changes, the content of zooKeeper’s providers node changes. The notify(List urls) method of ZookeeperRegistry is called to update the privider List. The notify(List) method is automatically triggered, and the main code is as follows:

public synchronized void notify(List<URL> urls) {...// Get service provider information from the urls in the callback
        List<URL> providerURLs = (List)urls.getOrDefault("providers", Collections.emptyList());
        // Update the list of local service providers
        this.refreshOverrideAndInvoker(providerURLs);
    }

Copy the code

The URL information contains the metadata information of the service provider. The debug screenshot is as follows: Outside the chain picture archiving failure, the source station might be hotlinking prevention mechanism, proposed to directly upload picture preserved (img – 8 gsawvyr – 1617207225262) (quiver – image – url / 763 a6034b1f510d4bfc30456997d0c74. JPG =1862×631)] there are two main steps:

  1. The URL information is parsed into a chain of invokers wrapped in layers, the innermost being DubboInvoker.

Each layer of wrapping is designed to add new features, the most important of which is AsyncToSyncInvoker with asynchronous support. The consumer blocks the Invoke method of AsyncToSyncInvoker and waits for the provider to return the result. The Invoker packaging chain formed in this step is as follows: Outside the chain picture archiving failure, the source station might be hotlinking prevention mechanism, proposed to directly upload picture preserved (img – AkF52hWn – 1617207225279) (quiver – image – url / 261433 a85d0d9ca4d4b9408679a5ac82. JPG =1000×262)]

  1. Create NettyClient, establish a long connection with the provider, and passDubboInvokerThe constructor shows the need to encapsulate oneExchangeClient, this object is responsible for network communication and uses the Netty framework by default.

The constructor code is as follows:

DubboInvoker<T> invoker = new DubboInvoker(serviceType, url, this.getClients(url), this.invokers);
Copy the code

The sequence diagram is as follows:

The complete sequence diagram for the registry callback is as follows:

3.6. Re-wrap the Invoker with the Cluster object to add Cluster features, including troubleshooting, load balancing, and routing logic

Invoker invoker = cluster.join(directory);
Copy the code

Clusters are also extended by SPI, and we can see what this abstraction means by looking at dubbo’s classification of clusters:The name indicates failover and failfastClusterThe idea is to abstract the different mechanisms for dealing with failure. The default is failover, and the final call isFailoverClusterThe debug screenshot is as follows:

Based on the Invoker chain in 3.6, after this layer of packaging, the Picture of the Invoker chain is as follows:

3.7 Wrap the Invoker chain as a proxy class and return it togetObject()methods

private static final ProxyFactory PROXY_FACTORY = (ProxyFactory)ExtensionLoader.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension();

PROXY_FACTORY.getProxy(this.invoker);
Copy the code

As can be seen from the code ProxyFactory is extended through SPI, by default, use JavassistProxyFactory JavassistProxyFactory. GetProxy code is as follows:

public <T> T getProxy(Invoker
       
         invoker, Class
        [] interfaces)
        {
        return Proxy.getProxy(interfaces).newInstance(new InvokerInvocationHandler(invoker));
    }
Copy the code

Here will be formed on the packaged into InvokerInvocationHandler invoker chain. The class, let’s look at the generated proxy class by the compiled source code:

public class proxy0
implements ClassGenerator.DC.Destroyable.EchoService.DemoService {
    public static Method[] methods;
    private InvocationHandler handler;

    @Override
    public Object $echo(Object object) {
        Object[] objectArray = new Object[]{object};
        Object object2 = this.handler.invoke(this, methods[0], objectArray);
        return object2;
    }

    public CompletableFuture sayHelloAsync(String string) {
        Object[] objectArray = new Object[]{string};
        Object object = this.handler.invoke(this, methods[1], objectArray);
        return (CompletableFuture)object;
    }

    public String sayHello(String string) {
        Object[] objectArray = new Object[]{string};
        Object object = this.handler.invoke(this, methods[2], objectArray);
        return (String)object;
    }

    @Override
    public void $destroy() {
        Object[] objectArray = new Object[]{};
        Object object = this.handler.invoke(this, methods[3], objectArray);
    }

    public proxy0(a) {}public proxy0(InvocationHandler invocationHandler) {
        this.handler = invocationHandler; }}Copy the code

The invoke method of InvokerInvocationHandler is invoked when demoservice.sayHello (String String) is called.

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (method.getDeclaringClass() == Object.class) {
            return method.invoke(this.invoker, args);
        } else{ String methodName = method.getName(); Class<? >[] parameterTypes = method.getParameterTypes();if (parameterTypes.length == 0) {
                if ("toString".equals(methodName)) {
                    return this.invoker.toString();
                }

                if ("$destroy".equals(methodName)) {
                    this.invoker.destroy();
                    return null;
                }

                if ("hashCode".equals(methodName)) {
                    return this.invoker.hashCode(); }}else if (parameterTypes.length == 1 && "equals".equals(methodName)) {
                return this.invoker.equals(args[0]);
            }
            // struct the RpcInvocation message, including the interface, method, parameters, and other metadata
            RpcInvocation rpcInvocation = new RpcInvocation(method, this.invoker.getInterface().getName(), args);
            rpcInvocation.setTargetServiceUniqueName(this.invoker.getUrl().getServiceKey());
            // Invoke the invoker chain
            return this.invoker.invoke(rpcInvocation).recreate(); }}Copy the code

The main logic is to encapsulate the metadata of the invocation class, method, and parameter into the RpcInvocation object and invoke the invocation method from the invocation chain generated earlier. At this point, the proxy class has been created, and what is returned to the getObject() method is a proxy class.