Dubbo的线程模型、handler

本系列参考官网文档、芋道源码的源码解读和《深入理解Apache Dubbo与实战》一书。Dubbo版本为2.6.1

这篇文章是为了理清Dubbo里的线程模型、Handler机制,顺便解释在服务暴露时候最后遗留下来的问题,即在最后的DubboProtocol#createServer(url)方法里调用了Exchangers.bind(url, requestHandler)做了什么。
我们将讨论当收到请求的时候是怎么从Netty转到Dubbo的逻辑来的,再介绍Handler的调用链路,并且分析如何将解码这一IO操作Dubbo的业务线程池做的

文章内容顺序:
1. Dubbo的线程模型
    1.1 Dubbo的线程模型调用流程
    1.2 Netty线程模型
    1.3 Dubbo中的线程池
    1.4 那么什么业务会由Dubbo自己的线程池来实现呢?
2. Handler的包装流程
    2.1 Handler包装图解和描述
    2.2 创建一个requestHandler
    2.3 Exchange层做两次包装new DecodeHandler(new HeaderExchangeHandler(requestHandler))
    2.4 HeaderExchangeHandler#received
    2.5 DecodeHandler#received
    2.6 Transporters#bind
    2.6.1 ChannelHandlerDispatcher#received
    2.7 NettyTransporter(netty3)    
    2.8 NettyServer构造方法
    2.9 ChannelHandlers#wrap 又进行三层包装
    2.10 AllChannelHandler 与Dubbo线程池耦合
         2.10.1 ChannelEventRunnable 线程的执行逻辑
     2.11 HeartbeatHandler 处理心跳请求
    2.12 MultiMessageHandler 处理批量请求
    2.13 NettyServer
    2.14 NettyServer#doOpen创建Netty的执行链,包装到NettyHandler里
    2.15 NettyHandler做了什么,为什么要封装?
3. Handler的调用顺序
4. 需要注意的几个类
5. Dubbo是怎么用到Netty的

1. Dubbo的线程模型模型

首先来讲讲Dubbo的线程模型,可以看下这篇博客的介绍,下面的图也是来此博客。
dubbo线程模型

1.1Dubbo的线程调用流程

image.png

客户端的主线程发出一个请求后获得future,在执行get时进行阻塞等待;
服务端使用worker线程(netty通信模型)接收到请求后,将请求提交到server线程池(Dubbo线程池)中进行处理
server线程处理完成之后,将相应结果返回给客户端的worker线程池(netty通信模型),最后,worker线程将响应结果提交到client线程池进行处理
client线程将响应结果填充到future中,然后唤醒等待的主线程,主线程获取结果,返回给客户端

这边再简单概括下博客的内容:

1.2Netty线程模型

Dubbo使用netty作为网络传输框架,所以我们先来简单了解下Netty,下图为Netty的线程模型。


image.png

Netty中存在两种线程:boss线程worker线程

boss线程的作用:
accept客户端的连接;将接收到的连接注册到一个worker线程上
个数:通常情况下,服务端每绑定一个端口,开启一个boss线程

worker线程的作用:
处理注册在其身上的连接connection上的各种io事件
个数:默认是核数+1
注意:
一个worker线程可以注册多个connection
一个connection只能注册在一个worker线程上

1.3Dubbo中的线程池

为了配合worker线程工作,在Dubbo中还实现了自己的线程池来执行各种业务。
Dubbo扩展接口 ThreadPool 的SPI实现有如下几种:

  • fixed:固定大小线程池,启动时建立线程,不关闭,一直持有(默认实现)。
    coresize:200
    maxsize:200
    队列:SynchronousQueue
    回绝策略:AbortPolicyWithReport - 打印线程信息jstack,之后抛出异常
  • cached:缓存线程池,空闲一分钟自动删除,需要时重建。
  • limited:可伸缩线程池,但池中的线程数只会增长不会收缩。只增长不收缩的目的是为了避免收缩时突然带来大流量引起性能问题。

1.4那么什么业务会由Dubbo自己的线程池来实现呢?

有5种派发策略:

  • 默认是all:所有消息都派发到Dubbo线程池,包括请求,响应,连接事件,断开事件,心跳等。 即worker线程接收到事件后,将该事件提交到业务线程池中,自己再去处理其他事。
  • direct:worker线程接收到事件后,由worker执行到底。
  • message:只有请求响应消息派发到Dubbo线程池,其它连接断开事件,心跳等消息,直接在 IO线程上执行
  • execution:只请求消息派发到Dubbo线程池,不含响应(客户端线程池),响应和其它连接断开事件,心跳等消息,直接在 IO 线程上执行
  • connection:在 IO 线程上,将连接断开事件放入队列,有序逐个执行,其它消息派发到Dubbo线程池。

image.png

如图所示,各个策略的实现也很简单,直接返回了一个对应名称的XXXChannelHandler,说明具体代码逻辑在这个Handler里面实现。
以上就是基本的Dubbo线程模型了。

2. Handler的包装流程

接下来来讲讲Dubbo里的Handler,会一步步来分析他是怎么包装起来的。
参考如下:dubbo的handler机制

2.1 Handler包装图解和描述

这是使用Dubbo Protocol并且使用Netty作为服务器的情况下Handler的整个包装过程

image.png

上图是来自参考链接的图的一部分,他的Transport层部分画错了,我就自己画了一个,如下图,比较简陋,下图就是完整的handler包装。(我也知道你们喜欢彩色的图啊,可惜我懒)

image.png

1.在DubboProtocol中构建ExchangeHandler命名requestHandler

2.在Exchange层做两次包装new DecodeHandler(new HeaderExchangeHandler(requestHandler)),具体参考类:HeaderExchanger
​ ① 使用HeaderExchangeHandler做一次包装,HeaderExchangeHandler的作用是实现了Request和Response的概念,当接到received请求后,将请求转为reply。请参考类HeaderExchangeHandler
​ ② 使用DecodeHandler做一次包装,DecodeHandler的作用是用来对Request MessageResponse Message做解码操作,解码完成后才能给HeaderExchangeHandler使用。

3.在Exchange层包装后的Handler会被传递到Transporter层(NettyTransporter)并且把类型转换成ChannelHandler,因为ChannelHandler更为抽象。

4.HandlerTransporter层流转,会被传递到NettyServer

5.在NettyServer中被AllChannelHandler包装,其作用是把NettyServer接收到的请求转移给Transporter层的线程池来处理。同步转异步。

6.接着就先被HeartbeatHandler包装用以处理心跳请求,接着被MultiMessageHandler包装用以处理批量请求

7.这个MultiMessageHandler会在NettyServer中以构造函数的方式注入进来

8.NettyServer被再次NettyHandler包装,NettyHandler的父类是SimpleChannelHandler。它属于NettyHandler。由Netty来管理和调用其中的回调方法。Netty在接受到channelActivechannelRead等方法后,会把请求转移给DubboHandler,这样每当请求过来,NettyHandler接到请求就立马把数据和相关信息转交给DubboHandler,由DubboHandler来管理了。

上面是简单的概括,我们来一个个上代码

2.2创建一个requestHandler

 private ExchangeHandler requestHandler = new ExchangeHandlerAdapter() {

        @Override
        public Object reply(ExchangeChannel channel, Object message) throws RemotingException {
            if (message instanceof Invocation) {
                Invocation inv = (Invocation) message;
                // 获得请求对应的 Invoker 对象
                Invoker<?> invoker = getInvoker(channel, inv);
                // 如果是callback 需要处理高版本调用低版本的问题
                if (Boolean.TRUE.toString().equals(inv.getAttachments().get(IS_CALLBACK_SERVICE_INVOKE))) {
                    String methodsStr = invoker.getUrl().getParameters().get("methods");
                    boolean hasMethod = false;
                    if (methodsStr == null || !methodsStr.contains(",")) {
                        hasMethod = inv.getMethodName().equals(methodsStr);
                    } else {
                        String[] methods = methodsStr.split(",");
                        for (String method : methods) {
                            if (inv.getMethodName().equals(method)) {
                                hasMethod = true;
                                break;
                            }
                        }
                    }
                    if (!hasMethod) {
                        logger.warn(new IllegalStateException("The methodName " + inv.getMethodName() + " not found in callback service interface ,invoke will be ignored. please update the api interface. url is:" + invoker.getUrl()) + " ,invocation is :" + inv);
                        return null;
                    }
                }
                // 设置调用方的地址
                RpcContext.getContext().setRemoteAddress(channel.getRemoteAddress());
                // 执行调用
                return invoker.invoke(inv);
            }
            throw new RemotingException(channel, message.getClass().getName() + ": " + message
                    + ", channel: consumer: " + channel.getRemoteAddress() + " --> provider: " + channel.getLocalAddress());
        }

        @Override
        public void received(Channel channel, Object message) throws RemotingException {
            if (message instanceof Invocation) {
                this.reply((ExchangeChannel) channel, message);
            } else {
                super.received(channel, message);
            }
        }

        @Override
        public void connected(Channel channel) {
            this.invoke(channel, Constants.ON_CONNECT_KEY);
        }

        @Override
        public void disconnected(Channel channel) throws RemotingException {
            if (logger.isInfoEnabled()) {
                logger.info("disconected from " + channel.getRemoteAddress() + ",url:" + channel.getUrl());
            }
            this.invoke(channel, Constants.ON_DISCONNECT_KEY);
        }

        /**
         * 调用方法
         *
         * @param channel 通道
         * @param methodKey 方法名
         */
        private void invoke(Channel channel, String methodKey) {
            // 创建 Invocation 对象
            Invocation invocation = createInvocation(channel, channel.getUrl(), methodKey);
            // 调用 received 方法,执行对应的方法
            if (invocation != null) {
                try {
                    this.received(channel, invocation);
                } catch (Throwable t) {
                    logger.warn("Failed to invoke event method " + invocation.getMethodName() + "(), cause: " + t.getMessage(), t);
                }
            }
        }

        private Invocation createInvocation(Channel channel, URL url, String methodKey) {
            String method = url.getParameter(methodKey);
            if (method == null || method.length() == 0) {
                return null;
            }
            RpcInvocation invocation = new RpcInvocation(method, new Class<?>[0], new Object[0]);
            invocation.setAttachment(Constants.PATH_KEY, url.getPath());
            invocation.setAttachment(Constants.GROUP_KEY, url.getParameter(Constants.GROUP_KEY));
            invocation.setAttachment(Constants.INTERFACE_KEY, url.getParameter(Constants.INTERFACE_KEY));
            invocation.setAttachment(Constants.VERSION_KEY, url.getParameter(Constants.VERSION_KEY));
            if (url.getParameter(Constants.STUB_EVENT_KEY, false)) {
                invocation.setAttachment(Constants.STUB_EVENT_KEY, Boolean.TRUE.toString());
            }
            return invocation;
        }
    };
image.png

他实现了ChannelHandler接口5个关键方法,连接,断开连接,发送消息,接受消息和异常处理方法。也是rpc调用的常用处理方法。 同时也是线程派发处理关注的方法。

requestHandler第一次用到,也就是在服务暴露那篇最后提的DubboProtocol#Exchangers.bind(url, requestHandler)方法中

2.3 Exchange层做两次包装new DecodeHandler(new HeaderExchangeHandler(requestHandler))

进去后发现Exchangers是个门面类,调用的是HeaderExchanger#bind方法,事实上也只有HeaderExchanger这一个实现。如下图

image.png

image.png

接着来看HeaderExchanger#bind

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

}

注意,这里我们的requestHandler直接被包装了两层,对应的是最上面图里的Exchange层的包装,从里到外看看这两个包装的Handler做了什么(主要看他的received方法)

2.4 HeaderExchangeHandler#received

/**
 * ExchangeReceiver
 *
 * 基于消息头部( Header )的信息交换处理器实现类
 */
public class HeaderExchangeHandler implements ChannelHandlerDelegate {
    public void received(Channel channel, Object message) throws RemotingException {
        // 设置最后的读时间
        channel.setAttribute(KEY_READ_TIMESTAMP, System.currentTimeMillis());
        // 创建 ExchangeChannel 对象
        ExchangeChannel exchangeChannel = HeaderExchangeChannel.getOrAddChannel(channel);
        try {
            // 处理请求( Request )
            if (message instanceof Request) {
                // handle request.
                Request request = (Request) message;
                // 处理事件请求
                if (request.isEvent()) {
                    handlerEvent(channel, request);
                } else {
                    // 处理普通请求,判断是否要响应(即双向通信)
                    if (request.isTwoWay()) {
                        Response response = handleRequest(exchangeChannel, request);
                    // 将调用结果返回给服务消费端
                        channel.send(response);
                    // 如果是单向通信,仅向后调用指定服务即可,无需返回调用结果
                    } else {
                        handler.received(exchangeChannel, request.getData());
                    }
                }
            // 处理响应( Response )
            } else if (message instanceof Response) {
                handleResponse(channel, (Response) message);
            // 处理 String
            } else if (message instanceof String) {
                // 客户端侧,不支持 String
                if (isClientSide(channel)) {
                    Exception e = new Exception("Dubbo client can not supported string message: " + message + " in channel: " + channel + ", url: " + channel.getUrl());
                    logger.error(e.getMessage(), e);
                // 服务端侧,目前是 telnet 命令
                } else {
                    String echo = handler.telnet(channel, (String) message);
                    if (echo != null && echo.length() > 0) {
                        channel.send(echo);
                    }
                }
                // 提交给装饰的 `handler`,继续处理
            } else {
                handler.received(exchangeChannel, message);
            }
        } finally {
            // 移除 ExchangeChannel 对象,若已断开
            HeaderExchangeChannel.removeChannelIfDisconnected(channel);
        }
    }
//省略其他代码
}

代码注释已经比较清晰,简单来说就是会辨别收到的message是服务端收到的Request还是消费端发起请求后得到的Reponse进行一系列业务判断操作(比如对于不同的请求,有事件请求、需要响应的和不需要响应的,都有不同的执行逻辑)
如果需要响应还会传到更里面一层也就是我们的requestHandler执行received方法

再来看看在更外面一层的DecodeHandler

2.5DecodeHandler#received

public class DecodeHandler extends AbstractChannelHandlerDelegate {
    public void received(Channel channel, Object message) throws RemotingException {
        if (message instanceof Decodeable) {
 // 对 Decodeable 接口实现类对象进行解码
            decode(message);
        }

        if (message instanceof Request) {
// 对 Request 的 data 字段进行解码
            decode(((Request) message).getData());
        }

        if (message instanceof Response) {
// 对 Request 的 result 字段进行解码
            decode(((Response) message).getResult());
        }
// 执行后续逻辑
        handler.received(channel, message);
    }

    private void decode(Object message) {
        if (message != null && message instanceof Decodeable) {
            try {
                ((Decodeable) message).decode(); // 解析消息
                if (log.isDebugEnabled()) {
                    log.debug(new StringBuilder(32).append("Decode decodeable message ").append(message.getClass().getName()).toString());
                }
            } catch (Throwable e) {
                if (log.isWarnEnabled()) {
                    log.warn(new StringBuilder(32).append("Call Decodeable.decode failed: ").append(e.getMessage()).toString(), e);
                }
            } // ~ end of catch
        } // ~ end of if
    } // ~ end of method decode
//省略其他代码
}

注意看这个received中的方法,很明显这个DecodeHandler就是用来解码的,根据不同的message类型,执行不同的逻辑,,这里的if (message instanceof Decodeable)判断下的代码就是我们解码篇提到的交由业务线程池来执行解码操作。
每个Handler都会执行到这一步,如果被解码过自然不用再解码了(在DecodeableRpcInvocation或者DecodeableRpcResult中会有是否解码已完成的标志位),如果还未解码,当前线程就会进行解码操作

接下来进入到Transporters.bind(url, new DecodeHandler(new HeaderExchangeHandler(handler)))中的Transporters#bind方法

2.6 Transporters#bind

    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");
        }
        // 创建 handler
        ChannelHandler handler;
        if (handlers.length == 1) {
            handler = handlers[0];
        } else {
            handler = new ChannelHandlerDispatcher(handlers);
        }
        // 创建 Server 对象
        return getTransporter().bind(url, handler);
    }

同样是门面类,会调用真正的Transporter来执行bind方法,
这里我们只传了一个ChannelHandler对象,所以直接到了rerun,如果ChannelHandler有多个的情况下,说明这些handler是同级的,new ChannelHandlerDispatcher(handlers)每个实现的方法,都会循环调用 channelHandlers的方法

2.6.1ChannelHandlerDispatcher#received

如下面的代码例子所示:

public class ChannelHandlerDispatcher implements ChannelHandler {
    public void received(Channel channel, Object message) {
        for (ChannelHandler listener : channelHandlers) {
            try {
                listener.received(channel, message);
            } catch (Throwable t) {
                logger.error(t.getMessage(), t);
            }
        }
    }
//省略其他代码
}

那我们便直接来到真正的Transporter#bind方法中一探究竟。

image.png

这个Transporter有四种实现,其中又有netty3netty4,默认实现为netty3,我们就来看看netty3的实现吧

netty3的NettyTransporter 实现如下

2.7 NettyTransporter(netty3)

public class NettyTransporter implements Transporter {

    public static final String NAME = "netty";

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

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

}

接着来看NettyServer的实现

2.8 NettyServer构造方法

public class NettyServer extends AbstractServer implements Server {
public NettyServer(URL url, ChannelHandler handler) throws RemotingException {
        super(url, ChannelHandlers.wrap(handler, ExecutorUtil.setThreadName(url, SERVER_THREAD_POOL_NAME)));
    }
}

又是一层对handler的包装,调用了ChannelHandlers#wrap方法,跟进去看看

这里注意一下!!!我们先关注ChannelHandlers#warp的调用,这里的super方法我们最后会分析。

2.9 ChannelHandlers#wrap 又进行三层包装

public class ChannelHandlers {

    /**
     * 单例
     */
    private static ChannelHandlers INSTANCE = new ChannelHandlers();



    public static ChannelHandler wrap(ChannelHandler handler, URL url) {
        return ChannelHandlers.getInstance().wrapInternal(handler, url);
    }

    protected ChannelHandler wrapInternal(ChannelHandler handler, URL url) {
        return new MultiMessageHandler(
                new HeartbeatHandler(
                        ExtensionLoader.getExtensionLoader(Dispatcher.class)
                                     .getAdaptiveExtension().dispatch(handler, url)
                )
        );
    }
//省略其他方法

}

wrap方法直接调用了他自己实现的wrapInternal方法,套了一层又一层。
注意ExtensionLoader.getExtensionLoader(Dispatcher.class) .getAdaptiveExtension().dispatch(handler, url)这一段代码,dispatch(handler, url)前面很好理解,就是拿到Dispatcher这个接口的扩展类,我们的默认扩展类是AllDispatcher,在前面已经介绍过了,我们就以默认的类来继续讲,后面直接调用了这个AllDispatcher#dispatch方法,这个我们前面也同样介绍过,再来简单复习下下面的图,就是直接把我们的handler又又又封装了一层,封装到了AllChannelHandler中。那么就来看看这个AllChannelHandler又做了什么吧

image.png

2.10 AllChannelHandler 与Dubbo线程池耦合

/**
 * `all` 所有消息都派发到线程池,包括请求,响应,连接事件,断开事件,心跳等。
 */
public class AllChannelHandler extends WrappedChannelHandler {

    public AllChannelHandler(ChannelHandler handler, URL url) {
        super(handler, url);
    }

 public void received(Channel channel, Object message) throws RemotingException {
        ExecutorService cexecutor = getExecutorService();
        try {
            cexecutor.execute(new ChannelEventRunnable(channel, handler, ChannelState.RECEIVED, message));
        } catch (Throwable t) {
            //TODO A temporary solution to the problem that the exception information can not be sent to the opposite end after the thread pool is full. Need a refactoring
          //用来解决线程池已满后无法将异常信息发送到另一端的问题的临时解决方案,仍然需要重构
            if(message instanceof Request && t instanceof RejectedExecutionException){
                Request request = (Request)message;
                if(request.isTwoWay()){
                    String msg = "Server side(" + url.getIp() + "," + url.getPort() + ") threadpool is exhausted ,detail msg:" + t.getMessage();
                    Response response = new Response(request.getId(), request.getVersion());
                    response.setStatus(Response.SERVER_THREADPOOL_EXHAUSTED_ERROR);
                    response.setErrorMessage(msg);
                    channel.send(response);
                    return;
                }
            }
            throw new ExecutionException(message, channel, getClass() + " error when process received event .", t);
        }
    }
}

这个类还调用了父类的有参构造,父类的构造方法里面就是通过SPI机制拿到url后创建对应的线程池(还记得我们Dubbo中的三种线程池不,默认是fixed),这里就不贴代码了。
还是来看看这个AllChannelHandler#received方法,直接调用了线程池来执行方法。

2.10.1 ChannelEventRunnable 线程的执行逻辑

熟悉线程池的小伙伴肯定猜出来了,这个执行的ChannelEventRunnable肯定是实现了Runnable接口的类了。再来进去看看他的执行逻辑吧

public class ChannelEventRunnable implements Runnable {
    public void run() {
        switch (state) {
            case CONNECTED:
                try {
                    handler.connected(channel);
                } catch (Exception e) {
                    logger.warn("ChannelEventRunnable handle " + state + " operation error, channel is " + channel, e);
                }
                break;
            case DISCONNECTED:
                try {
                    handler.disconnected(channel);
                } catch (Exception e) {
                    logger.warn("ChannelEventRunnable handle " + state + " operation error, channel is " + channel, e);
                }
                break;
            case SENT:
                try {
                    handler.sent(channel, message);
                } catch (Exception e) {
                    logger.warn("ChannelEventRunnable handle " + state + " operation error, channel is " + channel
                            + ", message is " + message, e);
                }
                break;
            case RECEIVED:
                try {
                    handler.received(channel, message);
                } catch (Exception e) {
                    logger.warn("ChannelEventRunnable handle " + state + " operation error, channel is " + channel
                            + ", message is " + message, e);
                }
                break;
            case CAUGHT:
                try {
                    handler.caught(channel, exception);
                } catch (Exception e) {
                    logger.warn("ChannelEventRunnable handle " + state + " operation error, channel is " + channel
                            + ", message is: " + message + ", exception is " + exception, e);
                }
                break;
            default:
                logger.warn("unknown state: " + state + ", message is " + message);
        }
    }
//省略其他代码
}

这里的线程池很明显,用了case语句来判断到底执行的我们传入的包装类DecodeHandler里的什么方法。(AllChannelHandler里面的一层是DecodeHandler)

到了这里,我们别忘了在ChannelHandlers#wrapInternal还没分析完,外面还有一层HeartbeatHandler和一层MultiMessageHandler。先来看HeartbeatHandler

2.11 HeartbeatHandler 处理心跳请求

public class HeartbeatHandler extends AbstractChannelHandlerDelegate {
    public void received(Channel channel, Object message) throws RemotingException {
        // 设置最后的读时间
        setReadTimestamp(channel);
        // 如果是心跳事件请求,返回心跳事件的响应
        if (isHeartbeatRequest(message)) {
            Request req = (Request) message;
            if (req.isTwoWay()) {
                Response res = new Response(req.getId(), req.getVersion());
                res.setEvent(Response.HEARTBEAT_EVENT);
                channel.send(res);
                if (logger.isInfoEnabled()) {
                    int heartbeat = channel.getUrl().getParameter(Constants.HEARTBEAT_KEY, 0);
                    if (logger.isDebugEnabled()) {
                        logger.debug("Received heartbeat from remote channel " + channel.getRemoteAddress()
                                + ", cause: The channel has no data-transmission exceeds a heartbeat period"
                                + (heartbeat > 0 ? ": " + heartbeat + "ms" : ""));
                    }
                }
            }
            return;
        }
        // 如果是心跳事件响应,返回
        if (isHeartbeatResponse(message)) {
            if (logger.isDebugEnabled()) {
                logger.debug(new StringBuilder(32).append("Receive heartbeat response in thread ").append(Thread.currentThread().getName()).toString());
            }
            return;
        }
        // 提交给装饰的 `handler`,继续处理
        handler.received(channel, message);
    }
//省略其他代码
}

可以看到HeartbeatHandlerreceived方法进行了处理,所以心跳的消息的接受和发送是不会派发到Dubbo线程池的。

接着是外面的那层MultiMessageHandler啦

2.12 MultiMessageHandler 处理批量请求

public class MultiMessageHandler extends AbstractChannelHandlerDelegate {

    public MultiMessageHandler(ChannelHandler handler) {
        super(handler);
    }

    @SuppressWarnings("unchecked")
    @Override
    public void received(Channel channel, Object message) throws RemotingException {
        if (message instanceof MultiMessage) { // 多消息
            MultiMessage list = (MultiMessage) message;
            for (Object obj : list) {
                handler.received(channel, obj);
            }
        } else {
            handler.received(channel, message);
        }
    }

}

从他的名字就可以看出来主要是处理多消息的,主要完成多消息类型的循环解析接收。

这里来做下收尾:还记得我当时三个!!!的NettyServer构造方法调用的父类构造方法嘛,一起来看看。

2.13 NettyServer

public abstract class AbstractServer extends AbstractEndpoint implements Server {
    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()));
    }
//省略其他代码
}

这个AbstractServer就是NettyServer的父类,可以看到这个构造方法中,开启服务器时调用了doOpen()方法,这是交由子类自己的实现的方法。(注意,我们这里只关注handler的传递过程,其他代码的介绍就略过啦)

2.14 NettyServer#doOpen创建Netty的执行链,包装到NettyHandler里

public class NettyServer extends AbstractServer implements Server {
public NettyServer(URL url, ChannelHandler handler) throws RemotingException {
        super(url, ChannelHandlers.wrap(handler, ExecutorUtil.setThreadName(url, SERVER_THREAD_POOL_NAME)));
    }

    @Override
 protected void doOpen() {
        // 设置日志工厂
        NettyHelper.setNettyLoggerFactory();

        // 创建线程池
        ExecutorService boss = Executors.newCachedThreadPool(new NamedThreadFactory("NettyServerBoss", true));
        ExecutorService worker = Executors.newCachedThreadPool(new NamedThreadFactory("NettyServerWorker", true));

        // 创建 ChannelFactory 对象
        ChannelFactory channelFactory = new NioServerSocketChannelFactory(boss, worker, getUrl().getPositiveParameter(Constants.IO_THREADS_KEY, Constants.DEFAULT_IO_THREADS));
        // 实例化 ServerBootstrap
        bootstrap = new ServerBootstrap(channelFactory);

        // 创建 NettyHandler 对象,着重注意!!!
        final NettyHandler nettyHandler = new NettyHandler(getUrl(), this);
        // 设置 `channels` 属性
        channels = nettyHandler.getChannels();
        bootstrap.setPipelineFactory(new ChannelPipelineFactory() {
            @Override
            public ChannelPipeline getPipeline() {
                // 创建 NettyCodecAdapter 对象
                NettyCodecAdapter adapter = new NettyCodecAdapter(getCodec(), getUrl(), NettyServer.this);
                ChannelPipeline pipeline = Channels.pipeline();
                pipeline.addLast("decoder", adapter.getDecoder()); // 解码
                pipeline.addLast("encoder", adapter.getEncoder()); // 解码
                pipeline.addLast("handler", nettyHandler); // 处理器,着重注意!!!
                return pipeline;
            }
        });
        // 服务器绑定端口监听
        // bind
        channel = bootstrap.bind(getBindAddress());
    }
//省略其他代码
}

这个就是NettyServer#doOpen()的代码啦,注意代码打上着重注意!!!的两行,
一行是final NettyHandler nettyHandler = new NettyHandler(getUrl(), this);
将这个NettyServer又包装进了NettyHandler里,
还有一行是pipeline.addLast("handler", nettyHandler);,这是Netty的机制,
可以参考详细讲解Netty中Pipeline责任链做一个简单的了解,这里解释了为什么收到消息后能调用到我们的Handler。
顺带一提,NettyServer其实也实现了ChannelHandler,他也算是个"handler",看下图。

image.png

闲话至此,我们来看下NettyHandler做了什么,为什么要封装?

2.15 NettyHandler做了什么,为什么要封装?

@Sharable
public class NettyHandler extends SimpleChannelHandler {

    public NettyHandler(URL url, ChannelHandler handler) {
        if (url == null) {
            throw new IllegalArgumentException("url == null");
        }
        if (handler == null) {
            throw new IllegalArgumentException("handler == null");
        }
        this.url = url;
        this.handler = handler;
    }

    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());
        }
    }
//省略其他代码
}

这个SimpleChannelHandlerNetty的接口,说明这个NettyHandlerNetty handler服务Dubbo handler职责交接的地方,Netty首先接收到信息后在内部进行一连串调用,然后调用到Dubbo自己实现的Netty接口的方法,从此开始在Dubbo中进行方法的调用。
再顺带一提,当我们debug的时到NettyHandler#messageReceived这行的时候,
这里的handler一个个分解下去,就是包装的顺序啦,看下图。

image.png

这里再再顺便提一嘴,我们介绍的都是以Netty3,如果换成Netty4实现的话,最外面的handler类名不是现在的NettyHandler而是NettyServerHandler。(一开始我迷糊了后来才知道所以在这一提,可能会有跟我一样迷糊的人呢)

3. Handler的调用顺序

至此,我们的Handler终于分析完毕了!,再来捋一遍调用的顺序:

  • netty处理接收请求
  • ->NioServerSocketPipelineSink$Boss
  • ->注册NioWorker线程用于处理长连接和IO操作
  • ->DefaultChannelPipeline(处理请求的反序列化和分发handler,初始化过程见NettyServer,会往pipeline加入decoder、encoder和NettyHandler)
  • ->decoder做反序列化
  • ->NettyHandler处理请求
  • ->NettyServer处理请求(NettyServer初始化封装了MultiMessageHandler、HeartbeatHandler以及根据dispatcher得到的channelHandler(里面还有封装,就不往下说了),默认是AllDispatcher)
  • ->MultiMessageHandler for each处理批量请求
  • ->HeartbeatHandler处理心跳请求
  • ->AllDispatcher得到的AllChannelHandler处理将通道所有状态变更用新线程处理(包括建立连接、断开连接、接收请求、异常)
  • ->从指定的线程池拿到线程执行rpc请求
  • ->DecodeHandler进行对message的解码,此操作可能会被放在work线程进行,如果work线程已经解码过了,得到的message内部会有标识,解码的方法会直接返回进行下一步操作
  • ->HeaderExchangeHandler判断双向和单向处理请求,双向则将结果使用当前channel回写
  • ->到DubboProtocol$ExchangeHandlerAdapter处理请求调用
  • ->到此服务端网络传输层结束

4. 需要注意的几个类

1.DubboProtocol类,Dubbo Handler初始化创建的地方
2.HeaderExchangeHandler类,Request和Response概念重点提现的地方
3.DecodeHandler类,Dubbo线程池帮忙解码的地方
3.NettyHandler类,Netty Handler服务与Dubbo Handler职责交接的地方

5. Dubbo是怎么用到Netty的?

可以看到,dubbo 使用Netty还是挺简单的,消费者使用 NettyClient,提供者使用 NettyServerProvider 启动的时候,会开启端口监听,使用我们平时启动 Netty一样的方式。而ClientSpring getBean的时候,会创建Client,当调用远程方法的时候,将数据通过 dubbo 协议编码发送到 NettyServer,然后 NettServer收到数据后解码,并调用本地方法,并返回数据,完成一次完美的 RPC 调用。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,233评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,013评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,030评论 0 241
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,827评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,221评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,542评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,814评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,513评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,225评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,497评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,998评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,342评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,986评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,055评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,812评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,560评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,461评论 2 266