书接上文,关于Dubbo,真心好用,真心强大!但是人红是非多,偶尔还能听到关于Dubbo的各种坑的说法。
比如,消费方启动报错:Failed to check the status of the service xxx. No provider available for the service。。。
再比如,消费方启动成功后,但一直与提供方重连报错:Fail to connect to HeaderExchangeClient。。。
你真的以为这是Dubbo的坑吗?
加一个小插曲,我想做一下调查:当你遇到技术问题时你会怎么做?
本文就以这两个Dubbo常见问题入手,带你进入源码分析如何排查问题 !一起拉开有趣的程序人生,Let’s go!
在消费方启动时,可能会遇到以下两种场景:
不管怎么样,得先让服务启动起来吧~~~
那么,消费方启动失败,会抛出 IllegalStateException
,报错信息大致如下:
Error creating bean with name ‘xxxBean’: Injection of @DubboReference dependencies is failed; nested exception is java.lang.IllegalStateException:
Failed to check the status of the service
xxxInterfaceName.No provider available for the service
xxxInterfaceName from the url xxxUrl to the consumer xxxLocalhost use dubbo version xxxVersion
例如,我未启动提供者,重现的报错截图如下:
搜索报错信息Failed to check the status of the service
,可以快速定位到报错的源码,如下图:
看到if
判断条件 shouldCheck()
方法了吧? 从命名就可以看出来,这是判断是否应该检查,进去看看:
没有几行代码,我想你能想到:
① :isCheck() 没有配置,默认为null
② :由于① 为null,所以主要看getConsumer()
③ :如果① 和②均没配置,第3步默认设为true,即检查是否可用!
所以,对于我们来说,主要可配置点在 getConsumer().isCheck() ,
即ConsumerConfig类的isCheck()方法
:
public Boolean isCheck() {
return check;
}
ConsumerConfig
类,从名子可以看出来:消费者配置类.
稍微找一找就可以找到,在@EnableDubbo
注解上有一个@EnableDubboConfig
注解,里面注释写着:
再到注释对应的源码里验证一下,如果你从@EnableDubboConfig
上的DubboConfigConfigurationRegistrar
进去,可以看到DubboConfigConfiguration
:
再跟进去,从这里就可以找到对应的源码:ConsumerConfig
类绑定的配置前缀为dubbo.consumer
,如下图:
综上,配置dubbo.consumer.check = false
就代表消费方启动时不检查提供方是否可用!
dubbo.consumer.check = false
这里补充说明如下: Dubbo服务消费方在启动时,缺省会检查依赖的服务提供方是否可用,不可用时会抛出异常,阻止 Spring 初始化完成,以便上线时,能及早发现问题,默认
check=true
。 可以通过check=false
关闭检查,比如:测试时,有些服务不关心,或者出现了循环依赖,必须有一方先启动。
当通过check=false
启动消费方后,还可能会遇到以下两种场景:
如果对报错的提供方 不关心,就真的不想看到一直重连的报错!
消费方启动成功,但与提供方连接失败,会一直报错并抛出 RemotingException
,报错信息大致如下:
header.ReconnectTimerTask : [DUBBO] Fail to connect to HeaderExchangeClient [channel=org.apache.dubbo.remoting.transport.netty4.NettyClient [xxx -> /xxx:20880]], dubbo version: xxxVersion, current host: xxxLocalhost org.apache.dubbo.remoting.RemotingException: client(url: xxxURL) failed to connect to server /xxx:20880 client-side timeout 3000ms (elapsed: 3012ms) from netty client xxx using dubbo version xxxVersion
例如,我这里消费方在本地环境,提供方在DEV环境,重现的报错截图如下:
全局
或者直接到报错的ReconnectTimerTask
类中搜索报错信息Fail to connect to
,可以快速定位到报错的源码,如下:
打印的e
根据报错信息,可以确定是这里:
ReconnectTimerTask
,从名子就可以看出来:是重连的定时任务,所以,如果想不让它不报错,就需要看看是否可以不启动这个定时器,这样自然就不会打印ERROR
了,是这个逻辑吧?
OK,那我们得先找到启动定时器的地方,怎么找?
对,先查找一下ReconnectTimerTask
的类的引用,很快就定位到了HeaderExchangeClient.startReconnectTask(URL url)
方法,看名子就知道:开始重连任务。
OK,到这我不说你应该也发现了,这里有个if
判断条件shouldReconnect(url)
方法,和第一个问题的shouldCheck
都是统一命名规则,想都不用想,可以肯定就在这里控制它!
我们看一下,代码就一行,url.getParameter
内部是从Map
中查找reconnect
,找不到默认会设为true:
String RECONNECT_KEY = "reconnect";
private boolean shouldReconnect(URL url) {
return url.getParameter(Constants.RECONNECT_KEY, true);
}
那么问题来了, reconnect参数在哪配置?
这里向上查找引用的话,链路有点深,所以为了看的更清晰,我们可以打个断点,看下调用堆栈。
这样,一下就找到了入口:ReferenceConfig.get
方法,这里代码更少,主要就是调用init()
方法。
转到init()
方法的313行,传入的正是一个map
,如下图,调试发现里面竟然有check=false
,所以猜测还是与ConsumerConfig(消费者配置类)
有关,方里里再向上找一找,还真找到了ConsumerConfig
类的对象consumer
,从名子appendParameters
就知道它是往map里追加consumer的配置!所以就这样配置上了!
所以我们在dubbo.consumer
下面配一下试试,
dubbo.consumer.reconnect = false
不出所料,生效了!如下图
综上,配置dubbo.consumer.reconnect = false
就代表消费方不重连提供方!
实际上,这里有一个机制,就是Dubbo的重连机制,也是为了能及早发现问题,所以生产环境建议不要修改此配置!
而这个配置多用于开发环境,用于忽略不关心的服务!
那么,对于关心的服务,需要调用的话,怎么做?
可以考虑以下两种做法:
可以通过配置Nacos服务发现group隔离服务注册,例如:
spring.cloud.nacos.discovery.group=XXX_GROUP
这样,就可以做到本地环境只调本地服务,DEV环境只调DEV服务,只需要配置相同组名即可!
因为调用链路可能会错综复杂,有时环境隔离成本太高,这时就可以转为HTTP请求,对Nginx或Gateway发起HTTP调用,这也是对上述两种场景RPC调用异常的兜底方案!
Java的开源框架,我们有时可以不用跟的太深,一样可以快速搞定一些问题!
虽然框架源码一直在变,但方法万变不离其宗,套路都是相通的,你更值得学习的是排查问题的方法,更重要的是养成独立解决问题的习惯,相信你可以做到!
如果你学会了排查问题的方法,那么你以后就会很独立,也会被你的领导和同事看到你有两把刷子,说不准还能成为疑难杂症专家!即使你在遇到难题问了师傅,他也会觉得你问的有水平,他也会为帮你解决这个问题,漏了一手而沾沾自喜!
所以,遇到问题不是坏事, 多解决问题,问题会让你的经验越来越丰富,也会让你对吃饭的框架越来越熟悉,这些都是你的财富,也可以写进简历让面试官更喜欢你。不过友情提醒,尺度把控好,项目中遇到问题切勿死磕,切勿陷入细节影响项目进度!
谢谢大家的支持,我们下文见!