Transport layer for Dubbo in-depth analysis

Keywords: Programming Dubbo Netty xml codec

Transporter class analysis

dubbo provides a unified bind and connet interface for communication framework, which is easy to manage and extend. It is encapsulated in the interface class: Transporter:

 @SPI("netty")
public interface Transporter {

    @Adaptive({Constants.SERVER_KEY, Constants.TRANSPORTER_KEY})
    Server bind(URL url, ChannelHandler handler) throws RemotingException;

    @Adaptive({Constants.CLIENT_KEY, Constants.TRANSPORTER_KEY})
    Client connect(URL url, ChannelHandler handler) throws RemotingException;
}
The bind and connect interfaces are provided, which correspond to the server side and the client side respectively. What implementation classes are there, as shown in the following figure:

Take the default netty framework as an example, the code 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 specific server side is encapsulated in NettyServer The client is encapsulated in NettyClient;url The parameters are included xml Configuration information(Including: external interface, protocol used, serialization method used, communication framework used, etc.),listener It's one Handler,After decoding, the data is handed over to it for subsequent business processing; corresponding to the above several open source communication frameworks are provided respectively. Transporter Include: NettyTransporter,NettyTransporter(netty4),MinaTransporter as well as GrizzlyTransporter,Which type is used specifically? Transporter,stay Transporters Provided in the class getTransporter Method:

 public static Transporter getTransporter() {
        return ExtensionLoader.getExtensionLoader(Transporter.class).getAdaptiveExtension();
    }
Instead of specifying the transporter parameter in the url and loading the specific transporter class, as in acquiring the specific serialization class, a dynamic transporter is generated, whereby the dynamic transporter loads the specific class.

Because Server and Client can be set up into different communication frameworks respectively, obtaining only one Transporter at a time can not meet this requirement; the specific method of generating dynamic code in Extension Loader's createAdaptive Extension ClassCode method is not listed here. Here we show the default generated dynamic code extension classes:

 package com.alibaba.dubbo.remoting;

import com.alibaba.dubbo.common.extension.ExtensionLoader;

public class Transporter$Adaptive implements com.alibaba.dubbo.remoting.Transporter {
    public com.alibaba.dubbo.remoting.Server bind(
        com.alibaba.dubbo.common.URL arg0,
        com.alibaba.dubbo.remoting.ChannelHandler arg1)
        throws com.alibaba.dubbo.remoting.RemotingException {
        if (arg0 == null) {
            throw new IllegalArgumentException("url == null");
        }

        com.alibaba.dubbo.common.URL url = arg0;
        String extName = url.getParameter("server",
                url.getParameter("transporter", "netty"));

        if (extName == null) {
            throw new IllegalStateException(
                "Fail to get extension(com.alibaba.dubbo.remoting.Transporter) name from url(" +
                url.toString() + ") use keys([server, transporter])");
        }

        com.alibaba.dubbo.remoting.Transporter extension = (com.alibaba.dubbo.remoting.Transporter) ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.remoting.Transporter.class)
                                                                                                                   .getExtension(extName);

        return extension.bind(arg0, arg1);
    }

    public com.alibaba.dubbo.remoting.Client connect(
        com.alibaba.dubbo.common.URL arg0,
        com.alibaba.dubbo.remoting.ChannelHandler arg1)
        throws com.alibaba.dubbo.remoting.RemotingException {
        if (arg0 == null) {
            throw new IllegalArgumentException("url == null");
        }

        com.alibaba.dubbo.common.URL url = arg0;
        String extName = url.getParameter("client",
                url.getParameter("transporter", "netty"));

        if (extName == null) {
            throw new IllegalStateException(
                "Fail to get extension(com.alibaba.dubbo.remoting.Transporter) name from url(" +
                url.toString() + ") use keys([client, transporter])");
        }

        com.alibaba.dubbo.remoting.Transporter extension = (com.alibaba.dubbo.remoting.Transporter) ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.remoting.Transporter.class)
                                                                                                                   .getExtension(extName);

        return extension.connect(arg0, arg1);
    }
}
It can be found that Server The end can pass through transporter and server Two parameters to set the extension class, and server The values of parameter settings can be overridden transporter The values of parameters are the same. Client Similarly; in the end, no matter what. bind()still connet()All through ExtensionLoader Of getExtension Methods to obtain specific transporter Category; Same serialize Layer, related transporter It is also defined in META-INF/dubbo/internal/com.alibaba.dubbo.remoting.Transporter In the document:

 netty=com.alibaba.dubbo.remoting.transport.netty.NettyTransporter
netty4=com.alibaba.dubbo.remoting.transport.netty4.NettyTransporter
mina=com.alibaba.dubbo.remoting.transport.mina.MinaTransporter
grizzly=com.alibaba.dubbo.remoting.transport.grizzly.GrizzlyTransporter
Server End sum Client Analysis

1.Server end

When instantiating a specific Server class, we first call the constructor of the parent class, initialize the parameters, and call the bind() method to start the server. The constructor of the parent class AbstractServer is as follows:

 public AbstractServer(URL url, ChannelHandler handler) throws RemotingException {
        super(url, handler);
        localAddress = getUrl().toInetSocketAddress();

        String bindIp = getUrl().getParameter(Constants.BIND_IP_KEY, getUrl().getHost());
        int bindPort = getUrl().getParameter(Constants.BIND_PORT_KEY, getUrl().getPort());
        if (url.getParameter(Constants.ANYHOST_KEY, false) || NetUtils.isInvalidLocalHost(bindIp)) {
            bindIp = NetUtils.ANYHOST;
        }
        bindAddress = new InetSocketAddress(bindIp, bindPort);
        this.accepts = url.getParameter(Constants.ACCEPTS_KEY, Constants.DEFAULT_ACCEPTS);
        this.idleTimeout = url.getParameter(Constants.IDLE_TIMEOUT_KEY, Constants.DEFAULT_IDLE_TIMEOUT);
        try {
            doOpen();
            if (logger.isInfoEnabled()) {
                logger.info("Start " + getClass().getSimpleName() + " bind " + getBindAddress() + ", export " + getLocalAddress());
            }
        } catch (Throwable t) {
            throw new RemotingException(url.toInetSocketAddress(), null, "Failed to bind " + getClass().getSimpleName()
                    + " on " + getLocalAddress() + ", cause: " + t.getMessage(), t);
        }
        //fixme replace this with better method
        DataStore dataStore = ExtensionLoader.getExtensionLoader(DataStore.class).getDefaultExtension();
        executor = (ExecutorService) dataStore.get(Constants.EXECUTOR_SERVICE_COMPONENT_KEY, Integer.toString(url.getPort()));
    }
Mainly from url Getting the startup parameters includes: ip,port,accepts(Acceptable number of connections, 0 for unrestricted number, default to 0),idleTimeout Wait; then call doOpen Method Start the service by binding ports through a specific communication framework; Netty For example, see doOpen()The methods are 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);
        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.setOption("child.tcpNoDelay", 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 > 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);
                return pipeline;
            }
        });
        // bind
        channel = bootstrap.bind(getBindAddress());
    }
These are regular startups netty Program, need to specify codec, nettyHandler;Encoding and decoding have already been introduced in the preceding section, but not in detail here. nettyHandler;server The end of the data is decoded and handed over to the end. NettyHandler To deal with, NettyHandler Inheritance Netty Of SimpleChannelHandler Class, rewrite channelConnected,channelDisconnected,messageReceived,writeRequested as well as exceptionCaught Method, basically, is the usual operation: establish a connection, disconnect, receive messages, send messages, exception handling; take a look at some of the source code:

 @Override
    public void channelConnected(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception {
        NettyChannel channel = NettyChannel.getOrAddChannel(ctx.getChannel(), url, handler);
        try {
            if (channel != null) {
                channels.put(NetUtils.toAddressString((InetSocketAddress) ctx.getChannel().getRemoteAddress()), channel);
            }
            handler.connected(channel);
        } finally {
            NettyChannel.removeChannelIfDisconnected(ctx.getChannel());
        }
    }

    @Override
    public void channelDisconnected(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception {
        NettyChannel channel = NettyChannel.getOrAddChannel(ctx.getChannel(), url, handler);
        try {
            channels.remove(NetUtils.toAddressString((InetSocketAddress) ctx.getChannel().getRemoteAddress()));
            handler.disconnected(channel);
        } finally {
            NettyChannel.removeChannelIfDisconnected(ctx.getChannel());
        }
    }
     @Override
    public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception {
        NettyChannel channel = NettyChannel.getOrAddChannel(ctx.getChannel(), url, handler);
        try {
            handler.received(channel, e.getMessage());
        } finally {
            NettyChannel.removeChannelIfDisconnected(ctx.getChannel());
        }
    }

    @Override
    public void writeRequested(ChannelHandlerContext ctx, MessageEvent e) throws Exception {
        super.writeRequested(ctx, e);
        NettyChannel channel = NettyChannel.getOrAddChannel(ctx.getChannel(), url, handler);
        try {
            handler.sent(channel, e.getMessage());
        } finally {
            NettyChannel.removeChannelIfDisconnected(ctx.getChannel());
        }
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) throws Exception {
        NettyChannel channel = NettyChannel.getOrAddChannel(ctx.getChannel(), url, handler);
        try {
            handler.caught(channel, e.getCause());
        } finally {
            NettyChannel.removeChannelIfDisconnected(ctx.getChannel());
        }
    }
Wrap the original channel of netty into Netty Channel of dubbo, and save Netty Channel in channelMap, the internal static variable of Netty Channel; all the methods here call getOrAddChannel method, add it first, and finally decide whether the channel has been closed in final, if the channel is removed from channelMap; the middle part calls the corresponding method of handler, and so on. The handler is the NettyServer that is passed in at instantiation. NettyServer itself is also a ChannelHandler. You can see the ChannelHandler interface class:

 public interface ChannelHandler {

    void connected(Channel channel) throws RemotingException;

    void disconnected(Channel channel) throws RemotingException;

    void sent(Channel channel, Object message) throws RemotingException;

    void received(Channel channel, Object message) throws RemotingException;

    void caught(Channel channel, Throwable exception) throws RemotingException;
}
Concrete server Classes can also do some processing, such as connected Does the time period exceed accepts,If the connection is rejected, it is handed over to instantiation after processing. Server Incoming ChannelHandler Handling, such as HeaderExchanger Initialized:

 public class HeaderExchanger implements Exchanger {

    public static final String NAME = "header";

    @Override
    public ExchangeClient connect(URL url, ExchangeHandler handler) throws RemotingException {
        return new HeaderExchangeClient(Transporters.connect(url, new DecodeHandler(new HeaderExchangeHandler(handler))), true);
    }

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

}
It can be found here that the specific ChannelHandler yes DecodeHandler,Note here Decode and Netty Oneself decode Dissimilarity, Netty Oneself decode Implementation NettyHandler Decoding was performed before; subsequent operations were performed at Exchange Layer processing, this article will not introduce for the time being;

2.Client side

Also look at the parent AbstractClient, constructed as follows:

 public AbstractClient(URL url, ChannelHandler handler) throws RemotingException {
        super(url, handler);

        send_reconnect = url.getParameter(Constants.SEND_RECONNECT_KEY, false);

        shutdown_timeout = url.getParameter(Constants.SHUTDOWN_TIMEOUT_KEY, Constants.DEFAULT_SHUTDOWN_TIMEOUT);

        // The default reconnection interval is 2s, 1800 means warning interval is 1 hour.
        reconnect_warning_period = url.getParameter("reconnect.waring.period", 1800);

        try {
            doOpen();
        } catch (Throwable t) {
            close();
            throw new RemotingException(url.toInetSocketAddress(), null,
                    "Failed to start " + getClass().getSimpleName() + " " + NetUtils.getLocalAddress()
                            + " connect to the server " + getRemoteAddress() + ", cause: " + t.getMessage(), t);
        }
        try {
            // connect.
            connect();
            if (logger.isInfoEnabled()) {
                logger.info("Start " + getClass().getSimpleName() + " " + NetUtils.getLocalAddress() + " connect to the server " + getRemoteAddress());
            }
        } catch (RemotingException t) {
            if (url.getParameter(Constants.CHECK_KEY, true)) {
                close();
                throw t;
            } else {
                logger.warn("Failed to start " + getClass().getSimpleName() + " " + NetUtils.getLocalAddress()
                        + " connect to the server " + getRemoteAddress() + " (check == false, ignore and retry later!), cause: " + t.getMessage(), t);
            }
        } catch (Throwable t) {
            close();
            throw new RemotingException(url.toInetSocketAddress(), null,
                    "Failed to start " + getClass().getSimpleName() + " " + NetUtils.getLocalAddress()
                            + " connect to the server " + getRemoteAddress() + ", cause: " + t.getMessage(), t);
        }

        executor = (ExecutorService) ExtensionLoader.getExtensionLoader(DataStore.class)
                .getDefaultExtension().get(Constants.CONSUMER_SIDE, Integer.toString(url.getPort()));
        ExtensionLoader.getExtensionLoader(DataStore.class)
                .getDefaultExtension().remove(Constants.CONSUMER_SIDE, Integer.toString(url.getPort()));
    }
The client needs to provide a reconnection mechanism, so several parameters of initialization are related to reconnection. send_reconnect Indicates whether a reconnection is initiated when a message is sent that the connection has been disconnected. reconnect_warning_period How often do you report a repeat warning? shutdown_timeout Represents a timeout that the connection server has never been able to connect; the next step is the call doOpen()The same has been done. Netty For example:

 protected void doOpen() throws Throwable {
        NettyHelper.setNettyLoggerFactory();
        bootstrap = new ClientBootstrap(channelFactory);
        // config
        // @see org.jboss.netty.channel.socket.SocketChannelConfig
        bootstrap.setOption("keepAlive", true);
        bootstrap.setOption("tcpNoDelay", true);
        bootstrap.setOption("connectTimeoutMillis", getTimeout());
        final NettyHandler nettyHandler = new NettyHandler(getUrl(), this);
        bootstrap.setPipelineFactory(new ChannelPipelineFactory() {
            @Override
            public ChannelPipeline getPipeline() {
                NettyCodecAdapter adapter = new NettyCodecAdapter(getCodec(), getUrl(), NettyClient.this);
                ChannelPipeline pipeline = Channels.pipeline();
                pipeline.addLast("decoder", adapter.getDecoder());
                pipeline.addLast("encoder", adapter.getEncoder());
                pipeline.addLast("handler", nettyHandler);
                return pipeline;
            }
        });
    }
Netty Client routine code, set and Server End-to-end identical NettyHandler,decoder and encoder;Let's focus on the following connect Method:

protected void connect() throws RemotingException {
        connectLock.lock();
        try {
            if (isConnected()) {
                return;
            }
            initConnectStatusCheckCommand();
            doConnect();
            if (!isConnected()) {
                throw new RemotingException(this, "Failed connect to server " + getRemoteAddress() + " from " + getClass().getSimpleName() + " "
                        + NetUtils.getLocalHost() + " using dubbo version " + Version.getVersion()
                        + ", cause: Connect wait timeout: " + getTimeout() + "ms.");
            } else {
                if (logger.isInfoEnabled()) {
                    logger.info("Successed connect to server " + getRemoteAddress() + " from " + getClass().getSimpleName() + " "
                            + NetUtils.getLocalHost() + " using dubbo version " + Version.getVersion()
                            + ", channel is " + this.getChannel());
                }
            }
            reconnect_count.set(0);
            reconnect_error_log_flag.set(false);
        } catch (RemotingException e) {
            throw e;
        } catch (Throwable e) {
            throw new RemotingException(this, "Failed connect to server " + getRemoteAddress() + " from " + getClass().getSimpleName() + " "
                    + NetUtils.getLocalHost() + " using dubbo version " + Version.getVersion()
                    + ", cause: " + e.getMessage(), e);
        } finally {
            connectLock.unlock();
        }
    }
First determine whether the connection has been made, if the connection is direct return;Next, the connection status checker is initialized and checked periodically. channel Whether to connect or not, disconnection will be reconnected, the specific code is as follows: Here I recommend an architecture learning exchange group. Communication Learning Group Number: 821169538  Some video recordings recorded by senior architects will be shared: Spring,MyBatis,Netty Source code analysis, principles of high concurrency, high performance, distributed and micro-service architecture, JVM Performance optimization, distributed architecture and so on become the necessary knowledge system for architects. They can also receive free learning resources, which have benefited a lot at present.

 private synchronized void initConnectStatusCheckCommand() {
        //reconnect=false to close reconnect
        int reconnect = getReconnectParam(getUrl());
        if (reconnect > 0 && (reconnectExecutorFuture == null || reconnectExecutorFuture.isCancelled())) {
            Runnable connectStatusCheckCommand = new Runnable() {
                @Override
                public void run() {
                    try {
                        if (!isConnected()) {
                            connect();
                        } else {
                            lastConnectedTime = System.currentTimeMillis();
                        }
                    } catch (Throwable t) {
                        String errorMsg = "client reconnect to " + getUrl().getAddress() + " find error . url: " + getUrl();
                        // wait registry sync provider list
                        if (System.currentTimeMillis() - lastConnectedTime > shutdown_timeout) {
                            if (!reconnect_error_log_flag.get()) {
                                reconnect_error_log_flag.set(true);
                                logger.error(errorMsg, t);
                                return;
                            }
                        }
                        if (reconnect_count.getAndIncrement() % reconnect_warning_period == 0) {
                            logger.warn(errorMsg, t);
                        }
                    }
                }
            };
            reconnectExecutorFuture = reconnectExecutorService.scheduleWithFixedDelay(connectStatusCheckCommand, reconnect, reconnect, TimeUnit.MILLISECONDS);
        }
    }
Create a Runnable,Used to detect whether the connection is connected, if the connection is disconnected, call connect Method: Scheduling and handing over at regular intervals ScheduledThreadPoolExecutor Execute; call the concrete after initialization Client Of doConnect Operations are also some of the general code for communication frameworks, which are not listed here. NettyChannel Introduction and Server Similar end, but more introduction;

summary

This paper focuses on the analysis of the transport layer in the dubbo architecture, focusing on the Transporter, Client, Server, ChannelHandler classes. The subsequent processing will be in the exchange information exchange layer.
 

Posted by kidintraffic on Sat, 19 Jan 2019 04:18:12 -0800