Source code analysis Dubbo service provider startup process - Part 2

Keywords: Programming Dubbo Netty network Attribute

This article continues the above process of Dubbo service provider startup. In the previous article, I combed the configuration mode based on dubbo spring file in detail. How Dubbo loads the configuration file and how dubbo:service Tag service exposes the whole process. This section focuses on the call of doLocalExport method in registryprotocol ා export. In fact, it is mainly based on the respective protocols, which are built by the service provider Set up a network server, set up listening on a specific port, and listen to the requests from the message consumer service.

RegistryProtocol#doLocalExport:

private <t> ExporterChangeableWrapper<t> doLocalExport(final Invoker<t> originInvoker) {
        String key = getCacheKey(originInvoker);
        ExporterChangeableWrapper<t> exporter = (ExporterChangeableWrapper<t>) bounds.get(key);
        if (exporter == null) {
            synchronized (bounds) {
                exporter = (ExporterChangeableWrapper<t>) bounds.get(key);
                if (exporter == null) {
                    final Invoker<!--?--> invokerDelegete = new InvokerDelegete<t>(originInvoker, getProviderUrl(originInvoker));   // @1
                    exporter = new ExporterChangeableWrapper<t>((Exporter<t>) protocol.export(invokerDelegete), originInvoker);    // @2
                    bounds.put(key, exporter);
                }
            }
        }
        return exporter;
    }

Code @ 1: if the service provider exposes the service with dubbo protocol, the URL returned by getProviderUrl(originInvoker) will be dubbo: / /. Code @ 2: according to Dubbo's built-in SPI mechanism, dubboprotocolාexport method will be called.

1. Source code analysis dubboprotocolාexport

public <t> Exporter<t> export(Invoker<t> invoker) throws RpcException {
        URL url = invoker.getUrl();     // @1
        // export service.
        String key = serviceKey(url);      // @2
        DubboExporter<t> exporter = new DubboExporter<t>(invoker, key, exporterMap);
        exporterMap.put(key, exporter);

        //export an stub service for dispatching event
        Boolean isStubSupportEvent = url.getParameter(Constants.STUB_EVENT_KEY, Constants.DEFAULT_STUB_EVENT);    //@3  start
        Boolean isCallbackservice = url.getParameter(Constants.IS_CALLBACK_SERVICE, false);                                                  
        if (isStubSupportEvent &amp;&amp; !isCallbackservice) {                                                                                                                        
            String stubServiceMethods = url.getParameter(Constants.STUB_EVENT_METHODS_KEY);
            if (stubServiceMethods == null || stubServiceMethods.length() == 0) {
                if (logger.isWarnEnabled()) {
                    logger.warn(new IllegalStateException("consumer [" + url.getParameter(Constants.INTERFACE_KEY) +
                            "], has set stubproxy support event ,but no stub methods founded."));
                }
            } else {
                stubServiceMethodsMap.put(url.getServiceKey(), stubServiceMethods);                                                                      
            }
        }   // @3 end

        openServer(url);   // @4
        optimizeSerialization(url);  // @5
        return exporter;                
    }

Code @ 1: get the service provider URL to the protocol name, here is dubbo: / /. Code @ 2: get the service name from the service provider URL, key: interface:port, for example: com.alibaba.dubbo.demo.DemoService:20880. Code @ 3: whether to export forwarding events as stub s. Code @ 4: open the service according to the url, and its implementation will be analyzed in detail below. Code @ 5: serialize according to url optimizer.

2. Source code analysis dubboprotocol ා OpenServer

private void openServer(URL url) {
        // find server.
        String key = url.getAddress();    // @1
        //client can export a service which's only for server to invoke
        boolean isServer = url.getParameter(Constants.IS_SERVER_KEY, true);
        if (isServer) {
            ExchangeServer server = serverMap.get(key);           // @2
            if (server == null) {
                serverMap.put(key, createServer(url));                    //@3
            } else {
                // server supports reset, use together with override
                server.reset(url);                                                       //@4
            }
        }
    }

Code @ 1: get the network address: ip:port according to the url, for example: 192.168.56.1:20880, service provider IP and exposed service port number. Code @ 2: get from the server cache according to the key. If it exists, execute code @ 4. If it does not exist, execute code @ 3 Code @ 3: create a server according to the URL. The Dubbo service provider server implementation class is exchange server. Code @ 4: if the server already exists, reset the server with the current URL. This is not hard to understand, because there will be multiple dubbo:service tags in a Dubbo service, which will expose the service on the same IP address and port number of the service desk provider.

2.1 source code analysis dubboprotocol ා createserver

private ExchangeServer createServer(URL url) {
        // send readonly event when server closes, it's enabled by default
        url = url.addParameterIfAbsent(Constants.CHANNEL_READONLYEVENT_SENT_KEY, Boolean.TRUE.toString());    // @1
        // enable heartbeat by default
        url = url.addParameterIfAbsent(Constants.HEARTBEAT_KEY, String.valueOf(Constants.DEFAULT_HEARTBEAT));     // @2
        String str = url.getParameter(Constants.SERVER_KEY, Constants.DEFAULT_REMOTING_SERVER);  // @3

        if (str != null &amp;&amp; str.length() &gt; 0 &amp;&amp; !ExtensionLoader.getExtensionLoader(Transporter.class).hasExtension(str))    // @4
            throw new RpcException("Unsupported server type: " + str + ", url: " + url);

        url = url.addParameter(Constants.CODEC_KEY, DubboCodec.NAME);       // @5
        ExchangeServer server;
        try {
            server = Exchangers.bind(url, requestHandler);    // @6
        } catch (RemotingException e) {
            throw new RpcException("Fail to start server(url: " + url + ") " + e.getMessage(), e);
        }
        str = url.getParameter(Constants.CLIENT_KEY);     //@7
        if (str != null &amp;&amp; str.length() &gt; 0) {
            Set<string> supportedTypes = ExtensionLoader.getExtensionLoader(Transporter.class).getSupportedExtensions();
            if (!supportedTypes.contains(str)) {
                throw new RpcException("Unsupported client type: " + str);
            }
        }
        return server;
    }

Code @ 1: add the channel.readonly.sent attribute to the url of the service provider, which is true by default. It indicates whether to wait for the bytes to be written to the socket before returning when sending the request. It is true by default. Code @ 2: add heartbeat attribute to the url of service provider, indicating heartbeat interval time. The default value is 60 * 1000, indicating 60s. Code @ 3: add the server attribute to the service provider url. The optional values are netty,mina and so on. The default value is netty. Code @ 4: judge whether the server property supports according to the SPI mechanism. Code @ 5: add codec attribute to service provider url, default value is dubbo, protocol encoding method. Code @ 6: according to the service provider URI, the service provider commands the request handler requestHandler to build the exchange server instance. The implementation of the requestHandler will be analyzed in detail later when the Dubbo service call is analyzed in detail. Code @ 7: verify that the client type is available.

2.1.1 source code analysis: exchange.bind builds the server according to the URL and exchange handler

public static ExchangeServer bind(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");
        return getExchanger(url).bind(url, handler);
    }

The above code is not hard to see. First, get the exchange instance according to the url, and then call the bind method to build the exchange server. The exchange interface is as follows

  • Exchange server bind (URL URL, exchange handler handler): called by the service provider.
  • Exchangeclient connect (URL, exchangehandler handler): service consumer call.

The implementation class provided by dubbo is HeaderExchanger, and its bind method is as follows:

HeaderExchanger#bind

public ExchangeServer bind(URL url, ExchangeHandler handler) throws RemotingException {
        return new HeaderExchangeServer(Transporters.bind(url, new DecodeHandler(new HeaderExchangeHandler(handler))));
}

As you can see here, the binding of ports is implemented by the bind method of Transporters.

2.1.2 source analysis Transporters.bind method

public static Server bind(URL url, ChannelHandler... handlers) throws RemotingException {
        if (url == null) {
            throw new IllegalArgumentException("url == null");
        }
        if (handlers == null || handlers.length == 0) {
            throw new IllegalArgumentException("handlers == null");
        }
        ChannelHandler handler;
        if (handlers.length == 1) {
            handler = handlers[0];
        } else {
            handler = new ChannelHandlerDispatcher(handlers);
        }
        return getTransporter().bind(url, handler);
    }

public static Transporter getTransporter() {
        return ExtensionLoader.getExtensionLoader(Transporter.class).getAdaptiveExtension();
}

From here, we can see that the interface of Dubbo network transmission is implemented by the Transporter interface, and its inheritance class diagram is as follows: This article takes a look at the Transporter implementation in the netty version.

The source code of NettyTransporter is as follows:

public class NettyTransporter implements Transporter {

    public static final String NAME = "netty";

    @Override
    public Server bind(URL url, ChannelHandler listener) throws RemotingException {
        return new NettyServer(url, listener);
    }

    @Override
    public Client connect(URL url, ChannelHandler listener) throws RemotingException {
        return new NettyClient(url, listener);
    }
}

The implementation method of NettyServer to establish network connection is as follows:

protected void doOpen() throws Throwable {
        NettyHelper.setNettyLoggerFactory();
        ExecutorService boss = Executors.newCachedThreadPool(new NamedThreadFactory("NettyServerBoss", true));
        ExecutorService worker = Executors.newCachedThreadPool(new NamedThreadFactory("NettyServerWorker", true));
        ChannelFactory channelFactory = new NioServerSocketChannelFactory(boss, worker, getUrl().getPositiveParameter(Constants.IO_THREADS_KEY, Constants.DEFAULT_IO_THREADS));
        bootstrap = new ServerBootstrap(channelFactory);

        final NettyHandler nettyHandler = new NettyHandler(getUrl(), this);      // @1
        channels = nettyHandler.getChannels();
        // https://issues.jboss.org/browse/NETTY-365
        // https://issues.jboss.org/browse/NETTY-379
        // final Timer timer = new HashedWheelTimer(new NamedThreadFactory("NettyIdleTimer", true));
        bootstrap.setPipelineFactory(new ChannelPipelineFactory() {
            @Override
            public ChannelPipeline getPipeline() {
                NettyCodecAdapter adapter = new NettyCodecAdapter(getCodec(), getUrl(), NettyServer.this);
                ChannelPipeline pipeline = Channels.pipeline();
                /*int idleTimeout = getIdleTimeout();
                if (idleTimeout &gt; 10000) {
                    pipeline.addLast("timer", new IdleStateHandler(timer, idleTimeout / 1000, 0, 0));
                }*/
                pipeline.addLast("decoder", adapter.getDecoder());
                pipeline.addLast("encoder", adapter.getEncoder());
                pipeline.addLast("handler", nettyHandler);     // @2
                return pipeline;
            }
        });
        // bind
        channel = bootstrap.bind(getBindAddress());
    }

To be familiar with this method, you need to have the knowledge of Netty. For the source code: Read the Netty series , we will not interpret each line of code here. For network related parameters, we will explain them in detail in the following articles. This method @ 1, @ 2 has attracted my attention. First, to create a NettyServer, you must pass in a service provider URL. However, from dubboprotocol 35; createServer, we can see that the Server is based on network socket (ip:port) cache. In a JVM application, it is inevitable There will be multiple dubbo:server tags and multiple URLs. Why can we do this here? From dubboprotocol ා createServer, it can be seen that when parsing the second dubbo:service tag, the createServer will not be called, but the Server ා reset method will be called. Is there any magic in this method? When the reset method is used, the URL can also be registered on the Server. Next, we will analyze how the NettyServer ා reset method is implemented.

2.2 source code analysis Finally, the reset method of the Server will be used, as well as the netty Server version of netty, to view the implementation principle of the reset method. NettyServer ා reset --- > parent class (AbstractServer)

AbstractServer#reset

public void reset(URL url) {
        if (url == null) {
            return;
        }
        try {                                                                                                       // @1 start
            if (url.hasParameter(Constants.ACCEPTS_KEY)) {
                int a = url.getParameter(Constants.ACCEPTS_KEY, 0);
                if (a &gt; 0) {
                    this.accepts = a;
                }
            }
        } catch (Throwable t) {
            logger.error(t.getMessage(), t);
        }
        try {
            if (url.hasParameter(Constants.IDLE_TIMEOUT_KEY)) {
                int t = url.getParameter(Constants.IDLE_TIMEOUT_KEY, 0);
                if (t &gt; 0) {
                    this.idleTimeout = t;
                }
            }
        } catch (Throwable t) {
            logger.error(t.getMessage(), t);
        }
        try {
            if (url.hasParameter(Constants.THREADS_KEY)
                    &amp;&amp; executor instanceof ThreadPoolExecutor &amp;&amp; !executor.isShutdown()) {
                ThreadPoolExecutor threadPoolExecutor = (ThreadPoolExecutor) executor;
                int threads = url.getParameter(Constants.THREADS_KEY, 0);
                int max = threadPoolExecutor.getMaximumPoolSize();
                int core = threadPoolExecutor.getCorePoolSize();
                if (threads &gt; 0 &amp;&amp; (threads != max || threads != core)) {
                    if (threads &lt; core) {
                        threadPoolExecutor.setCorePoolSize(threads);
                        if (core == max) {
                            threadPoolExecutor.setMaximumPoolSize(threads);
                        }
                    } else {
                        threadPoolExecutor.setMaximumPoolSize(threads);
                        if (core == max) {
                            threadPoolExecutor.setCorePoolSize(threads);
                        }
                    }
                }
            }
        } catch (Throwable t) {
            logger.error(t.getMessage(), t);
        }              // @1 end
        super.setUrl(getUrl().addParameters(url.getParameters()));    // @2
    }

Code @ 1: first, adjust the number of related threads in the thread pool, which is easy to understand. , Code @ 2: then set the property of calling setUrl to overwrite the private volatile URL url of the original NettyServer. Why doesn't it affect the originally registered dubbo:server? The original NettyHandler added a comment: @ Sharable, which is used to implement thread safety.

The startup process of Dubbo service provider will be analyzed here. This paper does not analyze the network details in detail, aiming to sort out the startup process. The implementation principle of Dubbo service network will be analyzed in detail in the following chapters, please look forward to.

Author introduction: Ding Wei, author of RocketMQ technology insider, community preacher of RocketMQ, public No.: Middleware interest circle At present, maintainers have successively published source code analysis Java collection, Java Concurrent contract (JUC), Netty, Mycat, Dubbo, RocketMQ, Mybatis and other source code columns. You can click on the link: Middleware knowledge planet , discuss high concurrent and distributed service architecture and exchange source code together.

</string></t></t></t></t></t></t></t></t></t></t></t></t></t></t>

Posted by Phasma Felis on Thu, 19 Dec 2019 12:21:37 -0800