我找到了Dubbo源码的BUG,同事说我有点东西

简介: 云栖号资讯:【点击查看更多行业资讯】在这里您可以找到不同行业的第一手的上云资讯,还在等什么,快来! 背景 某天运营反馈,点了一次保存,但是后台出现了3条数据,我当时就想,不应该啊,这代码我几万年没动了,我当时就叫他先别操作了,保留一下现场,我去排查一下。

云栖号资讯:【点击查看更多行业资讯
在这里您可以找到不同行业的第一手的上云资讯,还在等什么,快来!


背景

某天运营反馈,点了一次保存,但是后台出现了3条数据,我当时就想,不应该啊,这代码我几万年没动了,我当时就叫他先别操作了,保留一下现场,我去排查一下。

我看了下新增的代码,直接右键查看作者

1

没想到三歪做过改动,我就去问三歪,XX模块的新增代码你是不是动过?

他沉默了很久没说话,然后抓起桌子上用剩下来的纸擦了擦鬓角留下的汗水,咽了一下口水说,是的我改过,我把之前dubbo的xml配置方式改成了注解的方式。

怎么了?现在出BUG了?

你呀你,下次这种改动跟我说一下,我估计是dubbo源码的bug吧,不要慌,让我去看看什么问题。

正文

其实dubbo配置的方式有很多种,大家用的最多的就是xml配置的方式,如果不需要重试次数,我们会加上重试次数为0,因为他默认是有多次的。

11

或者使用注解的方式

@Reference(retries =0) 

其实我已经大概知道是什么原因了,但是为了证实自己的猜想,于是开启了接下来的debug之旅~~~

注:dubbo版本:2.6.2

首先是在采用@Reference注解条件下:

2

采用@Reference注解配置重试次数

首先是都找到了dubbo重试的代码位置(启动dubbo项目,到调用接口时,F5进入方法,会跳转到InvokerInvocationHandler中的invoke方法中,继续跟踪进入MockClusterInvoker中的invoke方法,然后进入AbstractClusterInvoker中的invoke方法中,这里主要是拿到配置的负载均衡策略,后面会到FailoverClusterInvoker的doInvoke方法中)。

重点来了,这里会获取配置的retries值,可以看到上面配置的是0,但是取出来居然是null,如图:

3

value为null

所以会返回defaultValue,加上本身调用的那一次,计算之后就会为3,如图:

4

值为3

所以可以发现,采用@Reference注解的形式配置retries为0时,dubbo重试次数为2次(3中包含本身调用的那次)。

后面是采用 dubbo:reference 标签的方式:

5

dubbo标签方式

方式如上,在获取属性的时候,可以看到获得的值为0,和注解形式配置的一致,如图:

6

value为0

加上本身调用的那一次,计算之后就会为1,如图:

7

value为1

所以可以发现,采用 dubbo:reference 标签形式配置retries为0时,dubbo重试次数为0(1为本身调用的那次)。

原因分析

首先是@Reference注解形式:

dubbo会把每个接口先解析为ReferenceBean,加上ReferenceBean实现了FactoryBean接口,所以在注入的时候,会调用getObject方法,生成代理对象。

但是这不是关键,因为到这一步时,所有的属性都已经加载完成,所以需要找到dubbo解析注解中属性的代码位置。

dubbo会使用自定义驱动器ReferenceAnnotationBeanPostProcessor来注入属性,而具体执行注入的代码位置是在ReferenceAnnotationBeanPostProcessor类的postProcessPropertyValues方法中调用inject方法执行的。

重点来了,因为采用标签时,是采用@Autowired注解注入,所以是采用spring原生方式注入,而在采用@Reference注解时,注入时会走到dubbo自己的ReferenceAnnotationBeanPostProcessor中私有内部类ReferenceFieldElement的inject方法中,然后调用buildReferenceBean创建ReferenceBean。

离原因越来越近了,在该方法中可以看到beanBuilder中的retries值还是0,说明到这一步还没有被解析为null,如图:

8

retries为“0”

继续往下走,调用build方法中的configureBean时,在第一步preConfigureBean中方法,在该方法中会创建
AnnotationPropertyValuesAdapter对象,在该对象构造方法中会调用adapt方法,然后走到AnnotationUtils中的getAttributes方法中,有一个关键方法nullSafeEquals,该方法会传入当前属性值和默认值。

如果相等,则会忽略掉该属性,然后将符合条件的属性放入actualAttributes这个map中,而我们的retries属性是0,和默认值一致,所以map中不会保存retries属性的值,只有timeout属性,因此出现了后面获取的值为null。

注解方式debug告一段落。

9

map不包含retries

后面是dubbo:reference标签形式:

上面说到了,标签形式走到inject时,会和注解形式有所不同,采用该标签时,dubbo会使用自定义的名称空间解析器去解析,很容易理解,spring也不知道它自定义标签里面那些玩意儿是什么意思,所以dubbo会继承spring的。

NamespaceHandlerSupport,采用自定义的DubboNamespaceHandler解析器来解析的标签,如下图:

10

dubbo自定义名称空间解析器

然后调用该类中的parse方法进行解析,而解析retries的地方就是获取class(此时的class就是上图绿色标明的ReferenceBean的class,其父类中有好多好多set方法,其中就包含setRetries方法)中所有的方法,过滤出set开头的方法,然后切割出属性名,放入属性池中,可以看到此处解析出的值为0,并不为null,如下图:

11

获取属性名的位置

12

获取retries值为0

小结

画个简单图:

13

结论

  • 采用注解形式:不配置retries或者配置为0,都会重试两次,只有配置为 -1 或更小,才会不执行重试。
  • 采用标签形式:不配置retries会重试两次,配置为0或更小都不会重试。

所以建议大家不需要重试时可以设置为-1,比如增删改操作的接口,否则需要保证幂等性。需要重试则设置为1或更大,其实这应该算dubbo的一个dug吧?(我觉得是。。)

到这里就结束了,而上面说到的调用getObject方法就是后续服务发现以及和服务端建立长连接并返回代理对象了。

数据出现3条是因为我定义了接口超时的时间比较短,但是我们的新增涉及文件的操作,流程时间比较久,但是线程还是在的,所以dubbo重试了三次,三次也都是成功的了。

我后面把文件操作改成异步,然后主流程是同步的时间就缩短了很多。

补充:2.7.3版本已修复,就是在注解情况下,nullSafeEquals方法中的默认值和后面保持一致了,都是2,所以为0时也能保存到map中。

【云栖号在线课堂】每天都有产品技术专家分享!
课程地址:https://yqh.aliyun.com/live

立即加入社群,与专家面对面,及时了解课程最新动态!
【云栖号在线课堂 社群】https://c.tb.cn/F3.Z8gvnK

原文发布时间:2020-04-27
本文作者:敖丙
本文来自:“掘金”,了解相关信息可以关注“掘金”

相关文章
|
4月前
|
缓存 Dubbo Java
趁同事上厕所的时间,看完了 Dubbo SPI 的源码,瞬间觉得 JDK SPI 不香了
趁同事上厕所的时间,看完了 Dubbo SPI 的源码,瞬间觉得 JDK SPI 不香了
|
4月前
|
Dubbo Java 应用服务中间件
从源码全面解析 dubbo 服务端服务调用的来龙去脉
从源码全面解析 dubbo 服务端服务调用的来龙去脉
|
3月前
|
缓存 Dubbo Java
Dubbo 第三节_ Dubbo的可扩展机制SPI源码解析
Dubbo会对DubboProtocol对象进⾏依赖注⼊(也就是⾃动给属性赋值,属性的类型为⼀个接⼝,记为A接⼝),这个时候,对于Dubbo来说它并不知道该给这个属性赋什么值,换句话说,Dubbo并不知道在进⾏依赖注⼊时该找⼀个什么的的扩展点对象给这个属性,这时就会预先赋值⼀个A接⼝的⾃适应扩展点实例,也就是A接⼝的⼀个代理对象。在调⽤getExtension去获取⼀个扩展点实例后,会对实例进⾏缓存,下次再获取同样名字的扩展点实例时就会从缓存中拿了。Protocol是⼀个接。但是,不是只要在⽅法上加了。
|
3月前
|
Dubbo Java 应用服务中间件
微服务框架(十七)Dubbo协议及编码过程源码解析
  此系列文章将会描述Java框架Spring Boot、服务治理框架Dubbo、应用容器引擎Docker,及使用Spring Boot集成Dubbo、Mybatis等开源框架,其中穿插着Spring Boot中日志切面等技术的实现,然后通过gitlab-CI以持续集成为Docker镜像。   本文为Dubbo协议、线程模型、和其基于Netty的NIO异步通讯机制及源码
|
4月前
|
缓存 负载均衡 Dubbo
从源码全面解析 dubbo 消费端服务调用的来龙去脉
从源码全面解析 dubbo 消费端服务调用的来龙去脉
|
4月前
|
存储 Dubbo Java
从源码全面解析 dubbo 服务订阅的来龙去脉
从源码全面解析 dubbo 服务订阅的来龙去脉
|
4月前
|
Dubbo 数据可视化 Java
从源码全面解析 dubbo 服务暴露的来龙去脉
从源码全面解析 dubbo 服务暴露的来龙去脉
|
4月前
|
Dubbo Java 应用服务中间件
从源码全面解析 dubbo 注解配置的来龙去脉
从源码全面解析 dubbo 注解配置的来龙去脉
|
4月前
|
Dubbo 应用服务中间件 Nacos
bug篇之基于docker安装nacos(2.1.1)使用dubbo连接不上的问题
bug篇之基于docker安装nacos(2.1.1)使用dubbo连接不上的问题
|
6月前
|
存储 Dubbo Java
Dubbo第三讲:Dubbo的可扩展机制SPI源码解析
Dubbo第三讲:Dubbo的可扩展机制SPI源码解析

热门文章

最新文章