dubbo3+sleuth+brave实现链路追踪及traceId未传递或不对应的原因分析

问题场景

笔者之前一直使用SpringCloud Alibaba + dubbo2 + nacos1.4进行开发,但是目前naocs2、dubbo3也已经推出有一段时间并逐渐达到生产环境可用状态,所以笔者也希望用最新版本的nacos2及dubbo3尝尝鲜。但在搭建框架的过程中遇到了链路追踪的问题,所以在这里详细记录一下。

SpringCloud Alibaba + dubbo2 + nacos1

笔者基于dubbo2和nacos1的框架的依赖版本如下(这里只展示链路追踪相关依赖):

SpringCloud Alibaba 2.2.5.RELEASE
Springboot 2.3.7.RELEASE
Dubbo 2.7.8 
Nacos 1.4.3
spring-cloud-starter-sleuth 3.0.0
brave-instrumentation-dubbo 5.13.7

基于dubbo2和nacos1,该依赖版本网上都有较为成熟的搭建方案。直接引入依赖,按照度娘上的配置一下yml文件,就能实现链路追踪,配置过程不太困难。这里就不详细介绍了。

SpringCloud Alibaba + dubbo3 + nacos2

在搭建基于dubbo3和nacos2的框架时也希望能实现链路追踪。其中方案一和方案二是目前Dubbo3官方手册列举的实现方案。
方案一:Skywalking
听说是最简单的,但因为要额外部署Skywalking,所以我暂未尝试这种方案。
方案二:OpenTelemetry或brave
我根据dubbo3的文档进行依赖的引入和yml文件的配置,但是发现服务提供者日志正常写入了traceId和spanId,但服务消费者无论traceId还是spanId都没写入,这个在dubbo的GitHub issue中也未有人提问,所以最终也没找到解决方案。
方案三:sleuth+brave
我尝试了沿用SpringCloud Alibaba + dubbo2 + nacos1框架时的sleuth+brave方案实现链路追踪,但我引入sleuth+brave并根据SpringCloud Alibaba + dubbo2 + nacos1框架时的yml文件配置后,发现虽然服务消费者和服务提供者的日志都写入了traceId和spanId但两个服务的traceId不一致,变成各写各的了,整个追踪链条并未正确串联起来。 最终通过查找GitHub上的issue终于找到一个brave的dubbo2扩展能兼容dubbo3的解决方案,在这里感谢@ShenFeng312这位大佬。
GitHub issue的comment链接如下:
https://github.com/apache/dubbo/issues/11650#issuecomment-1446313706

解决方案

笔者用的依赖版本如下:

SpringCloud Alibaba 2021.0.5.0
Springboot 2.7.8
Dubbo 3.2.4 
Nacos 2.2.0
spring-cloud-starter-sleuth 3.1.9
brave-instrumentation-dubbo 5.16.0

解决方案非常简单,只需修改一行源码即可。
修改brave.dubbo.TracingFilter#invoke中的RpcContext.getContext().getAttachments()改为invocation.getAttachments()
修改前:
在这里插入图片描述
修改后:
在这里插入图片描述
PS: 因为是要修改依赖的源码,所以各位读者可以复制该类、修改这行、增加Dubbo的SPI来指向自定义的TracingFilter。又或者自行重新打包jar。这里就不详细叙述了。

原因

根据GitHub上https://github.com/apache/dubbo/issues/11650#issuecomment-1446313706中@ShenFeng312大佬的分析,主要是因为dubbo2和dubbo3的RpcContext中invcation的attachment属性的实现方式改变了导致的。从issue的回复中其实也看到@ShenFeng312大佬也向brave提交了merge request,让brave-instrumentation-dubbo能兼容dubbo3,但是brave的开发人员一直未同意合并,这里就不展开说了,大家有兴趣可以自行去GitHub上看看。

疑问

根据知其然,也要知其所以然的想法,我尝试分析其中的原因,但也产生了一些疑问,以下疑问也会在后续的排查过程中逐一解答。

疑问一:dubbo2中为什么直接用RpcContext.getContext().getAttachments()就能传递traceId而不用invocation.getAttachments()呢?
疑问二:为什么dubbo3中brave.dubbo.TracingFilter继续用RpcContext.getContext().getAttachments()是不行的呢?
疑问三:dubbo3中的RpcContext.getContext().getAttachments()和invocation.getAttachments()有什么区别?

排查过程

追踪brave.dubbo.TracingFilter#invoke方法的try…catch…部分可以看到,最终传递到下一个服务的参数是通过invocation的,而traceId和spanId是保存在invocation的attachment这个map中的。所以我们接下来排查的目标都是检查并验证最终invoke时invocation的attachment中有没有traceId和spanId为准。
在这里插入图片描述

排查疑问一:

在这里插入图片描述
根据上述思路及brave.dubbo.TracingFilter#invoke方法中的注释得知,其实clientHandler.handleSendWithParent(clientRequest, invocationContext)一直都只是在操作RpcContext.getContext()的attachment并未操作invocation中的attachment
在这里插入图片描述
而invocation中的attachment其实是直到invoker.invoke(invocation)时才在org.apache.dubbo.rpc.protocol.AbstractInvoker#invoke方法把RpcContext.getContext()的attachment注入到invocation中的attachment中
在这里插入图片描述
所以通过上面的代码跟踪可以发现,dubbo2中虽然一直都是在操作RpcContext.getContext()的attachment,但会在AbstractInvoker#invoke方法中invoke下一个服务的前一刻把RpcCotext.getContext()的attachment注入到invocation中的attachment中,最终还是通过invocation中的attachment传递traceId给下一个服务的。
综上所述,在dubbo2中通过RpcContext.getContext().getAttachments()来操作RpcContext的attachment最终都会在AbstractInvoker#invoke方法里被注入到invocation中的attachment中,所以dubbo2中是可以通过RpcContext.getContext().getAttachments()来传递traceId的

排查疑问二、三:

追踪dubbo3中的RpcContext.getContext().getAttachments():
在这里插入图片描述
比对dubbo2中的RpcContext.getContext().getAttachments():
在这里插入图片描述
通过比较dubbo3和dubbo2的RpcContext.getContext().getAttachments()的实现,可以发现,dubbo3实现方式完全不同了,这个改动在dubbo3的官方手册中其实是有提及的
在这里插入图片描述
正是因为RpcContext.getContext().getAttachments()的实现改动,dubbo3中RpcContext.getContext().getAttachments()返回的不是RpcContext中的attachment也不是invocation中的attachment。而是把RpcContext中的SERVER_ATTACHMENT、CLIENT_ATTACHMENT复制一份并返回。后面再怎么put参数进RpcContext中的attachment都没有用了,即使后面在AbstractInvoker#invoke方法中把RpcContext中的attachment注入进invocation的attachment也没用了,因为put参数的时候是put进RpcContext的attachment的副本,而不是RpcContext中的attachment本身
而且! 本身dubbo3中AbstractInvoker#invoke方法中把RpcContext中的attachment注入invocation的attachment的实现也变了
在这里插入图片描述
不过这里已经不重要了,因为put参数的时候是put进RpcContext中的attachment的副本并未put进RpcContext的任何属性中。
综上所述,通过上述对源码的分析也解释了疑问二疑问三,解释了dubbo3中的RpcContext.getContext().getAttachments()和invocation.getAttachments()有什么区别和为什么dubbo3中brave.dubbo.TracingFilter继续用RpcContext.getContext().getAttachments()是不行的。

总结分析

总的来说,主要是dubbo3的RpcContext被拆分为四大模块才导致brave的dubbo扩展不能直接用,但是其实改动起来还是比较简单的。上述如果有说的不对的地方,还请各位大佬指正。下面笔者还画了一张图来帮助理解:
在这里插入图片描述
最后,版权归作者所有,任何形式转载请联系作者,谢谢!

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值