微服务调用中TraceId是如何传递的?

1,904 阅读3分钟

背景

最近脉脉上比较多脉友关注的技术面试题目“微服务调用中TraceId是怎么传递的?”,有比较多的同学关注并发表了自己的见解。

image.png

TraceId是可以串连一个完整请求的唯一记录id,通过traceId可以关联到整个请求过程中涉及到的上下游通信,包括网关,rpc通信,mq通信链路。traceId总体来说会在两种场景中传递。

TraceId还是很有用的,日常开发中,我们经常通过TraceId来捞服务日志,通过TraceId查看整个请求链路耗时,异常等信息,可以说是日常开发运维中不可缺少的排障手段。

image.png

总体来说,traceId会在两种场景中传递。 image.png

应用内传递TraceId

第一种是应用内部传递,这种传递我们一般通过ThreadLocal,InheritableThreadLocal传递,这个不是本文重点,不做具体分析。

跨进程的应用间传递TraceId

第二种是跨进程的应用间传递,这种会涉及到远程rpc通信,mq通信,数据库通信等,这种一般我们需要借助中间件的扩展机制来实现传递TraceId,这个也是本文分析的重点。

OpenFeign调用传递

64145B9A-9034-4593-8F40-1DF54C58D376.png

由于openfeign底层使用http协议作为通信协议,而http的请求报文包含header和body,body一般是用于业务数据交换,而header一般用于交换非业务数据,TraceId就非常适合在header中传输,那么openfeign的header我们需要怎么增加自定义属性呢?这个时候就需要看openfeign的扩展机制了。上文(面试官:OpenFeign十大可扩展组件你知道哪些?)分析openfeign扩展点的文章就提到了feign.RequestInterceptor, 这个机制能够实现修改请求对象的目的,那么可以利用他来传输traceId。下面是一个写traceId拦截器的例子。

EE08C55C-4877-4571-AC18-E5D1C4A5EBC1.png

通过实现feign.RequestInterceptor接口就可以在发起feign请求前注入traceId并透传给下游服务,是不是超简单。

image.png

Dubbo调用传递TraceId

dubbo不像openfeign, dubbo支持多种通信协议,下图是dubbo官方的通信协议图谱,默认有自己的dubbo通信协议,dubbo协议默认使用netty作为通信框架

CFC5A8E2-3521-4EEC-95D0-47E766EE94CC.png

Dubbo协议虽然是Dubbo框架自定义的协议,但是dubbo协议也有请求头和请求体,Dubbo协议可以使用Attachments对象传输非业务数据,相信使用过dubbo的同学应该会知道这个。通过RpcContext.getContext()拿到当前执行请求的上下文信息,再通过setAttachment方法进行传递。下面是结合Dubbo的过滤器机制统一实现rpc传递。

843E3095-D350-48A3-9961-F79893B7651A.png

RocketMq通过消息传递traceId

要知道rocketmq怎么传递traceId,同样需要看下他的消息体,通过org.apache.rocketmq.common.message.Message可知properties属性最适合传递。

6CE3518A-0A91-46B5-AC8E-15FE95EDA8C4.png

生产者最终将在发送消息前会将properties传入requestHeader进行传输,我们也可以写一个拦截器,拦截所有的消息生产者,在properties里加入TraceId。

462BA52C-C457-4568-BA00-4E52E8E637FA.png

总结

本文分析了TraceId传输的场景以及在应用间传输的部分实现,通过应用间的传输实践,我们知道了通信框架一般都是可以通过请求报文的请求头来传输TraceId的,而且会使用拦截器来实现通用的传输TraceId。