问题场景
笔者之前一直使用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扩展不能直接用,但是其实改动起来还是比较简单的。上述如果有说的不对的地方,还请各位大佬指正。下面笔者还画了一张图来帮助理解:
最后,版权归作者所有,任何形式转载请联系作者,谢谢!