TraceId如何在Spring-Cloud微服务的REST调用中传递
文章目录
- 推荐方案:基于Spring Cloud Sleuth(无侵入,官方推荐)
- 1. 集成Sleuth
- 2. 核心原理
- 3. 日志配置(输出traceId)
- 4. 验证
- 自定义实现方案(不依赖Sleuth,了解原理)
- 1. 定义常量(统一Header键)
- 2. 发送端:通过拦截器传递traceId
- (1)RestTemplate调用场景
- (2)Feign调用场景
- 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为例):
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>
2. 核心原理
- 发送端:Sleuth会自动拦截
RestTemplate、FeignClient等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:
<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键)
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时添加拦截器:
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 {@Beanpublic RestTemplate restTemplate() {RestTemplate restTemplate = new RestTemplate();// 添加自定义拦截器,传递traceIdrestTemplate.getInterceptors().add(new TraceIdInterceptor());return restTemplate;}// 自定义拦截器public static class TraceIdInterceptor implements ClientHttpRequestInterceptor {@Overridepublic 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到请求头:
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 {@Beanpublic RequestInterceptor traceIdRequestInterceptor() {return new RequestInterceptor() {@Overridepublic void apply(RequestTemplate template) {// 1. 获取或生成traceIdString 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,并在请求结束后清除。
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 {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {// 1. 从请求头提取traceIdString traceId = request.getHeader(TraceConstant.TRACE_ID_HEADER);// 2. 若不存在,可生成新的(如入口服务)if (traceId == null) {traceId = generateTraceId();}// 3. 设置到MDCMDC.put(TraceConstant.TRACE_ID_MDC_KEY, traceId);return true;}@Overridepublic 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("-", "");}
}
注册拦截器(确保生效):
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 {@Resourceprivate TraceIdInterceptor traceIdInterceptor;@Overridepublic void addInterceptors(InterceptorRegistry registry) {// 拦截所有请求registry.addInterceptor(traceIdInterceptor).addPathPatterns("/**");}
}
关键注意事项
- 优先级:推荐使用Spring Cloud Sleuth,它与Spring Cloud生态(如Zipkin、Feign、Gateway)无缝集成,支持更复杂的链路追踪(如spanId、调用耗时)。
- 入口服务处理:对于整个链路的第一个服务(如网关),若请求头中无
traceId,需自动生成(Sleuth或自定义拦截器均会处理)。 - 线程安全:接收端必须在
afterCompletion中清除MDC,避免Tomcat线程池复用导致traceId串用。 - 网关传递:若使用Spring Cloud Gateway,需确保网关也配置了
traceId传递(Sleuth对Gateway有原生支持,自定义方案需通过GlobalFilter实现)。
通过以上方式,可在Spring Cloud微服务的REST调用中实现traceId的自动传递,全链路日志将包含相同的traceId,便于问题追踪。
