美文网首页
Dubbo的线程模型

Dubbo的线程模型

作者: gregoriusxu | 来源:发表于2022-02-20 22:24 被阅读0次

    作为一款高性能的实战性的RPC框架,它是如何能支撑超大请求的呢?

    其实不管是什么框架,在高性能这块的设计,基本上大致都是一样的,就是其核心思想都是一样的,只是实现方式上各种框架因地制宜,会对理论做一下取舍来适应框架的不同的定位和目标。

    RPC框架是一个分布式调用框架,简单来说就是消费端通过网络连接来调用服务端的服务,我们把线程分为业务线程和IO线程两种,什么是业务线程和IO线程?从官网给出的设计图我们可以看到dubbo分为3层,如下图:

    b5086467ca79b2a2ea6be34822a29229.jpg

    第一层是Business,第二层是RPC,第三层是Remoting,从这个划分角度来讲,Business和RPC属于业务线程,Remoting是IO线程,IO就是输入输出,远程调用,网络传输就是属于IO的一种,在IO交互这块,dubbo封装了各种基层协议,包括自己的dubbo协议,rmi,hessian,http等。在网络传输这块dubbo使用了netty,mina等框架,从下图可以看出。

    0941cba96cb110ce0cf45f05d3eec064.jpg

    借助于netty的设计思想,我们知道为了达到高性能,
    我们必须采用NIO或者IO多路复用,AIO等这些设计模式来进行设计,其核心思想是能快速的接待消费线程,而让慢速的IO通过队列进行等待来避免消费线程的阻塞,借助于现代计算机的多线程CPU来提高IO的并行能力,所以不管是什么框架,在高性能方面都离不开多线程和队列这两样东西,如果你熟悉线程池的相关原理,那么就知道它就是我们想要的东西,实现高性能的必备条件,从上图可以看出,treadpool就是线程池,所以dubbo在实现高性能这块也没有例外。

    Dubbo在传输这块封装了Transport层,与TCP/IP协议的传输层类似,不过Dubbo这块是使用的TPC/IP协议层的东西进行封装,使用的是传输层的一些框架,并且Dubbo使用SPI机制来通过配置方式动态进行切换。

    8aa526300043f1a796916137e7b615cb.png

    有人用netty的pipeline机制和dubbo的handler 包装来进行类比,其实不太恰当,首先从实现目的上来区分, netty pipeline 的各种handler 相互独立,没有上下层的关系,dubbo 的handler 有明确的上下层关系,上层是对底层的协议进行扩展和扩充来实现完整的数据包传输,最终实现的是一个完整的TCP/IP协议传输。从技术实现上来讲netty实现 pipeline 使用的是双端队列,即双链表的实现方式,而 dubbo 实现 handler 使用的是装饰器模式,即洋葱模型。

    e91fcfe96652c2cf7ff4200a7b46eb4a.png

    首先最上层在DubboProtocol 抽象出requestHandler实现,这是一个ExchangeHandler接口,是 dubbo抽象出的Exchange层的handler接口,具体实现是ExchangeHandlerAdapter

    然后dubbo将此实现通过 createServer方法创建出自己抽象的ExchangeServer

     server = Exchangers.bind(url, requestHandler);
    

    Exchangers.bind方法通过SPI机制来创建ExchangeServer,默认的配置实现是HeaderExchanger,所以我们看一下HeaderExchanger的代码:

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

    可以看到具体的实现是HeaderExchangeServer,在其构造函数通过Transporters.bind接入传输层的RemotingServer,Transporters的bind方法同样使用的是SPI机制,Transporter接口我们看到默认使用的是netty,自然使用的是NettyTransporter,我们看到在其bind方法创建的是NettyServer,从上面的代码可以看出DecodeHandler来装饰HeaderExchangeHandler,而HeaderExchangeHandler再来装饰DubboProtocol的requestHandler的过程。

    3a649fb4b312b136593a3df646e391ca.png
    
    @Override
        public RemotingServer bind(URL url, ChannelHandler handler) throws RemotingException {
            return new NettyServer(url, handler);
        }
    

    顾名思义, NettyServer底层使用的是netty作为数据传输工具,是对netty的封装。首先我们看一下NettyServer的构造函数:

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

    上面的代码我们主要关注ChannelHandlers的wrap方法,最终调用的是其wrapInternal方法:

    
        protected ChannelHandler wrapInternal(ChannelHandler handler, URL url) {
            return new MultiMessageHandler(new HeartbeatHandler(url.getOrDefaultFrameworkModel().getExtensionLoader(Dispatcher.class)
                    .getAdaptiveExtension().dispatch(handler, url)));
        }
    

    我们可以看到最外层又使用了MultiMessageHandler装饰HeartbeatHandler,HeartbeatHandler又装饰我们传入的handler,所以我们猜测HeartbeatHandler肯定是用来装饰DecodeHandler的,那么他们中间有没有存在其它handler呢?为了找到答案我们需要搞清楚HeartbeatHandler的构造函数传入的是什么。url的getOrDefaultFrameworkModel用来获取的FrameworkModel是继承于ExtensionAccessor接口的,这个接口是dubbo的SPI扩展接口,通过getExtensionLoader获取Dispatcher配置的实例,我们可以通过配置可以知道这里使用的是AllDispatcher实现:

    
    @Override
        public ChannelHandler dispatch(ChannelHandler handler, URL url) {
            return new AllChannelHandler(handler, url);
        }
    
    

    通过上面的代码我们知道了上面的问题的答案,HeartbeatHandler和DecodeHandler之间还存在一个AllChannelHandler。所以最后我们拿到一个完整的dubbo handler传输链条如下:

    MultiMessageHandler->HeartbeatHandler->AllChannelHandler->DecodeHandler->HeaderExchangeHandler->requestHandler

    下面是整个dubbo的调用过程图

    3564e1c2cf918c6224efc9220d16fe79.jpg

    从上图中我们可以看到dubbo中与线程池有关的就是Dispather 有关的 handler,其中AllChannelHandler只是默认的一种实现,我们首先来看看AllChannelHandler的实现原理:

    
    @Override
        public void received(Channel channel, Object message) throws RemotingException {
            ExecutorService executor = getPreferredExecutorService(message);
            try {
                executor.execute(new ChannelEventRunnable(channel, handler, ChannelState.RECEIVED, message));
            } catch (Throwable t) {
                if(message instanceof Request && t instanceof RejectedExecutionException){
                    sendFeedback(channel, (Request) message, t);
                    return;
                }
                throw new ExecutionException(message, channel, getClass() + " error when process received event .", t);
            }
        }
    

    我们可以看到在AllChannelHandler 通过线程池来处理接受到的读取任务,线程池的Runnable任务参数接收的是AllChannelHandler包装好的Handler(即DecodeHandler)用于将消息向后传递进行下一步处理,很明显下一步是对消息进行解码。getPreferredExecutorService根据是否是异步任务决定是否采用单独的线程池还是共享线程池处理任务。

    既然说到这里,那么AllChannelHandler是怎么触发的呢?

    让我们再次回到NettyServer,其实NettyServer其实也是一个Handler,而且它也有自己的包装类,那就是NettyHandler,NettyHanlder是一个Netty的自定义管道扩展类,继承自SimpleChannelHandler,对于Netty来说3.X版本和4.X版本在自定义管道的接口有些差异,3.X版本的读取消息方法名叫messageReceived,而4.X的方法名叫channelRead,这个不是重点,重点是当netty采用NIO线程模型接受到远程发送过来的消息后会触发自定义管理处理器的读取方法,处理流程从NettyHandler开始包装类传递依次从NettyServer到MultiMessageHandler,再到HeartbeatHandler,最后到AllChannelHanlder,我们可以看到在所有Handler中只有AllChannelHandler涉及到线程的任务调度。AllChannelHandler只是其中的一种线程调度策略,其实还有其他策略,比如:DirectChannelHandler,MessageOnlyChannelHandler,ConnectionOrderedChannelHandler。

    最后来总结一下,dubbo作为一款高性能的RPC框架在解决高性能问题的时候也离不开线程池,dubbo同时提供了多种协议可供开发人员选择,netty作为dubbo传输层的首选,但不是唯一,它提供灵活的SPI配置方式,借鉴了TCP/IP的分层模式,同时形似而不是神似netty的管道设计采用了洋葱模型,以netty为例,dubbo的管道传播路径如下:
    NettyHandler-> NettyServer->MultiMessageHandler->HeartbeatHandler->AllChannelHandler->DecodeHandler->HeaderExchangeHandler->requestHandler。

    相关文章

      网友评论

          本文标题:Dubbo的线程模型

          本文链接:https://www.haomeiwen.com/subject/erxblrtx.html