Dubbo篇:服务端请求响应与消费端异步写回结果源码分析



概述



         上文消费端服务调用中描述了发起一次远程调用的调用链,解析到了触发了Netty的outBound写事件writeAndFlush,将请求编码发送,但一次远程调用其实并没有真正完成,完整的一次远程调用还应包括接受服务端返回数据,将其返回给业务方。本文会继续上文做余下工作的解析。

         在服务端,据前面对Netty的分析可知,NioEventLoop会监听OP_READ事件,收到OP_READ事件之后,会对其进行处理,然后触发ChannelRead入站事件,在pipiline中传播,会先经过解码handler做解码操作,然后会传播到NettyServerHandler做真正的逻辑处理。

         消费端的接收响应结果实践逻辑与服务端大致相同,而且没有服务端的请求响应过程甚至更为简单一些,下文会分开介绍。



服务端请求响应源码分析



         服务端的请求响应源码分析从NettyServerHandler#channelRead方法开始,代码实现如下:

Dubbo篇:服务端请求响应与消费端异步写回结果源码分析_第1张图片
         此处实现较为简单,主要是获取NettyChannel对象,此处的handler为NettyServer,跟进会进入AbstractPeer#received,然后进入MultiMessageHandler#received,因为这两个方法实现非常简单,就不再代码展开,直接跟进到HeartbeatHandle#received,代码实现如下:

Dubbo篇:服务端请求响应与消费端异步写回结果源码分析_第2张图片

         此处主要是对心跳请求的处理,心跳请求则新建response设置requestid和心跳返回事件,将其返回,如果是心跳响应直接return。如果是正常远程调用则进入AllChannelHandler#received方法,实现代码如下:

Dubbo篇:服务端请求响应与消费端异步写回结果源码分析_第3张图片

         前面的操作都是I/O线程处理,此处将派发到业务线程池去执行,因为I/O线程数量有限,因此比较耗时的业务操作一般交由业务线程池处理,以免阻塞I/O线程。

         Dubbo中提供了5种线程派发策略(all,direct,message,execution,connection),来决定哪些操作线程由业务线程池处理,哪些操作由I/O线程处理。具体规则如下:

             all 所有消息都派发到线程池,包括请求,响应,连接事件,断开事件,心跳等。
             direct 所有消息都不派发到线程池,全部在 IO 线程上直接执行。
             message 只有请求响应消息派发到线程池,其它连接断开事件,心跳等消息,直接在 IO 线程上执行。
             execution 只请求消息派发到线程池,不含响应,响应和其它连接断开事件,心跳等消息,直接在 IO 线程上执行。
             connection 在 IO 线程上,将连接断开事件放入队列,有序逐个执行,其它消息派发到线程池。


         继续往下跟进,就要进入业务线程,代码跟踪到ChannelEventRunnable#run方法,代码实现如下:

Dubbo篇:服务端请求响应与消费端异步写回结果源码分析_第4张图片

         在上面新建ChannelEventRunnable对象时写入了RECIVED状态,所以此处进入DecodeHandler#received,实现代码如下:

Dubbo篇:服务端请求响应与消费端异步写回结果源码分析_第5张图片

         消息解码操作,因为此前已经在解码handler中做过解码操作,所以此处不会执行具体解码逻辑,直接跳过,继续跟进会进入HeaderExchangeHandler#received方法,代码实现如下:

Dubbo篇:服务端请求响应与消费端异步写回结果源码分析_第6张图片

         主要对消息类型以及双向还是单向进行分类处理,因为此处是响应消费端的请求,所以进入handleRequest方法,实现代码如下:

Dubbo篇:服务端请求响应与消费端异步写回结果源码分析_第7张图片

         其实到了这个地方就能很清楚的看出服务端响应请求的整体逻辑,构造response,处理消费端发来的请求,将结果回写到response中,然后调用channel.send()发送给消费端,发送消息在消费端已经分析过一次,所以这里看到了这个channel.send基本可以盲猜消费端也有一个handler重写了channelRead方法,等待处理服务端返回的结果。

         对handler.reply方法作展开,因为此处是Dubbo协议,所以跟进到DubboProtocol#reply方法,代码实现如下:

Dubbo篇:服务端请求响应与消费端异步写回结果源码分析_第8张图片

         前面在分析ServiceBean服务暴露过程中描述过,最终会将服务Invoker转换根据对应协议转换成exporter放到exporterMap中,此处就是根据消费发来的请求消息取出,执行其invoke方法。

         执行invoke方法之后也会触发拦截器链,这里不再展开描述,直接跟进到最后的AbstractProxyInvoker#invoke方法,实现代码如下:

Dubbo篇:服务端请求响应与消费端异步写回结果源码分析_第9张图片

         到了这里终于到了执行我们自己写的service的时候,doInvoke方法默认调用的JavassistProxyFactory返回的AbstractProxyInvoker中重写的doInvoke方法,这里贴一下代码:

Dubbo篇:服务端请求响应与消费端异步写回结果源码分析_第10张图片

         这里就看到了invokeMethod方法,获得结果后,调用wrapWithFuture方法获取future对象,这里主要对异步调用做处理,如果是异步则获取异步Future对象,如果不是这直接以执行结果作为result创建一个完成的future,然后是回调构建异步结果对象asyncRpcResult,然后执行返回以及回调,最终数据发送给消费端,同样是触发writeAndFlush出站写事件。



消费端异步回写响应结果



         上文中我们盲猜过,消费端肯定有一个handler重写了channelRead方法用于接收服务端的响应结果,所以,进入NettyClientHandler中,就看到了盲猜的结果,代码如下:

Dubbo篇:服务端请求响应与消费端异步写回结果源码分析_第11张图片

         看上去和服务端的长得一样,其实,不光长得一样,后续流程也都一样,一直到HeaderExchangeHandler#received方法,进入handleResponse方法,才开始实现其写回结果的逻辑,再贴一遍HeaderExchangeHandler#received方法,代码如下:

Dubbo篇:服务端请求响应与消费端异步写回结果源码分析_第12张图片

         此处进入了handleResponse方法,代码实现如下:

在这里插入图片描述
         可以看到直接调用了DefaultFuture#received方法,具体实现如下:

Dubbo篇:服务端请求响应与消费端异步写回结果源码分析_第13张图片

         此处会根据消费端传过来的requestId从保存future对象的map中获取对应的future,设置结果并标记完成,此处再贴一下doReceived方法的实现,代码如下:

Dubbo篇:服务端请求响应与消费端异步写回结果源码分析_第14张图片

         根据返回结果状态,标记future对象的完成。上篇文章中调用get方法阻塞等带返回结果的就可以继续往下执行了。

         到这里才算完成了一次完整的远程调用。



你可能感兴趣的:(Dubbo篇)