背景:
客户端调用dubbo服务端,dubbo服务端线程溢出,客户端无法收到响应,请求超时
前提:
建议打开netty3.7.0Final代码,dubbox2.8.4代码,边读边看代码。
关键信息:
类:
ChannelUpstreamHandler
请求消息流进NettyServer端(当前代码重点关注)
ChannelDownstreamHandler
NettyServer端响应返回(当前代码分析不涉及)
ChannelPipeline
NettyServer解析数据的职责链
ChannelHandler
消息处理器(AllHandler)
Sink
消息最后一步处理
netty事件
MessageEvent:普通请求消息事件
ExceptionEvent:异常事件
方法:
sendUpStream()
流入信息总入口
handleUpstream()
处理流入信息
received()
消息事件处理
caught()
异常事件消息
先分析下NettyServer的构造,此处为静态信息(信息还没有流入进来)
编解码channelHandler
NettyServer -> pipeline.addLast("decoder", adapter.getDecoder());
NettyServer -> pipeline.addLast("encoder", adapter.getEncoder());
核心处理业务channelHandler
final NettyHandler nettyHandler = new NettyHandler(this.getUrl(), this);
NettyServer -> pipeline.addLast("handler", nettyHandler);
将NettyHandler增加到nettyServer的职责链中,处理请求消息
Handler类继承关系:
NettyHandler ->SimpleChannelHandler -> ChannelUpstreamHandler(处理流入消息总接口)->ChannelHandler(顶级接口sendUpstream()方法)
NettyServer会按照pipline职责链执行每个ChannelHandler的sendUpstream()方法,ChannelUpstreamHandler实例的handleUpstream方法
所以此处 sendUpstream() 作为所有请求消息总入口,后面的分析均从此接口开始。
以下开始分析消息流入处理流程:
1.流入一个dubbo请求信息
DefaultChannelPipeline.sendUpstream()职责链驱动
核心代码在此,调用handler的handlerUpstream()(可以理解为reveived),如果received处理异常,则执行notifyHandlerException()
2.SimpleChannelHandler.handleUpstream()执行职责单元中的方法
3.消息类型为MessageEvent,交给messageReceived()方法处理, NettyHandler实现了messageReceived()方法,所以由NettyHandler的messageReceived()方法处理
NettyHandler.messageReceived(ctx, (MessageEvent)e);
4.messageReceived方法内调用handler.received()方法,
this.handler.received(channel, e.getMessage());
此时的handler使用装配器模式,已经变成了AllChannelHandler(主角出场了)
5.AllChannelHandler由于线程池溢出,导致执行失败,抛出异常
6.按调用链层层返回,则返回到DefaultChannelPipeline.sendUpstream()中,执行notifyHandlerException()方法
7.继续跟踪notifyHandlerException()方法
关注两个点:
1)当前事件还是MessageEvent,所以不走第一个if
2)调用sink.exceptionCaught()方法处理异常,此处的sink是谁呢????
8.回看一下NettyServer里边是否定义了sink??(sink是pipline中的最后一个元素)
好像没有定义sink????
我们再看下第一个红框中的代码,这里new了一个ChannelFactory,看看里边有什么玄机??
层层跟进,找到根构造方法,sink终于现身了 NioServerSocketPipelineSink,继续看 NioServerSocketPipelineSink中的exceptionCaught()方法中干了啥??
很失望,NioServerSocketPipelineSink没有实现exceptionCaught()方法,那就找父类,最终找到了实现了exceptionCaught()方法的实例 AbstractChannelSink
代码好多,继续深入,当前执行线程是iothreads
看一下这个if判断里做了啥???
AbstractNioChannelSink重写了AbstractChannelSink的该方法,此处该方法,应该返回false
现在代码执行到了fireExceptionCaught,继续跟踪
向管道里推了一个异常事件sendUpstream(异常事件),继续跟踪,代码执行和前面一些步骤一样,只是事件类型变更了,所以我们只看关键代码
跟踪到SimpleChannelHandler的handleUpstream方法,这次,走异常事件流程,执行exceptionCaught(异常事件)方法
同样,NettyHandler实现了exceptionCaught(异常事件)方法,代码如下
敲黑板,重点重点!!!,此时可以理解AllChannelHandler要执行caught方法了,截止到目前,终于跟踪到了AllChannelHandler第一次线程溢出异常,调用了自己的caught方法,基本找到了dubbo在何处调用caught方法的,本次分析任务就要结束了??
第二大段:
本着打破砂锅问到底的钻研精神,这个时候,如果dubbo处理了这个异常,那消费者应该会收到一个失败或者异常,消费者就会处理失败消息,但是现状为何是消费者因为没有收到响应而超时呢????
继续跟踪
此时,执行AllChannelHandler的caught方法,会不会有问题呢? 实际情况是,确实有问题,由于业务线程池满了,AllChannelHandler的caught方法继续执行失败。。啥?又抛异常了??!!!
咱们再次回到,sendUpstream()方法,看看系统如何处理二次异常??
巩固记忆,入口图,再贴一次
继续跟踪第二次异常处理流程
和第一次处理异常流程不一样了,此时的ChannelEvent为ExceptionEvent,所以打印了一句日志,然后就返回了,也就是不处理了。。
然后就没然后了,这个请求就这样被沉入大海了,客户端没有收到任何响应。。。
本次分析就彻底结束了,客户端超时,真相大白。