1.   与Netty整合后的线程模型分析

本文是对Dubbo的学习笔记,由于Dubbo功能比较庞大,这里重点介绍DubboNetty整合部分。目的是搞懂一个Rpc请求从ConsumerProvider调用链中所涉及到的线程模型,因为我认为微服务中RPC最关键的无非三点:

1.     数据协议

2.     传输通道

3.     线程模型

弄懂线程模型是调优的关键一环,DubboDispatcher就是设置线程模型的

·        all 所有消息都派发到线程池,包括请求,响应,连接事件,断开事件,心跳等。

·        direct 所有消息都不派发到线程池,全部在IO线程上直接执行。

·        message 只有请求响应消息派发到线程池,其它连接断开事件,心跳等消息,直接在IO线程上执行。

·        execution 只请求消息派发到线程池,不含响应,响应和其它连接断开事件,心跳等消息,直接在IO线程上执行。

·        connection 在IO线程上,将连接断开事件放入队列,有序逐个执行,其它消息派发到线程池。

看懂DubboNetty整合也是需要前提条件的,比如

1.     Java线程池有一定了解

2.     了解Nio的编写

3.     Netty NioEventLoopPipeline有一定了解,Netty主要还是对Java原生Nio进行封装,提供了Reactor线程模型,事件Pipeline,内存池等功能。

1.2.   框架设计

http://dubbo.apache.org/#/docs/dev/design.md?lang=zh-cn

目标是看懂这张图

 Dubbo学习笔记-与Netty整合后的线程模型分析_dubbo netty 线程模型 微服务

1.3.   领域模型

Dubbo 的核心领域模型中:

  • Protocol 是服务域,它是 Invoker 暴露和引用的主功能入口,它负责 Invoker 的生命周期管理。

  • Invoker 是实体域,它是 Dubbo 的核心模型,其它模型都向它靠扰,或转换成它,它代表一个可执行体,可向它发起 invoke 调用,它有可能是一个本地的实现,也可能是一个远程的实现,也可能一个集群实现。

  • Invocation 是会话域,它持有调用过程中的变量,比如方法名,参数等。

 

1.4.   特色

Ø  透明化RPC调用

Ø  实现自己的AOP/IOC

Ø  服务治理的特性

Ø  模块分层,接口清晰

Ø  面向对象:高内聚低耦合

 

1.5.   核心机制【如果已经了解可以跳过】

1.5.1.  设计模式

Ø  装饰器+责任链模式

Provider提供的调用链为例,具体的调用链代码是在ProtocolFilterWrapperbuildInvokerChain完成的,filter可以给原有功能增加装饰(装饰器),也可以决定请求是否在链上继续传递(责任链)。

Ø  观察者模式: Listener

Ø  动态代理:ExtensionLoaderAdaptive就典型的动态代理

 

1.5.2.  ExtensionLoader机制【关键点】

扩展点机制有几个要点:

SPI+Adaptive原理 https://my.oschina.net/ywbrj042/blog/688042

 

1 根据关键字去读取配置文件,获得具体的实现类;

比如在dubbo-demo-provider.xml文件中配置:

<dubbo:service protocol="rmi" interface="com.alibaba.dubbo.demo.DemoService"

ref="demoService" /> 则会根据rmi去读取具体的协议实现类RmiProtocol.java

2 注解@SPI@Adaptive

@SPI注解:可以认为是定义默认实现类;

比如Protocol接口中,定义默认协议时dubbo

@SPI("dubbo")

public interface Protocol {}

 

@Adaptive注解:

该注解打在接口方法上;ExtensionLoader.getAdaptiveExtension()获取设配类,会先通过前面的过程生成java的源代码,在通过编译器编译成class加载。但是Compiler的实现策略选择也是通过ExtensionLoader.getAdaptiveExtension(),如果也通过编译器编译成class文件那岂不是要死循环下去了吗?

该注解打在接口类上;所以对于有实现类上去打了注解@Adaptivedubbo spi扩展机制,它获取设配类不在通过前面过程生成设配类java源代码,而是使用注解@Adaptive就把这个类作为设配类缓存在ExtensionLoader中,调用是直接返回。

 AdaptiveComiler @Adaptive注解在 Class 类上

Dubbo学习笔记-与Netty整合后的线程模型分析_dubbo netty 线程模型 微服务_02

3 filterlistener

在生成具体的实现类对象时,不是直接读取类文件,而是在读取类文件的基础上,通过filterlistener去封装类对象;

Dubbo学习笔记-与Netty整合后的线程模型分析_dubbo netty 线程模型 微服务_03

比如:ProtocolFilterWrapper

 

1.5.3.  DI注入【关键,很多wrapper都是DI做的】

refer引用为例

 

1.5.4.  Refer过程

1.     ReferenceConfig.get会调用 Protocol.refer 返回 Invoke 对象。

2.     通过ProxyFactory.getProxy 返回 Refer T类型的代理对象实现透明化调用。

代理对象的所有方法都是调用 InvokerInvocationHandlerJavassistProxyFactory为例). Invoke方法,把用户请求转换为 RpcInvocation对象。

 

Netty3的 NettyClient代码

Dubbo学习笔记-与Netty整合后的线程模型分析_dubbo netty 线程模型 微服务_04


Netty4 NettyClient代码,和netty3差别在于netty4线程模型hander变了,所有使用了新的  NettyClientHander,除TCP报文编解码外,主要逻辑还是在DubboChannelHandler中。

Dubbo学习笔记-与Netty整合后的线程模型分析_dubbo netty 线程模型 微服务_05

 

1.5.5.  编解码

NettyClient为例,

Dubbo学习笔记-与Netty整合后的线程模型分析_dubbo netty 线程模型 微服务_06

 

最终负责编解码的是 Codec2 SPI接口,默认为DubboCodec实现,decode方法调用的 DubboCodec父类 ExchangeCodec 的方法。

Dubbo学习笔记-与Netty整合后的线程模型分析_dubbo netty 线程模型 微服务_07

读取头信息,最后会回调  DubboCodec.decodeBody 方法

Dubbo学习笔记-与Netty整合后的线程模型分析_dubbo netty 线程模型 微服务_08

 

 

DubboCodec extends ExchangeCodec encodedecode中看出,传输层中的 Presentation数据就是

org.apache.dubbo.remoting.exchange.Request,内部成员变量mData就是RpcInvocation对象

org.apache.dubbo.remoting.exchange.Response,内部成员变量mResult就是RpcResult对象

Dubbo学习笔记-与Netty整合后的线程模型分析_dubbo netty 线程模型 微服务_09

 

1.5.6.  Dubbo ChannelHander Pipeline

Dubbo学习笔记-与Netty整合后的线程模型分析_dubbo netty 线程模型 微服务_10

public ExchangeClient connect(URL url, ExchangeHandler handler) throws RemotingException {

        return new HeaderExchangeClient(Transporters.connect(url, new DecodeHandler(new HeaderExchangeHandler(handler))), true);

}

 

标黄的是dubbo封装的ChannelHandel, Transporters.connect会调用 NettyTransporter.connect() à new NettyServer(url, listener);

 

关键点 wrapChannelHandler会再次对上面标黄的 DecodeHandler再次装饰

public NettyClient(final URL url, final ChannelHandler handler) throws RemotingException {

        super(url, wrapChannelHandler(url, handler));

}

 

protected ChannelHandler wrapInternal(ChannelHandler handler, URL url) {

        return new MultiMessageHandler(new HeartbeatHandler(ExtensionLoader.getExtensionLoader(Dispatcher.class)

                .getAdaptiveExtension().dispatch(handler, url)));

    }

 

 

 

Netty + Dubbo封装的ChannelHandler的顺序是:

MultiMessageHandlerà

HeartbeatHandlerà

DispatcherHandler(默认 AllChannelHandler)à

 

DecodeHandler à 不知道作用

HeaderExchangeHandler à 同步转异步

DubboProtocol

Dubbo学习笔记-与Netty整合后的线程模型分析_dubbo netty 线程模型 微服务_11

1.6.   DubboNetty整合【重点】

1.6.1.  Netty Handler Dubbo Handler 之间的桥梁 NettyClientHandler

Dubbopipeline

MultiMessageHandlerà 处理received MultiMessage

HeartbeatHandlerà 处理心跳

Dispatcher (默认 AllChannelHandler )à NioEventLoop IO线程分发到Dubbo线程池

 

DecodeHandler à 不知道作用,没什么代码

HeaderExchangeHandler à  关键的received方法,在channelRead时触发,在client处理response请求(notify DefaultFuture),在server端处理reqeust请求。

DubboProtocol

 

Dispatcher 的事件类型

  public enum ChannelState {

        CONNECTED,

        DISCONNECTED,

        SENT,

        RECEIVED,

        CAUGHT

    }

 

 

1.6.2.  Consumer发送Rpc Request请求

1.6.2.1.  一:业务线程的流程

channel.writeAndFlush触发就正式进入Netty API

netty在处理IO请求时大致调用链

1.     channel发起io请求

2.     channel交给ChannelPipeline处理

3.     channelPipeline调用tailContext

4.     tailContext发起Outbound事件在pipeline上传递到HeadContext处理。

Dubbo学习笔记-与Netty整合后的线程模型分析_dubbo netty 线程模型 微服务_12

Thread [main] (Suspended (breakpoint at line 101 in NettyChannel))

       NettyChannel.send(Object, boolean) line: 101  

       NettyClient(AbstractClient).send(Object, boolean) line: 265 

       NettyClient(AbstractPeer).send(Object) line: 53

       HeaderExchangeChannel.request(Object, int) line: 116  

       HeaderExchangeClient.request(Object, int) line: 90 

       ReferenceCountExchangeClient.request(Object, int) line: 83 

       DubboInvoker<T>.doInvoke(Invocation) line: 108    

       DubboInvoker<T>(AbstractInvoker<T>).invoke(Invocation) line: 154  

       ListenerInvokerWrapper<T>.invoke(Invocation) line: 77 

       MonitorFilter.invoke(Invoker<?>, Invocation) line: 75     

       ProtocolFilterWrapper$1.invoke(Invocation) line: 72      

       FutureFilter.invoke(Invoker<?>, Invocation) line: 47 

       ProtocolFilterWrapper$1.invoke(Invocation) line: 72      

       ConsumerContextFilter.invoke(Invoker<?>, Invocation) line: 50   

       ProtocolFilterWrapper$1.invoke(Invocation) line: 72      

      RegistryDirectory$InvokerDelegate<T>(InvokerWrapper<T>).invoke(Invocation) line: 56  

       FailoverClusterInvoker<T>.doInvoke(Invocation, List<Invoker<T>>, LoadBalance) line: 78 

       FailoverClusterInvoker<T>(AbstractClusterInvoker<T>).invoke(Invocation) line: 234

       MockClusterInvoker<T>.invoke(Invocation) line: 75 

       InvokerInvocationHandler.invoke(Object, Method, Object[]) line: 70  

       proxy0.sayHello(String) line: not available 

       Consumer.main(String[]) line: 35  

 

业务线程进入NioEventLoop线程

 

private static void safeExecute(EventExecutor executor, Runnable runnable, ChannelPromise promise, Object msg) {

        try {

            //加入NioEventLoop任务队列

            executor.execute(runnable);

        } catch (Throwable cause) {

            try {

                promise.setFailure(cause);

            } finally {

                if (msg != null) {

                    ReferenceCountUtil.release(msg);

                }

            }

        }

}

 

堆栈信息:

org.apache.dubbo.demo.consumer.Consumer at localhost:55727      

       Thread [main] (Suspended)【还是业务线程】

              AbstractChannelHandlerContext.safeExecute(EventExecutor, Runnable, ChannelPromise, Object) line: 1007【添加TaskEventLopp的队列】

             DefaultChannelPipeline$TailContext(AbstractChannelHandlerContext).write(Object, boolean, ChannelPromise) line: 825    

        DefaultChannelPipeline$TailContext(AbstractChannelHandlerContext).writeAndFlush(Object, ChannelPromise) line: 794     

        DefaultChannelPipeline$TailContext(AbstractChannelHandlerContext).writeAndFlush(Object) line: 831

              DefaultChannelPipeline.writeAndFlush(Object) line: 1071    

              NioSocketChannel(AbstractChannel).writeAndFlush(Object) line: 300

              NettyChannel.send(Object, boolean) line: 101【上一步Dubbo业务线程】

              NettyClient(AbstractClient).send(Object, boolean) line: 265 

              NettyClient(AbstractPeer).send(Object) line: 53

              HeaderExchangeChannel.request(Object, int) line: 116  

              HeaderExchangeClient.request(Object, int) line: 90 

              ReferenceCountExchangeClient.request(Object, int) line: 83 

              DubboInvoker<T>.doInvoke(Invocation) line: 108    

              DubboInvoker<T>(AbstractInvoker<T>).invoke(Invocation) line: 154  

              ListenerInvokerWrapper<T>.invoke(Invocation) line: 77 

              MonitorFilter.invoke(Invoker<?>, Invocation) line: 75     

              ProtocolFilterWrapper$1.invoke(Invocation) line: 72      

              FutureFilter.invoke(Invoker<?>, Invocation) line: 47 

              ProtocolFilterWrapper$1.invoke(Invocation) line: 72      

              ConsumerContextFilter.invoke(Invoker<?>, Invocation) line: 50   

              ProtocolFilterWrapper$1.invoke(Invocation) line: 72      

             RegistryDirectory$InvokerDelegate<T>(InvokerWrapper<T>).invoke(Invocation) line: 56 

             FailoverClusterInvoker<T>.doInvoke(Invocation, List<Invoker<T>>, LoadBalance) line: 78 

              FailoverClusterInvoker<T>(AbstractClusterInvoker<T>).invoke(Invocation) line: 234

              MockClusterInvoker<T>.invoke(Invocation) line: 75 

              InvokerInvocationHandler.invoke(Object, Method, Object[]) line: 70  

              proxy0.sayHello(String) line: not available 

              Consumer.main(String[]) line: 35  

       Daemon Thread [DubboRegistryFailedRetryTimer-thread-1] (Running)     

       Daemon Thread [DubboMulticastRegistryReceiver] (Running)     

       Daemon Thread [DubboMulticastRegistryCleanTimer-thread-1] (Running)

       Daemon Thread [DubboSaveRegistryCache-thread-1] (Running) 

       Daemon Thread [DubboClientReconnectTimer-thread-1] (Running)  

       Daemon Thread [ObjectCleanerThread] (Running)  

       Daemon Thread [NettyClientWorker-1-1] (Running)

       Daemon Thread [DubboClientHandler-192.168.199.230:20880-thread-1] (Running)      

       Daemon Thread [dubbo-remoting-client-heartbeat-thread-1] (Running)  

       Daemon Thread [DubboResponseTimeoutScanTimer] (Running)

       Daemon Thread [DubboClientReconnectTimer-thread-2] (Running)  

业务线程阻塞: DefaultFuture.get();await

业务线程由 return (Result) currentClient.request(inv, timeout).get(); 而阻塞;

 

 

public Object get(int timeout) throws RemotingException {

        if (timeout <= 0) {

            timeout = Constants.DEFAULT_TIMEOUT;

        }

        if (!isDone()) {

            long start = System.currentTimeMillis();

            lock.lock();

            try {

                while (!isDone()) {

                    done.await(timeout, TimeUnit.MILLISECONDS);

                    if (isDone() || System.currentTimeMillis() - start > timeout) {

                        break;

                    }

                }

            } catch (InterruptedException e) {

                throw new RuntimeException(e);

            } finally {

                lock.unlock();

            }

            if (!isDone()) {

                throw new TimeoutException(sent > 0, channel, getTimeoutMessage(false));

            }

        }

        return returnFromResponse();

}

 

 

1.6.2.2.  二:Netty Event线程部分:NioEventLoop线程

这部分和之前看Netty时一样,关键点还是NettyClientChannel,该类是Pipeline上最接近 TailContextHandler,而NettyClientChannel内部持有ChannelHandlerdubbohandler】,所以这里可能触发到 Dubbo ChannelHandler Pipeline

比如上一个步骤中 write outbound事件触发

Dubbo学习笔记-与Netty整合后的线程模型分析_dubbo netty 线程模型 微服务_13

Dubbo pipeline 

 Dubbo学习笔记-与Netty整合后的线程模型分析_dubbo netty 线程模型 微服务_14

 

1.6.2.3.  三:Dubbo Dispatcher 线程池【然而并不会用到】

Netty NioEventLoop线程中运行到dubbo 提供的 NettyClientHandler.write方法时,super.write() 将任务继续传递,执行到 handler.send时进入 dubbo pipeline,开始想 AllChannelHandler会把task放线程池,然而并没有,AllChannelHandler没有重写 send方法,因为request请求发出去后 client并不需要做耗时操作,所以直接在EventLoop线程中做完了。

 

 

1.6.2.4.  问题1Netty HandlerDubbo Handler之间的执行顺序,是在一个线程中执行的吗?

 

 

 

1.6.3.  Provider 接受Request并处理

第一部分 Netty线程:

NettyClientHandler à MultiMessageHandler à Dispatcher (默认 AllChannelHandler )

 

第二部分: Dubbo Dispatcher 线程池

Server端接受到client发送的Request

HeaderExchangeHandler.received(Channel, Object) line: 195

Dubbo学习笔记-与Netty整合后的线程模型分析_dubbo netty 线程模型 微服务_15 Dubbo学习笔记-与Netty整合后的线程模型分析_dubbo netty 线程模型 微服务_16


堆栈信息:

Daemon Thread [DubboServerHandler-192.168.199.230:20880-thread-2] dubbo线程池】(Suspended (breakpoint at line 29 in DemoServiceImpl)) 

       DemoServiceImpl.sayHello(String) line: 29 

       Wrapper1.invokeMethod(Object, String, Class[], Object[]) line: not available  

       JavassistProxyFactory$1.doInvoke(T, String, Class<?>[], Object[]) line: 47  

       JavassistProxyFactory$1(AbstractProxyInvoker<T>).invoke(Invocation) line: 85

       DelegateProviderMetaDataInvoker<T>.invoke(Invocation) line: 52     

       RegistryProtocol$InvokerDelegete<T>(InvokerWrapper<T>).invoke(Invocation) line: 56  

       ExceptionFilter.invoke(Invoker<?>, Invocation) line: 63   

       ProtocolFilterWrapper$1.invoke(Invocation) line: 72      

       MonitorFilter.invoke(Invoker<?>, Invocation) line: 75     

       ProtocolFilterWrapper$1.invoke(Invocation) line: 72      

       TimeoutFilter.invoke(Invoker<?>, Invocation) line: 42     

       ProtocolFilterWrapper$1.invoke(Invocation) line: 72      

       TraceFilter.invoke(Invoker<?>, Invocation) line: 78   

       ProtocolFilterWrapper$1.invoke(Invocation) line: 72      

       ContextFilter.invoke(Invoker<?>, Invocation) line: 73      

       ProtocolFilterWrapper$1.invoke(Invocation) line: 72      

       GenericFilter.invoke(Invoker<?>, Invocation) line: 138    

       ProtocolFilterWrapper$1.invoke(Invocation) line: 72      

       ClassLoaderFilter.invoke(Invoker<?>, Invocation) line: 38

       ProtocolFilterWrapper$1.invoke(Invocation) line: 72      

       EchoFilter.invoke(Invoker<?>, Invocation) line: 38    

       ProtocolFilterWrapper$1.invoke(Invocation) line: 72      

       DubboProtocol$1.reply(ExchangeChannel, Object) line: 114 

       HeaderExchangeHandler.handleRequest(ExchangeChannel, Request) line: 99 

       HeaderExchangeHandler.received(Channel, Object) line: 195

       DecodeHandler.received(Channel, Object) line: 51  

       ChannelEventRunnable.run() line: 57  

       ThreadPoolExecutor.runWorker(ThreadPoolExecutor$Worker) line: 1149 

       ThreadPoolExecutor$Worker.run() line: 624      

       InternalThread(Thread).run() line: 748

 

1.6.4.  Provider 将处理结果返回给Consumer

Servicesendclient端的reqeust触发的send一样,进入NioEventLoopAllChannelHandler不会多线程处理send事件。

Dubbo学习笔记-与Netty整合后的线程模型分析_dubbo netty 线程模型 微服务_17

 

1.6.5.  Consumer接受到Provider返回的结果

1.6.5.1.  Netty Handler

EventLoop中的Selector监听到inbound事件,由headContext传递给pipeline,经过decodeHandlerNettyClientHandler.channelRead方法。

 

   @Override

    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {

        NettyChannel channel = NettyChannel.getOrAddChannel(ctx.channel(), url, handler);

        try {

           【调用dubbo handler

            handler.received(channel, msg);

        } finally {

            NettyChannel.removeChannelIfDisconnected(ctx.channel());

        }

}

 

 

 

1.6.5.2.  Dubbo Handler & notify 业务线程

Dubbo handler从线程模型来看分两部分

第一部分 Netty线程:

MultiMessageHandler à Dispatcher (默认 AllChannelHandler )

 

第二部分: Dubbo Dispatcher 线程池

DecodeHandler à HeaderExchangeHandler

Dubbo学习笔记-与Netty整合后的线程模型分析_dubbo netty 线程模型 微服务_18

    

Daemon Thread [DubboClientHandler-192.168.199.230:20880-thread-1] (Suspended (breakpoint at line 254 in DefaultFuture))  

       DefaultFuture.doReceived(Response) line: 254  notify唤醒 future

       DefaultFuture.received(Channel, Response) line: 97

       HeaderExchangeHandler.handleResponse(Channel, Response) line: 62     

       HeaderExchangeHandler.received(Channel, Object) line: 201

       DecodeHandler.received(Channel, Object) line: 51  

       ChannelEventRunnable.run() line: 57   dubbo类开始】

       ThreadPoolExecutor.runWorker(ThreadPoolExecutor$Worker) line: 1149 

       ThreadPoolExecutor$Worker.run() line: 624 [local variables unavailable]  

       InternalThread(Thread).run() line: 748 [local variables unavailable]   

 

1.7.   总结

通过对“DubboNetty整合”章节可以看出,整个请求中涉及到线程的地方就是两处,1.Dubbo管理的线程 2.Netty管理的线程池;知道线程池的作用后,就可以通过参数调整Dubbo的线程模型,或者线程数量来优化自己的应用啦。