我发现一个关于Dubbo服务调用的一个BUG
点击下面图片直达文章合集
大家好,我是威哥,《RocketMQ技术内幕》作者、RocketMQ社区首席布道师、中通快递基础架构资深架构师,越努力越幸运,唯有坚持不懈,与大家共勉。
我在使用Dubbo的过程中遇到了如下错误:No provider available for the service,本以为这个是服务提供者不存在,但经过我的排查,别的客户端是能够正确使用该服务的,原来背后蕴含Dubbo一个BUG。
理论知识
结合我对Dubbo的理解,通常dubbo调用出现 No provider available for the service xxx,其原因通常如下:
1.服务方未启动
2.代码内客户端和服务端的group、version不匹配
3.有dubbo tag路由过滤,标签不匹配
4.动态配置过滤,没有匹配的服务(比如disable等)
但这次遇到一个非以上问题,因此研究了一番,发现了dubbo在实现上有一些瑕疵。
背景
在做JT808协议指令数据上行指令,指令通过808采集平台(netty长连接),解析后,通过dubbo调用服务,做指令的业务逻辑处理,奇怪是服务存在,但是却报错No provider available for the service com.xxx.ioc.api.service.JTService,错误截图如下:
分析过程
调用链路根据之前自己分析的dubbo transport层记录,dubbo客户端调用时序图如下(可以参考链接的泳道图):
dubbo客户端的调用的基本流程说明如下:
-
客户端经netty pipeline的TailContext处理,业务线程切换到reactor IO线程,业务线程在DefaultFuture.get()阻塞等待响应。 -
dubbo的编码/解码是在reactor IO线程处理,编码抛出异常,消息不会发送给服务方,此时异常(错误码BAD_REQUEST)被被封装为Response,继而唤醒业务线程在DefaultFuture.get()阻塞等待。
这里有个疑问,编码失败,那么是如何返回响应消息的呢?后面下篇文章分析),在执行com.alibaba.dubbo.remoting.exchange.support.DefaultFuture#returnFromResponse
,把异常信息封装为RemotingException进行抛出,代码如下:
private Object returnFromResponse() throws RemotingException {
Response res = response;
if (res == null) {
throw new IllegalStateException("response cannot be null");
}
if (res.getStatus() == Response.OK) {
return res.getResult();
}
if (res.getStatus() == Response.CLIENT_TIMEOUT || res.getStatus() == Response.SERVER_TIMEOUT) {
throw new TimeoutException(res.getStatus() == Response.SERVER_TIMEOUT, channel, res.getErrorMessage());
}
throw new RemotingException(channel, res.getErrorMessage());//序列化异常(错误码BAD_REQUEST)被封装为RemotingException向上抛出
}
重点关注一下调用链中的异常处理:
-
在 com.alibaba.dubbo.rpc.protocol.dubbo.DubboInvoker#doInvoke
内异常被catch,异常信息被封装为RpcException(异常code=NETWORK_EXCEPTION)向上抛。 -
接着在 com.alibaba.dubbo.rpc.protocol.AbstractInvoker#invoke
内异常RpcException被catch,由于异常code=NETWORK_EXCEPTION,非业务异常代码,因此异常继续向上抛 -
最后异常RpcException在 com.alibaba.dubbo.rpc.cluster.support.FailoverClusterInvoker#doInvoke
内被catch,由于是failover失效转移策略默认重试2次,因此接着尝试去调用调用其它节点,如果服务的节点数少于重试的次数+1(即3次),则没有匹配的服务节点,因此在com.alibaba.dubbo.rpc.cluster.support.AbstractClusterInvoker#checkInvokers
操作内,会报错No provider available for the service xxx
详细的代码调用截图如下所示:
因此就淹没了序列化异常,导致真正的异常失真。这也是dubbo错误提示的一点小问题,如果要修复,解决方法也简单:FailoverClusterInvoker新增如下方法
private void checkInvokers(List<Invoker<T>> invokers, Invocation invocation, RpcException le) {
if (invokers == null || invokers.isEmpty()) {
if (le != null) {
throw le;
}
checkInvokers(invokers, invocation);//请求父类
}
}
同时修改方法doInvoke如下
最后说一句(求关注,别白嫖我)
本文系读者朋友投稿,以下是作者信息:
张永杰:中通科技中间件团队高级高级工程师,在Dubbo、RocketMQ等领域拥有丰富的实战经验。
如果这篇文章对您有所帮助,或者有所启发的话,帮忙扫描下发二维码关注一下,您的支持是我坚持写作最大的动力。
求一键三连:点赞、转发、在看。
走进作者
点击查看“阅读原文”,可直接进入[中间件兴趣圈]文章合集。