TraceId如何在Spring-Cloud微服务的REST调用中传递

文章目录

      • [推荐方案:基于Spring Cloud Sleuth(无侵入,官方推荐)](#推荐方案:基于Spring Cloud Sleuth(无侵入,官方推荐))
        • [1. 集成Sleuth](#1. 集成Sleuth)
        • [2. 核心原理](#2. 核心原理)
        • [3. 日志配置(输出traceId)](#3. 日志配置(输出traceId))
        • [4. 验证](#4. 验证)
      • 自定义实现方案(不依赖Sleuth,了解原理)
        • [1. 定义常量(统一Header键)](#1. 定义常量(统一Header键))
        • [2. 发送端:通过拦截器传递traceId](#2. 发送端:通过拦截器传递traceId)
        • [3. 接收端:通过过滤器提取traceId并设置到MDC](#3. 接收端:通过过滤器提取traceId并设置到MDC)
      • 关键注意事项

在Spring Cloud微服务中,REST接口调用的 traceId传递核心是通过 HTTP请求头(Header) 携带 traceId,结合拦截器(发送端)和过滤器(接收端)实现自动传递,且不侵入业务代码。

推荐方案:基于Spring Cloud Sleuth(无侵入,官方推荐)

Spring Cloud Sleuth是Spring官方的分布式追踪组件,默认已实现traceId的生成、传递和MDC绑定,无需手动编码,完全无侵入。

1. 集成Sleuth

在微服务的pom.xml中引入依赖(以Spring Boot 2.x为例):

xml 复制代码
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>
2. 核心原理
  • 发送端 :Sleuth会自动拦截RestTemplateFeignClient等HTTP调用,将当前线程MDC中的traceId(默认键为X-B3-TraceId)添加到HTTP请求头中。
  • 接收端 :Sleuth会拦截HTTP请求,从请求头中提取X-B3-TraceId,自动设置到当前线程的MDC中(键为traceId),供日志框架使用。
  • 自动生成 :若当前线程无traceId(如请求入口),Sleuth会自动生成全局唯一的traceId
3. 日志配置(输出traceId)

logback-spring.xml(或其他日志配置文件)中,通过%X{traceId}占位符输出traceId

xml 复制代码
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
    <encoder>
        <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%X{traceId}] [%thread] %-5level %logger{50} - %msg%n</pattern>
    </encoder>
</appender>
4. 验证
  • 服务A调用服务B的REST接口时,Sleuth会自动在请求头中添加X-B3-TraceId
  • 服务B的日志中会包含与服务A相同的traceId,实现全链路关联。

自定义实现方案(不依赖Sleuth,了解原理)

若需自定义traceId传递逻辑(如自定义Header键、生成规则),可通过发送端拦截器接收端过滤器实现。

1. 定义常量(统一Header键)
java 复制代码
public class TraceConstant {
    // 自定义traceId在HTTP头中的键
    public static final String TRACE_ID_HEADER = "X-Trace-Id";
    // MDC中的键
    public static final String TRACE_ID_MDC_KEY = "traceId";
}
2. 发送端:通过拦截器传递traceId

发送端(调用其他服务的微服务)需要在HTTP请求发送前,将当前MDC的traceId放入请求头。

(1)RestTemplate调用场景

配置RestTemplate时添加拦截器:

java 复制代码
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpRequest;
import org.springframework.http.client.ClientHttpRequestExecution;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.web.client.RestTemplate;
import org.slf4j.MDC;
import java.io.IOException;
import java.util.UUID;

@Configuration
public class RestTemplateConfig {

    @Bean
    public RestTemplate restTemplate() {
        RestTemplate restTemplate = new RestTemplate();
        // 添加自定义拦截器,传递traceId
        restTemplate.getInterceptors().add(new TraceIdInterceptor());
        return restTemplate;
    }

    // 自定义拦截器
    public static class TraceIdInterceptor implements ClientHttpRequestInterceptor {
        @Override
        public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
            // 1. 获取当前MDC的traceId,若不存在则生成
            String traceId = MDC.get(TraceConstant.TRACE_ID_MDC_KEY);
            if (traceId == null) {
                traceId = generateTraceId();
            }
            // 2. 将traceId添加到请求头
            HttpHeaders headers = request.getHeaders();
            headers.add(TraceConstant.TRACE_ID_HEADER, traceId);
            // 3. 继续执行请求
            return execution.execute(request, body);
        }

        private String generateTraceId() {
            return UUID.randomUUID().toString().replace("-", "");
        }
    }
}
(2)Feign调用场景

通过RequestInterceptor拦截Feign请求,添加traceId到请求头:

java 复制代码
import feign.RequestInterceptor;
import feign.RequestTemplate;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.slf4j.MDC;
import java.util.UUID;

@Configuration
public class FeignConfig {

    @Bean
    public RequestInterceptor traceIdRequestInterceptor() {
        return new RequestInterceptor() {
            @Override
            public void apply(RequestTemplate template) {
                // 1. 获取或生成traceId
                String traceId = MDC.get(TraceConstant.TRACE_ID_MDC_KEY);
                if (traceId == null) {
                    traceId = UUID.randomUUID().toString().replace("-", "");
                }
                // 2. 添加到Feign请求头
                template.header(TraceConstant.TRACE_ID_HEADER, traceId);
            }
        };
    }
}
3. 接收端:通过过滤器提取traceId并设置到MDC

接收端(被调用的微服务)需要从HTTP请求头中提取traceId,设置到MDC,并在请求结束后清除。

java 复制代码
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.slf4j.MDC;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@Component
public class TraceIdInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        // 1. 从请求头提取traceId
        String traceId = request.getHeader(TraceConstant.TRACE_ID_HEADER);
        // 2. 若不存在,可生成新的(如入口服务)
        if (traceId == null) {
            traceId = generateTraceId();
        }
        // 3. 设置到MDC
        MDC.put(TraceConstant.TRACE_ID_MDC_KEY, traceId);
        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
        // 4. 请求处理完成后清除MDC(关键:避免线程池复用导致的残留)
        MDC.remove(TraceConstant.TRACE_ID_MDC_KEY);
    }

    private String generateTraceId() {
        return UUID.randomUUID().toString().replace("-", "");
    }
}

注册拦截器(确保生效):

java 复制代码
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import javax.annotation.Resource;

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

    @Resource
    private TraceIdInterceptor traceIdInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 拦截所有请求
        registry.addInterceptor(traceIdInterceptor).addPathPatterns("/**");
    }
}

关键注意事项

  1. 优先级:推荐使用Spring Cloud Sleuth,它与Spring Cloud生态(如Zipkin、Feign、Gateway)无缝集成,支持更复杂的链路追踪(如spanId、调用耗时)。
  2. 入口服务处理 :对于整个链路的第一个服务(如网关),若请求头中无traceId,需自动生成(Sleuth或自定义拦截器均会处理)。
  3. 线程安全 :接收端必须在afterCompletion中清除MDC,避免Tomcat线程池复用导致traceId串用。
  4. 网关传递 :若使用Spring Cloud Gateway,需确保网关也配置了traceId传递(Sleuth对Gateway有原生支持,自定义方案需通过GlobalFilter实现)。

通过以上方式,可在Spring Cloud微服务的REST调用中实现traceId的自动传递,全链路日志将包含相同的traceId,便于问题追踪。

相关推荐
wuxuanok11 小时前
Debug —— 部署微服务中微服务的配置文件及本地注册Nacos没问题但部署到Docker中就注册失败
docker·微服务·架构
猪猪拆迁队12 小时前
前端图形引擎架构设计:双引擎架构设计
前端·后端·架构
俞凡13 小时前
[大厂实践] 超越极限:利用路由服务器实现稳定集群
架构
小毅&Nora13 小时前
【云计算】【Kubernetes】 ① K8S的架构、应用及源码解析 - 核心架构与组件全景图
架构·kubernetes·云计算
百***659514 小时前
PON架构(全光网络)
网络·数据库·架构
快递鸟15 小时前
小程序开发之物流打单API接口预留解决方案:规避返工,高效对接
架构
帅锅锅00716 小时前
Android 源码学习之init进程
android·架构·操作系统
wanhengidc18 小时前
云手机的网络架构
服务器·网络·游戏·智能手机·架构·云计算
xinyu_Jina19 小时前
WebRTC的P2P实践:局域网文件传输中的信令、ICE与DataChannel架构解析
架构·webrtc·p2p