文章目录
-
-
- [推荐方案:基于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会自动拦截
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:
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("/**");
}
}
关键注意事项
- 优先级:推荐使用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,便于问题追踪。