- 前言
- 背景
- 过程
- 总结
记录下遇到的dubbo坑, No provider available for the service xxx
前言通常dubbo调用出现 No provider available for the service xxx,有以下几种情况:
1.服务方未启动
2.代码内客户端和服务端的group、version不匹配
3.有dubbo tag路由过滤,标签不匹配
4.动态配置过滤,没有匹配的服务(比如disable等)
但是最近遇到一个非以上问题,但是服务调用异常 No provider available for the service xxx
,很是奇怪,因此研究了一番,发现了dubbo此处的一点瑕疵。
在做JT808协议指令数据上行指令,指令通过808采集平台(netty长连接),解析后,通过dubbo调用服务,做指令的业务逻辑处理,奇怪是服务存在,但是却报错No provider available for the service com.xxx.ioc.api.service.JTService,错误截图如下:
很奇怪,服务明明是启动的,也没有动态配置,为什么服务竟然会很奇怪的找不到呢?然后debug下看了代码,发现是dubbo编码阶段报错io.netty.handler.codec.EncoderException: java.lang.RuntimeException: Serialized class com.xxx.ioc.codec.util.KeyValuePair must implement java.io.Serializable Java field: private com.xxx.ioc.codec.util.KeyValuePair com.xxx.ioc.protocol.t808.T0900.message
原来是参数T009的内部类KeyValuePair未实现序列化导致,但是如果是未实现序列化,应该报错Serialized class xxx must implement java.io.Serializable
的错误,但是为什么收到的错误却是No provider available for the service xxx
,带着这个问题,分析一波。
调用链路根据之前自己分析的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如下
如果上次调用结果异常le非null,且注册表RegistryDirectory没有对应服务,把上此调用异常抛出即可。
总结下这个问题发生的情况:
dubbo参数的属性对象未实现序列化,且服务节点数小于重试次数+1(默认总计为3次),则序列化异常会被吞了,出现的条件挺苛刻的,如果服务节点数大于3或者不重试,则该序列化异常就会被抛出,这个问题其实不影响什么,通常也是出现在开发测试阶段,生产估计没有,不修复也没什么关系,但是不修复,问题不太容易发现,就要帮助别人来解决这个问题了。
此外查看了其它集群策略failfast、failsafe、failback、broadcast,均没有这个问题。
总结总结No provider available for the service xxx出现的几种情况如下:
1.服务方未启动
2.客户端和服务方不在同一个注册中心
3.代码内客户端和服务端的group、version不匹配
4.dubbo tag路由标签不匹配
5.动态配置过滤(比如disable禁用此服务)
6.序列化问题且服务节点数小于3次,也会出现提示这个错误,实际上并不是
后续是否要给dubbo官方提个bug呢,看了下dubbo3.0的FailoverClusterInvoker代码,代码还是和2.6相同,官方并未发现这个小问题。