Dubbo Source Learning-Service Reference (Service Startup Phase)

Keywords: Programming Dubbo Java Netty Spring

In the previous article, we looked at the process of publishing a dubbo service, and this article outlined the process of analyzing dubbo service references.

1. What should service consumers do?

  • Generate proxy objects (help us achieve communication details)
  • Set up a communication connection (netty)
  • Get the service provider address (subscription provider) from zk
  • load balancing
  • fault-tolerant
  • serialize
  • ...

2. Two Steps

The logic above can be roughly divided into two steps

  • Service startup phase

         Build communication connection, create proxy object
    
  • Remote Call Phase

         Remote invocation of service provider methods
    

3. Service Startup Phase

The Dubbo service references two times:

  • The first is to reference the service when the Spring container calls the afterPropertiesSet method of the ReferenceBean
  • The second is referenced when the service corresponding to the ReferenceBean is injected into another class

The difference between the two referencing services is that the first is hungry and the second is lazy.
By default, Dubbo uses lazy reference services.If you need to use Hungry Han style, you can configure dubbo:reference The init property of is turned on.

Typically, we use the dubbo service in our code as follows:

@Reference
private ISayHelloService iSayHelloService;

ISayHelloService.hello();

By annotating @Reference or the configuration file dubbo-consumer.xml, I'm using annotations here.

Let's debug first to see what this ISayHelloService object is.

As you can see, ISayHelloService is a proxy object and a wrapper class that contains many layers.

ReferenceAnnotationBeanPostProcessor -> ReferenceBean -> InvokerHandler -> mockClusterInvoker -> RegistryDirectory

Classes like BeanPostProcessor will execute when spring starts, so let's start with this class.

//ReferenceAnnotationBeanPostProcessor.java

private static class ReferenceBeanInvocationHandler implements InvocationHandler {

        private final ReferenceBean referenceBean;

        private Object bean;

        private ReferenceBeanInvocationHandler(ReferenceBean referenceBean) {
            this.referenceBean = referenceBean;
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            return method.invoke(bean, args);
        }
        //Initialization Method
        private void init() {
            this.bean = referenceBean.get();
        }
    }

ReferenceBean.get()

Continue tracking referenceBean.get():

//ReferenceConfig.java

//Get Service Reference
public synchronized T get() {
        if (destroyed) {
            throw new IllegalStateException("Already destroyed!");
        }
        // Detect if ref is empty, or create it by init method
        if (ref == null) {
            // The init method is primarily used to handle configurations and to call createProxy to generate proxy classes
            init();
        }
        return ref;
    }

Continue tracking init():

//ReferenceConfig.java

private void init() {
        if (initialized) {
            return;
        }
        initialized = true;
        if (interfaceName == null || interfaceName.length() == 0) {
            throw new IllegalStateException("<dubbo:reference interface=\"\" /> interface not allow null!");
        }
        // get consumer's global configuration
        checkDefault();
        //Fill ConsumerConfig
        appendProperties(this);
        //...omitted
        
        //Assembly URL
        Map<String, String> map = new HashMap<String, String>();
        Map<Object, Object> attributes = new HashMap<Object, Object>();
        map.put(Constants.SIDE_KEY, Constants.CONSUMER_SIDE);
        map.put(Constants.DUBBO_VERSION_KEY, Version.getProtocolVersion());
        map.put(Constants.TIMESTAMP_KEY, String.valueOf(System.currentTimeMillis()));
        //...omitted
        
        //Get registry host
        String hostToRegistry = ConfigUtils.getSystemProperty(Constants.DUBBO_IP_TO_REGISTRY);
        if (hostToRegistry == null || hostToRegistry.length() == 0) {
            hostToRegistry = NetUtils.getLocalHost();
        } else if (isInvalidLocalHost(hostToRegistry)) {
            throw new IllegalArgumentException("Specified invalid registry ip from property:" + Constants.DUBBO_IP_TO_REGISTRY + ", value:" + hostToRegistry);
        }
        map.put(Constants.REGISTER_IP_KEY, hostToRegistry);

        //attributes are stored by system context.
        StaticContext.getSystemContext().putAll(attributes);
        //A key!!Create Proxy Object
        ref = createProxy(map);
        ConsumerModel consumerModel = new ConsumerModel(getUniqueServiceName(), this, ref, interfaceClass.getMethods());
        ApplicationModel.initConsumerModel(getUniqueServiceName(), consumerModel);
    }

There is a lot of code here, I've omitted a few, let's focus on the general process.
The first is to populate the ConsumerConfig field with a system variable or dubbo.properties configuration file.
The various configurations are then collected and stored in a map to assemble the URL.
Finally, a proxy object is created.

createProxy()

Let's focus on how to create proxy objects:

//ReferenceConfig.java

private T createProxy(Map<String, String> map) {
        URL tmpUrl = new URL("temp", "localhost", 0, map);
        final boolean isJvmRefer;
        //...omitted
        
        //Local Reference
        if (isJvmRefer) {
           //...omitted
        } else {
            //Remote Reference-Direct Connection
            if (url != null && url.length() > 0) { 
                //...omitted
            } else { //Remote Reference-Walk Registry
            
                // Load registry 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()));
                        }
                       
                       // Add refer parameter to url and Add url to urls 
                       urls.add(u.addParameterAndEncoded(Constants.REFER_KEY, StringUtils.toQueryString(map)));
                    }
                }
                if (urls.isEmpty()) {
                    throw new IllegalStateException("No such any registry to reference " + interfaceName + " on the consumer " + NetUtils.getLocalHost() + " use dubbo version " + Version.getVersion() + ", please config <dubbo:registry address=\"...\" /> to your spring config.");
                }
            }
            // Individual registry or service provider (direct service connection, same below)
            if (urls.size() == 1) {
                //Adaptive - > wrapper (filter (RegisterProtocol). refer
                // Call RegistryProtocol's refer to build an Invoker instance
                invoker = refprotocol.refer(interfaceClass, urls.get(0));
            } else {
                //...omitted
            }
        }

        //Generate Proxy Class
        // create service proxy
        return (T) proxyFactory.getProxy(invoker);
    }

The logic here is simpler, the way service calls are processed logically, and we look directly at the most important refprotocol.refer(interfaceClass, urls.get(0)) method.

The entry urls.get(0) of this method is the url of the registry and should be parsed out like registry://registry-host/org.apache.dubbo.registry.RegistryService?refer=URL.encode("consumer://consumer-host/com.foo.FooService?version=1.0.0")

Based on the extension point adaptive mechanism, the RegistryProtocol.refer() method is invoked when the URL is identified by the registry://protocol header. Based on the conditions in the refer parameter, the provider URL is queried, such as dubbo://service-host/com.foo.FooService version=1.0.0?

Then, identified by the dubbo://protocol header of the provider URL, the DubboProtocol.refer() method is called to get the provider reference.

RegistryProtocol.refer()

So let's move on to the refer() method:

//RegistryProtocol.java

public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
        url = url.setProtocol(url.getParameter(Constants.REGISTRY_KEY, Constants.DEFAULT_REGISTRY)).removeParameter(Constants.REGISTRY_KEY);
        //Get Registry Object
        Registry registry = registryFactory.getRegistry(url);
        if (RegistryService.class.equals(type)) {
            return proxyFactory.getInvoker((T) registry, type, url);
        }
        //...omitted
        
        // Call doRefer to continue executing service reference logic
        return doRefer(cluster, registry, type, url);
    }

Continue tracking doRefer()

//RegistryProtocol.java

/**
     *
     * @param cluster
     * @param registry Registry Object
     * @param type 
     * @param url   Registry url
     * @param <T>
     * @return
     */
    private <T> Invoker<T> doRefer(Cluster cluster, Registry registry, Class<T> type, URL url) {
        //Get the RegistryDirectory object, the service directory
        //A service directory is similar to a registry, managing producers ip, port s, and so on, and is actually a collection of invoker s
        RegistryDirectory<T> directory = new RegistryDirectory<T>(type, url);
        directory.setRegistry(registry); //Set up a registry
        directory.setProtocol(protocol); //Setting up a service provider
        // Create a subscription url, that is, a consumer url
        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);
        // Register yourself with the registry (service consumer)
        //Write yourself to the/dubbo/com.foo.BarService/consumers directory in zk
        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)));
        }
        // Subscribe to node data such as providers, configurators, routers
        directory.subscribe(subscribeUrl.addParameter(Constants.CATEGORY_KEY,
                Constants.PROVIDERS_CATEGORY
                        + "," + Constants.CONFIGURATORS_CATEGORY
                        + "," + Constants.ROUTERS_CATEGORY));

        // A registry may have multiple service providers, so you need to merge multiple service providers into one
        //MockClusterWrapper(FailoverCluster)
        Invoker invoker = cluster.join(directory);
        // Register consumer with local registry
        ProviderConsumerRegTable.registerConsumer(invoker, url, subscribeUrl, directory);
        return invoker;
    }

Here are the main things to do:

  • Connect Registry
  • Register yourself with the registry (service consumer)
  • Subscribe to nodes such as registry providers
subscribe()

Let's look at the most important directory.subscribe() method, Subscribe:

//ZookeeperRegistry.java

/**
     *
     * @param url Consumer url consumer://
     * @param listener  RegistryDirectory
     */
    @Override
    protected void doSubscribe(final URL url, final NotifyListener listener) {
        try {
            // interface = *, that is, subscribe to global
            if (Constants.ANY_VALUE.equals(url.getServiceInterface())) {
                //...omitted
            } else {
                // Interface = a specific interface, which triggers a callback only if its child nodes change
                // For example: interface = com.lol.test.SayFacade
                // All URL s under the Service layer
                List<URL> urls = new ArrayList<URL>();
                //Path is the directory of producers in zk: path -> /dubbo/com.foo.BarService/providers
                for (String path : toCategoriesPath(url)) {
                    ConcurrentMap<NotifyListener, ChildListener> listeners = zkListeners.get(url);
                    if (listeners == null) {
                        zkListeners.putIfAbsent(url, new ConcurrentHashMap<NotifyListener, ChildListener>());
                        listeners = zkListeners.get(url);
                    }
                    // Get ChildListener Object
                    ChildListener zkListener = listeners.get(listener);
                    if (zkListener == null) {
                        listeners.putIfAbsent(listener, new ChildListener() {
                            @Override
                            public void childChanged(String parentPath, List<String> currentChilds) {
                                // When the service provider url changes, call the `#notify(...)'method and call back through the listener
                                ZookeeperRegistry.this.notify(url, listener, toUrlsWithEmpty(url, parentPath, currentChilds));
                            }
                        });
                        zkListener = listeners.get(listener);
                    }
                    zkClient.create(path, false);
                    //children is a child node - > service provider
                    List<String> children = zkClient.addChildListener(path, zkListener);
                    if (children != null) {
                        //urls are the true service provider urls
                        urls.addAll(toUrlsWithEmpty(url, path, children));
                    }
                }
                //First subscription, full notification, create corresponding invoker
                notify(url, listener, urls);
            }
        } 
    }

The general logic here is:

  • Get the service provider urls for the corresponding service through zk
  • These urls are then monitored through RegistryDirectory, and if there are changes, the notify() method is called
  • The notify() method is called on all urls for the first time
notify()

Next we'll focus on notify():

//AbstractRegistry.java

/**
     *
     * @param url Service consumer url consumer://
     * @param listener RegistryDirectory
     * @param urls Service provider urls
     */
    protected void notify(URL url, NotifyListener listener, List<URL> urls) {
        //...omitted
        for (Map.Entry<String, List<URL>> entry : result.entrySet()) {
            String category = entry.getKey();
            //categoryList service provider url
            List<URL> categoryList = entry.getValue();
            categoryNotified.put(category, categoryList);
            saveProperties(url);
            listener.notify(categoryList);
        }
    }

Continue tracking

//RegistryDirectory.java

/**
     * Receive service change notifications
     * @param urls Service Provider url Collection
     */
    @Override
    public synchronized void notify(List<URL> urls) {
        // Define three collections for service provider url, routing url, and configurator URL
        List<URL> invokerUrls = new ArrayList<URL>();
        List<URL> routerUrls = new ArrayList<URL>();
        List<URL> configuratorUrls = new ArrayList<URL>();
        for (URL url : urls) {
            String protocol = url.getProtocol();
            // Get the category parameter
            String category = url.getParameter(Constants.CATEGORY_KEY, Constants.DEFAULT_CATEGORY);
            // Put URLs in different lists according to the category parameter
            if (Constants.ROUTERS_CATEGORY.equals(category)
                    || Constants.ROUTE_PROTOCOL.equals(protocol)) {
                routerUrls.add(url);
            } else if (Constants.CONFIGURATORS_CATEGORY.equals(category)
                    || Constants.OVERRIDE_PROTOCOL.equals(protocol)) {
                configuratorUrls.add(url);
            } else if (Constants.PROVIDERS_CATEGORY.equals(category)) {
                // Add service provider url
                invokerUrls.add(url);
            } else {
                logger.warn("Unsupported category " + category + " in notified url: " + url + " from registry " + getUrl().getAddress() + " to consumer " + NetUtils.getLocalHost());
            }
        }
        // Convert url to Configurator
        if (configuratorUrls != null && !configuratorUrls.isEmpty()) {
            this.configurators = toConfigurators(configuratorUrls);
        }
        // Convert url to Router
        if (routerUrls != null && !routerUrls.isEmpty()) {
            List<Router> routers = toRouters(routerUrls);
            if (routers != null) { // null - do nothing
                setRouters(routers);
            }
        }
        List<Configurator> localConfigurators = this.configurators; // local reference
        // merge override parameters
        this.overrideDirectoryUrl = directoryUrl;
        if (localConfigurators != null && !localConfigurators.isEmpty()) {
            for (Configurator configurator : localConfigurators) {
                this.overrideDirectoryUrl = configurator.configure(overrideDirectoryUrl);
            }
        }
        //A key!!Refresh Invoker List
        refreshInvoker(invokerUrls);
    }

Here we just need to look at the last line of code, refreshInvoker(invokerUrls), which refreshes invokers.
This approach is key to ensuring that the collection of service providers, methodInvokerMap, in RegistryDirectory changes with the registry.

refreshInvoker()
//RegistryDirectory.java

 /**
     * 
     * @param invokerUrls Service provider url
     */
    private void refreshInvoker(List<URL> invokerUrls) {
        // If invokerUrls has only one element and the url protocol header is empty, then all services are disabled
        if (invokerUrls != null && invokerUrls.size() == 1 && invokerUrls.get(0) != null
                && Constants.EMPTY_PROTOCOL.equals(invokerUrls.get(0).getProtocol())) {
            // Set forbidden to true
            this.forbidden = true; 
            this.methodInvokerMap = null; 
            // Destroy all Invoker s
            destroyAllInvokers(); 
        } else {
            this.forbidden = false; 
            Map<String, Invoker<T>> oldUrlInvokerMap = this.urlInvokerMap; 
            if (invokerUrls.isEmpty() && this.cachedInvokerUrls != null) {
                // Add cache url to invokerUrls
                invokerUrls.addAll(this.cachedInvokerUrls);
            } else {
                this.cachedInvokerUrls = new HashSet<URL>();
                // Cache invokerUrls
                this.cachedInvokerUrls.addAll(invokerUrls);
            }
            if (invokerUrls.isEmpty()) {
                return;
            }
            // Convert url to Invoker
            Map<String, Invoker<T>> newUrlInvokerMap = toInvokers(invokerUrls);// Translate url list to Invoker map
            // Mapping newUrlInvokerMap to method name to Invoker list
            Map<String, List<Invoker<T>>> newMethodInvokerMap = toMethodInvokers(newUrlInvokerMap); 
            // state change
            if (newUrlInvokerMap == null || newUrlInvokerMap.size() == 0) {
                logger.error(new IllegalStateException("urls to invokers error .invokerUrls.size :" + invokerUrls.size() + ", invoker.size :0. urls :" + invokerUrls.toString()));
                return;
            }
            // Invoker merging multiple groups
            this.methodInvokerMap = multiGroup ? toMergeMethodInvokerMap(newMethodInvokerMap) : newMethodInvokerMap;
            this.urlInvokerMap = newUrlInvokerMap;
            try {
                // Destroy unused Invoker
                destroyUnusedInvokers(oldUrlInvokerMap, newUrlInvokerMap); // Close the unused Invoker
            } catch (Exception e) {
                logger.warn("destroyUnusedInvokers error. ", e);
            }
        }
    }

The general logic here is:

  • Determine whether services are disabled based on the protocol header
  • Convert url to invoker
  • Destroy useless Invoker

First, when the url protocol header is empty://, this means that all services are disabled and all Invoker s are destroyed.
Then convert URL to invoker to get the mapping relationship of <url, Invoker>.Further conversion is performed to get the <methodName, Invoker List>mapping relationship.
Group Invoker merge operations are then performed and the merge results are assigned to the methodInvokerMap.
Finally, destroy the useless Invoker to prevent service consumers from invoking services that are offline

Invoker is the core model of Dubbo and represents an executable entity.At the service provider, Invoker is used to invoke the service provider class.On the service consumer side, Invoker is used to perform remote calls.

At this point, the focus is on the process of url to invoker, because dubbo remote calls are made through invoker, there must be a lot of important content in the process of transformation.

toInvokers()

Let's go on to toInvokers:

//RegistryDirectory.java

private Map<String, Invoker<T>> toInvokers(List<URL> urls) {
    Map<String, Invoker<T>> newUrlInvokerMap = new HashMap<String, Invoker<T>>();
    if (urls == null || urls.isEmpty()) {
        return newUrlInvokerMap;
    }
    Set<String> keys = new HashSet<String>();
    // Protocol for getting service consumer configuration
    String queryProtocols = this.queryMap.get(Constants.PROTOCOL_KEY);
    for (URL providerUrl : urls) {
        if (queryProtocols != null && queryProtocols.length() > 0) {
            boolean accept = false;
            String[] acceptProtocols = queryProtocols.split(",");
            // Detect whether service provider agreements are supported by service consumers
            for (String acceptProtocol : acceptProtocols) {
                if (providerUrl.getProtocol().equals(acceptProtocol)) {
                    accept = true;
                    break;
                }
            }
            if (!accept) {
                // Ignore the current providerUrl if the service consumer agreement header is not supported by the consumer
                continue;
            }
        }
        // Ignore empty protocol
        if (Constants.EMPTY_PROTOCOL.equals(providerUrl.getProtocol())) {
            continue;
        }
        // SPI detects whether the server-side protocol is supported by the consumer and throws an exception if it is not supported
        if (!ExtensionLoader.getExtensionLoader(Protocol.class).hasExtension(providerUrl.getProtocol())) {
            logger.error(new IllegalStateException("Unsupported protocol..."));
            continue;
        }
        
        // Merge URLs
        URL url = mergeUrl(providerUrl);

        String key = url.toFullString();
        if (keys.contains(key)) {
            // Ignore duplicate URLs
            continue;
        }
        keys.add(key);
        // Assign local Invoker cache to localUrlInvokerMap
        Map<String, Invoker<T>> localUrlInvokerMap = this.urlInvokerMap;
        // Get Invoker corresponding to url
        Invoker<T> invoker = localUrlInvokerMap == null ? null : localUrlInvokerMap.get(key);
        // Cache Miss
        if (invoker == null) {
            try {
                boolean enabled = true;
                if (url.hasParameter(Constants.DISABLED_KEY)) {
                    // Get the disable configuration, reverse it, and assign it to the enable variable
                    enabled = !url.getParameter(Constants.DISABLED_KEY, false);
                } else {
                    // Gets the enable configuration and assigns it to the enable variable
                    enabled = url.getParameter(Constants.ENABLED_KEY, true);
                }
                if (enabled) {
                    // Call refer to get Invoker
                    invoker = new InvokerDelegate<T>(protocol.refer(serviceType, url), url, providerUrl);
                }
            } catch (Throwable t) {
                logger.error("Failed to refer invoker for interface...");
            }
            if (invoker != null) {
                // Cache Invoker Instances
                newUrlInvokerMap.put(key, invoker);
            }
            
        // Cache Hit
        } else {
            // Store invoker in newUrlInvokerMap
            newUrlInvokerMap.put(key, invoker);
        }
    }
    keys.clear();
    return newUrlInvokerMap;
}

The logic here is simple:

  • The service provider url is first detected, and toInvokers ignore the service provider url if either the service consumer's configuration does not support the service-side protocol or the service-side url protocol header is empty.

  • Merge the urls, then access the cache and try to get the invoker corresponding to the url.

  • If the cache hits, store the Invoker directly in the newUrlInvokerMap.

  • If you missed, you need to create a new Invoker.

DubboProtocol.refer()

invoker = new InvokerDelegate<T>(protocol.refer(serviceType, url), url, providerUrl);
This is the code for creating invoker, where you get the DubboProtocol adaptively. So let's take a closer look at DubboProtocol.refer():

//DubboProtocol.java

    //Client Instance
    private final ExchangeClient[] clients;

    private final AtomicPositiveInteger index = new AtomicPositiveInteger();

    private final String version;

    private final ReentrantLock destroyLock = new ReentrantLock();

    private final Set<Invoker<?>> invokers;

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

The code here is simple, just new an Invoker.The focus is on populating the properties of DubboInvoker.
Let's first look at the ExchangeClient[] clients property, which is a collection of client instances obtained by the getClients(url) method.

ExchangeClient does not actually have communication capabilities; it needs to communicate based on lower-level client instances.For example, NettyClient, MinaClient, and so on, by default, Dubbo uses NettyClient to communicate.Next, let's take a brief look at the logic of the getClients method.

getClients()
//DubboProtocol.java

private ExchangeClient[] getClients(URL url) {
    // Whether to share the connection
    boolean service_share_connect = false;
  	// Gets the number of connections, defaulting to 0, indicating no configuration
    int connections = url.getParameter(Constants.CONNECTIONS_KEY, 0);
    // Shared connection if connections are not configured
    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) {
            // Get Shared Clients
            clients[i] = getSharedClient(url);
        } else {
            // Initialize a new client
            clients[i] = initClient(url);
        }
    }
    return clients;
}

This determines whether to get a shared client or create a new client instance based on the number of connections, which is used by default.The initClient method is also called in the getSharedClient method, so let's look at these two methods together.

//DubboProtocol.java

private ExchangeClient getSharedClient(URL url) {
    String key = url.getAddress();
    // Get ExchangeClient with Reference Counting
    ReferenceCountExchangeClient client = referenceClientMap.get(key);
    if (client != null) {
        if (!client.isClosed()) {
            // Increase Reference Count
            client.incrementAndGetCount();
            return client;
        } else {
            referenceClientMap.remove(key);
        }
    }

    locks.putIfAbsent(key, new Object());
    synchronized (locks.get(key)) {
        if (referenceClientMap.containsKey(key)) {
            return referenceClientMap.get(key);
        }

        // Create ExchangeClient Client Client
        ExchangeClient exchangeClient = initClient(url);
        // Pass the ExchangeClient instance to the ReferenceCountExchangeClient, using decoration mode
        client = new ReferenceCountExchangeClient(exchangeClient, ghostClientMap);
        referenceClientMap.put(key, client);
        ghostClientMap.remove(key);
        locks.remove(key);
        return client;
    }
}

First try to get a shared instance with reference counting, and if not, create a new one with initClient(url).

initClient()
//DubboProtocol.java

private ExchangeClient initClient(URL url) {

    // Gets the client type, defaulting to netty4
    String str = url.getParameter(Constants.CLIENT_KEY, url.getParameter(Constants.SERVER_KEY, Constants.DEFAULT_REMOTING_CLIENT));

    // Add codec and heartbeat package parameters to url
    url = url.addParameter(Constants.CODEC_KEY, DubboCodec.NAME);
    url = url.addParameterIfAbsent(Constants.HEARTBEAT_KEY, String.valueOf(Constants.DEFAULT_HEARTBEAT));

    // Detect if client type exists, throw exception if none exists
    if (str != null && str.length() > 0 && !ExtensionLoader.getExtensionLoader(Transporter.class).hasExtension(str)) {
        throw new RpcException("Unsupported client type: ...");
    }

    ExchangeClient client;
    try {
        // Get the lazy configuration and determine the type of client to create based on the configuration value
        if (url.getParameter(Constants.LAZY_CONNECT_KEY, false)) {
            // Create lazy-loading ExchangeClient instance
            client = new LazyConnectExchangeClient(url, requestHandler);
        } else {
            // Create a normal ExchangeClient instance
            client = Exchangers.connect(url, requestHandler);
        }
    } catch (RemotingException e) {
        throw new RpcException("Fail to create remoting client for service...");
    }
    return client;
}

The initClient method first gets the client type configured by the user, defaulting to netty4.It then detects if the client type configured by the user exists and throws an exception if it does not exist.Finally, depending on the lazy configuration, you decide what type of client to create.

Next, look at the Exchangers.connect(url, requestHandler) method for creating the client

//Exchangers.java

public static ExchangeClient connect(URL url, ExchangeHandler handler) throws RemotingException {
    if (url == null) {
        throw new IllegalArgumentException("url == null");
    }
    if (handler == null) {
        throw new IllegalArgumentException("handler == null");
    }
    url = url.addParameterIfAbsent(Constants.CODEC_KEY, "exchange");
    // Get an Exchanger instance, defaulting to HeaderExchangeClient
    return getExchanger(url).connect(url, handler);
}

Here, by adapting, HeaderExchanger.connect() is called

//HeaderExchanger.java

public ExchangeClient connect(URL url, ExchangeHandler handler) throws RemotingException {
    // There are several calls, as follows:
    // 1. Create the HeaderExchangeHandler object
    // 2. Create DecodeHandler object
    // 3. Building Client Instances with Transporters
    // 4. Create the HeaderExchangeClient object
    return new HeaderExchangeClient(Transporters.connect(url, new DecodeHandler(new HeaderExchangeHandler(handler))), true);
}

There are many calls here, so let's focus on Transporters'connect method.The following:

//Transporters.java

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 {
        // Create a ChannelHandler Distributor if the number of handler s is greater than 1
        handler = new ChannelHandlerDispatcher(handlers);
    }
    
    // Get the Transporter adaptive extension class and call the connect method to generate a Client instance
    return getTransporter().connect(url, handler);
}

The getTransporter() method returns an adaptive extension class that loads the specified Transporter implementation class at run time based on the client type.If the user does not configure the client type, NettyTransporter is loaded by default and the connect method of that class is called.The following:

connect()
//NettyTransporter.java

public Client connect(URL url, ChannelHandler listener) throws RemotingException {
    // Create NettyClient object
    return new NettyClient(url, listener);
}

You won't be able to keep up with this anymore. The next step is to build Netty clients through the API s provided by Netty. You are interested to see them for yourself.

createProxy()

At this point, the service provider's Invoker is created, and the next step is to create the proxy object.
This is the ReferenceConfig.createProxy() method, which is not covered here.

summary

After the proxy object has been created, the service startup phase referenced by the dubbo service has been completed. To review what we have done:

  • Connect Registry
  • Register yourself with the registry (service consumer)
  • Subscribe to the registry service provider and obtain the service provider url
  • Create Invoker
  • Establish communication to connect netty
  • Create Proxy Object

After the service starts, the next step is the remote invocation phase, which we will analyze in more detail in the next article.

Finally, draw a simple flowchart for easy understanding.

Reference resources:
Dubbo website

Posted by gooman on Fri, 20 Mar 2020 19:56:52 -0700