文章目录
-
-
- 推荐方案:基于Spring Cloud Sleuth(无侵入,官方推荐)
-
- 1. 集成Sleuth
* 2. 核心原理
* 3. 日志配置(输出traceId)
* 4. 验证
- 1. 集成Sleuth
- 自定义实现方案(不依赖Sleuth,了解原理)
-
- 1. 定义常量(统一Header键)
* 2. 发送端:通过拦截器传递traceId
* * (1)RestTemplate调用场景
* (2)Feign调用场景
* 3. 接收端:通过过滤器提取traceId并设置到MDC
- 1. 定义常量(统一Header键)
- 关键注意事项
-
- 推荐方案:基于Spring Cloud Sleuth(无侵入,官方推荐)
-
在Spring Cloud微服务中,REST接口调用的traceId传递核心是通过HTTP请求头(Header) 携带traceId,结合拦截器(发送端)和过滤器(接收端)实现自动传递,且不侵入业务代码。
推荐方案:基于Spring Cloud Sleuth(无侵入,官方推荐)
Spring Cloud Sleuth是Spring官方的分布式追踪组件,默认已实现traceId的生成、传递和MDC绑定,无需手动编码,完全无侵入。
1. 集成Sleuth
在微服务的pom.xml中引入依赖(以Spring Boot 2.x为例):
1<dependency> 2 <groupId>org.springframework.cloud</groupId> 3 <artifactId>spring-cloud-starter-sleuth</artifactId> 4</dependency> 5
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:
1<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender"> 2 <encoder> 3 <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%X{traceId}] [%thread] %-5level %logger{50} - %msg%n</pattern> 4 </encoder> 5</appender> 6
4. 验证
- 服务A调用服务B的REST接口时,Sleuth会自动在请求头中添加
X-B3-TraceId。 - 服务B的日志中会包含与服务A相同的
traceId,实现全链路关联。
自定义实现方案(不依赖Sleuth,了解原理)
若需自定义traceId传递逻辑(如自定义Header键、生成规则),可通过发送端拦截器和接收端过滤器实现。
1. 定义常量(统一Header键)
1public class TraceConstant { 2 // 自定义traceId在HTTP头中的键 3 public static final String TRACE_ID_HEADER = "X-Trace-Id"; 4 // MDC中的键 5 public static final String TRACE_ID_MDC_KEY = "traceId"; 6} 7
2. 发送端:通过拦截器传递traceId
发送端(调用其他服务的微服务)需要在HTTP请求发送前,将当前MDC的traceId放入请求头。
(1)RestTemplate调用场景
配置RestTemplate时添加拦截器:
1import org.springframework.context.annotation.Bean; 2import org.springframework.context.annotation.Configuration; 3import org.springframework.http.HttpHeaders; 4import org.springframework.http.HttpRequest; 5import org.springframework.http.client.ClientHttpRequestExecution; 6import org.springframework.http.client.ClientHttpRequestInterceptor; 7import org.springframework.http.client.ClientHttpResponse; 8import org.springframework.web.client.RestTemplate; 9import org.slf4j.MDC; 10import java.io.IOException; 11import java.util.UUID; 12 13@Configuration 14public class RestTemplateConfig { 15 16 @Bean 17 public RestTemplate restTemplate() { 18 RestTemplate restTemplate = new RestTemplate(); 19 // 添加自定义拦截器,传递traceId 20 restTemplate.getInterceptors().add(new TraceIdInterceptor()); 21 return restTemplate; 22 } 23 24 // 自定义拦截器 25 public static class TraceIdInterceptor implements ClientHttpRequestInterceptor { 26 @Override 27 public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException { 28 // 1. 获取当前MDC的traceId,若不存在则生成 29 String traceId = MDC.get(TraceConstant.TRACE_ID_MDC_KEY); 30 if (traceId == null) { 31 traceId = generateTraceId(); 32 } 33 // 2. 将traceId添加到请求头 34 HttpHeaders headers = request.getHeaders(); 35 headers.add(TraceConstant.TRACE_ID_HEADER, traceId); 36 // 3. 继续执行请求 37 return execution.execute(request, body); 38 } 39 40 private String generateTraceId() { 41 return UUID.randomUUID().toString().replace("-", ""); 42 } 43 } 44} 45
(2)Feign调用场景
通过RequestInterceptor拦截Feign请求,添加traceId到请求头:
1import feign.RequestInterceptor; 2import feign.RequestTemplate; 3import org.springframework.context.annotation.Bean; 4import org.springframework.context.annotation.Configuration; 5import org.slf4j.MDC; 6import java.util.UUID; 7 8@Configuration 9public class FeignConfig { 10 11 @Bean 12 public RequestInterceptor traceIdRequestInterceptor() { 13 return new RequestInterceptor() { 14 @Override 15 public void apply(RequestTemplate template) { 16 // 1. 获取或生成traceId 17 String traceId = MDC.get(TraceConstant.TRACE_ID_MDC_KEY); 18 if (traceId == null) { 19 traceId = UUID.randomUUID().toString().replace("-", ""); 20 } 21 // 2. 添加到Feign请求头 22 template.header(TraceConstant.TRACE_ID_HEADER, traceId); 23 } 24 }; 25 } 26} 27
3. 接收端:通过过滤器提取traceId并设置到MDC
接收端(被调用的微服务)需要从HTTP请求头中提取traceId,设置到MDC,并在请求结束后清除。
1import org.springframework.stereotype.Component; 2import org.springframework.web.servlet.HandlerInterceptor; 3import org.slf4j.MDC; 4import javax.servlet.http.HttpServletRequest; 5import javax.servlet.http.HttpServletResponse; 6 7@Component 8public class TraceIdInterceptor implements HandlerInterceptor { 9 10 @Override 11 public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { 12 // 1. 从请求头提取traceId 13 String traceId = request.getHeader(TraceConstant.TRACE_ID_HEADER); 14 // 2. 若不存在,可生成新的(如入口服务) 15 if (traceId == null) { 16 traceId = generateTraceId(); 17 } 18 // 3. 设置到MDC 19 MDC.put(TraceConstant.TRACE_ID_MDC_KEY, traceId); 20 return true; 21 } 22 23 @Override 24 public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { 25 // 4. 请求处理完成后清除MDC(关键:避免线程池复用导致的残留) 26 MDC.remove(TraceConstant.TRACE_ID_MDC_KEY); 27 } 28 29 private String generateTraceId() { 30 return UUID.randomUUID().toString().replace("-", ""); 31 } 32} 33
注册拦截器(确保生效):
1import org.springframework.context.annotation.Configuration; 2import org.springframework.web.servlet.config.annotation.InterceptorRegistry; 3import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; 4import javax.annotation.Resource; 5 6@Configuration 7public class WebMvcConfig implements WebMvcConfigurer { 8 9 @Resource 10 private TraceIdInterceptor traceIdInterceptor; 11 12 @Override 13 public void addInterceptors(InterceptorRegistry registry) { 14 // 拦截所有请求 15 registry.addInterceptor(traceIdInterceptor).addPathPatterns("/**"); 16 } 17} 18
关键注意事项
- 优先级:推荐使用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,便于问题追踪。