如何复用入站请求中的traceId以实现跨服务链路追踪?
跨服务Span追踪复用traceId解决方案
要实现跨服务的traceId复用,核心是在服务间调用时传递标准的追踪上下文头,并在下游服务初始化时解析这些头来构建Span上下文,让@NewSpan能基于上游的traceId生成子Span。以下是具体实现步骤:
一、统一追踪标准(选其一)
目前主流的跨服务追踪头标准有两种,上下游服务必须保持一致:
- W3C Traceparent(推荐):格式为
00-{traceId}-{spanId}-{flags},其中traceId是32位十六进制字符串,spanId是16位十六进制字符串 - B3协议:使用
X-B3-TraceId、X-B3-SpanId、X-B3-Sampled三个请求头
二、上游服务:传递追踪上下文
在调用下游服务时,将当前Span的上下文信息通过请求头传递出去,以Feign和RestTemplate为例:
1. Feign请求拦截器(自动传递头)
@Component public class TracePropagationFeignInterceptor implements RequestInterceptor { @Override public void apply(RequestTemplate template) { Span currentSpan = Span.current(); SpanContext spanContext = currentSpan.getSpanContext(); if (spanContext.isValid()) { // 传递W3C traceparent头 String traceparent = String.format("00-%s-%s-01", spanContext.getTraceId(), spanContext.getSpanId()); template.header("traceparent", traceparent); // 如果用B3协议,替换为以下代码: // template.header("X-B3-TraceId", spanContext.getTraceId()); // template.header("X-B3-SpanId", spanContext.getSpanId()); // template.header("X-B3-Sampled", "1"); } } }
2. RestTemplate拦截器
@Configuration public class RestTemplateConfig { @Bean public RestTemplate restTemplate() { RestTemplate restTemplate = new RestTemplate(); restTemplate.getInterceptors().add((request, body, execution) -> { SpanContext spanContext = Span.current().getSpanContext(); if (spanContext.isValid()) { request.getHeaders().add("traceparent", String.format("00-%s-%s-01", spanContext.getTraceId(), spanContext.getSpanId())); } return execution.execute(request, body); }); return restTemplate; } }
三、下游服务:解析追踪上下文并初始化Span
通过过滤器拦截入站请求,解析传递的追踪头,手动构建Span上下文,确保@NewSpan执行前已有可用的traceId:
@Component @Order(Ordered.HIGHEST_PRECEDENCE) // 确保过滤器最先执行 public class TraceContextInitFilter extends OncePerRequestFilter { private static final String TRACEPARENT_HEADER = "traceparent"; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { String traceparent = request.getHeader(TRACEPARENT_HEADER); if (traceparent != null && traceparent.matches("00-[a-f0-9]{32}-[a-f0-9]{16}-[a-f0-9]{2}")) { // 解析W3C traceparent头 String[] parts = traceparent.split("-"); String traceId = parts[1]; String parentSpanId = parts[2]; // 构建Span上下文 SpanContext spanContext = SpanContext.create( traceId, parentSpanId, TraceFlags.getSampled(), TraceState.getDefault() ); // 将父Span设置为当前上下文,后续@NewSpan会自动生成子Span Span parentSpan = Span.wrap(spanContext); try (Scope scope = parentSpan.makeCurrent()) { filterChain.doFilter(request, response); } finally { parentSpan.end(); } } else { // 无有效追踪头时,走默认Span生成逻辑 filterChain.doFilter(request, response); } } }
四、简化方案:用Spring Cloud Sleuth自动处理
如果你的技术栈是Spring Cloud,可以直接引入Sleuth依赖,它会自动完成追踪头的传递和解析,无需手动写拦截器/过滤器:
- 引入依赖
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-sleuth</artifactId> </dependency>
- 配置文件指定追踪协议
spring: sleuth: propagation: type: W3C # 或B3,与上游保持一致
常见问题排查
- 若之前手动预设请求头失败,大概率是头格式不符合标准,比如使用自定义头而非traceparent/X-B3系列,导致下游无法识别
- 确保下游的过滤器执行顺序高于业务拦截器,保证Span上下文在
@NewSpan注解执行前已初始化
内容的提问来源于stack exchange,提问作者Martin Valensky
相关产品推荐
相关产品推荐

