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,便于问题追踪。

相关推荐
机灵猫4 小时前
微服务中的服务熔断、降级与限流
java·数据库·微服务
周杰伦_Jay7 小时前
【常用设计模式全解析】创建型模式(聚焦对象创建机制)、结构型模式(优化类与对象的组合关系)、行为型模式(规范对象间的交互行为)
设计模式·架构·开源·交互·1024程序员节
周杰伦_Jay8 小时前
【Elasticsearch 全解析】分布式搜索引擎的原理、实践与优化
大数据·分布式·elasticsearch·架构·开源·1024程序员节
赋创小助手8 小时前
“短小精悍”的边缘AI算力利器:超微SYS-E403-14B-FRN2T服务器评测
服务器·人工智能·科技·ai·架构·边缘计算·1024程序员节
oak隔壁找我9 小时前
JavaScript 模块化演进历程:问题与解决方案。
前端·javascript·架构
王嘉俊92514 小时前
HarmonyOS 超级终端与服务卡片开发:打造无缝多设备交互体验
华为·架构·harmonyos·arkts·1024程序员节
小马哥编程15 小时前
【软考架构】架构风格:RAG知识库是属于软件八大架构风格中的哪一个,黑板架构风格 ?规则系统体系风格?
大数据·计算机网络·架构·1024程序员节
报错小能手15 小时前
项目——基于C/S架构的预约系统平台(2)
linux·c语言·笔记·学习·架构