在分布式系统中,traceId
需要在服务之间传递,以确保整个请求链路的日志能够被追踪。下面我们通过一个具体的例子,展示如何通过 A 服务调用 B 服务时传递 traceId
,并确保日志中能够正确记录 traceId
。
实现思路
A 服务生成
traceId
:在 A 服务的HandlerInterceptor
中生成traceId
,并将其放入MDC
。A 服务调用 B 服务时传递
traceId
:在 A 服务调用 B 服务时,将traceId
通过 HTTP 头传递给 B 服务。B 服务接收
traceId
:在 B 服务中,通过HandlerInterceptor
或过滤器从 HTTP 头中提取traceId
,并将其放入MDC
。日志输出
traceId
:在 A 服务和 B 服务的日志配置中,确保traceId
能够被输出。
代码实现
1. A 服务代码
(1) TraceIdInterceptor
生成 traceId
java
复制
import org.slf4j.MDC; import org.springframework.stereotype.Component; import org.springframework.web.servlet.HandlerInterceptor; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.util.UUID; @Component public class TraceIdInterceptor implements HandlerInterceptor { private static final String TRACE_ID = "traceId"; @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 生成唯一的 traceId String traceId = UUID.randomUUID().toString(); // 将 traceId 放入 MDC MDC.put(TRACE_ID, traceId); return true; } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { // 请求完成后清理 MDC 中的 traceId MDC.remove(TRACE_ID); } }
(2) 注册 TraceIdInterceptor
java
复制
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @Configuration public class WebConfig implements WebMvcConfigurer { @Autowired private TraceIdInterceptor traceIdInterceptor; @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(traceIdInterceptor); } }
(3) 调用 B 服务时传递 traceId
在 A 服务中,使用 RestTemplate
或 Feign
调用 B 服务时,将 traceId
添加到 HTTP 头中。
java
复制
import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.slf4j.MDC; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.RequestEntity; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; import java.net.URI; @RestController public class AServiceController { private static final Logger logger = LoggerFactory.getLogger(AServiceController.class); private final RestTemplate restTemplate = new RestTemplate(); @GetMapping("/call-b-service") public String callBService() { // 获取当前请求的 traceId String traceId = MDC.get("traceId"); logger.info("A Service - Calling B Service with traceId: {}", traceId); // 设置 HTTP 头,传递 traceId HttpHeaders headers = new HttpHeaders(); headers.add("X-Trace-Id", traceId); // 调用 B 服务 RequestEntity<Void> request = new RequestEntity<>(headers, HttpMethod.GET, URI.create("http://localhost:8081/b-service")); ResponseEntity<String> response = restTemplate.exchange(request, String.class); return "A Service received response from B Service: " + response.getBody(); } }
2. B 服务代码
(1) TraceIdFilter
接收 traceId
在 B 服务中,使用过滤器从 HTTP 头中提取 traceId
,并将其放入 MDC
。
java
复制
import org.slf4j.MDC; import org.springframework.stereotype.Component; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import java.io.IOException; import java.util.UUID; @Component public class TraceIdFilter implements Filter { private static final String TRACE_ID = "traceId"; @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest httpRequest = (HttpServletRequest) request; // 从 HTTP 头中获取 traceId String traceId = httpRequest.getHeader("X-Trace-Id"); if (traceId == null || traceId.isEmpty()) { // 如果头中没有 traceId,生成一个新的 traceId = UUID.randomUUID().toString(); } // 将 traceId 放入 MDC MDC.put(TRACE_ID, traceId); try { chain.doFilter(request, response); } finally { // 请求完成后清理 MDC 中的 traceId MDC.remove(TRACE_ID); } } @Override public void init(FilterConfig filterConfig) throws ServletException { } @Override public void destroy() { } }
(2) 注册 TraceIdFilter
java
复制
import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class FilterConfig { @Bean public FilterRegistrationBean<TraceIdFilter> traceIdFilter() { FilterRegistrationBean<TraceIdFilter> registrationBean = new FilterRegistrationBean<>(); registrationBean.setFilter(new TraceIdFilter()); registrationBean.addUrlPatterns("/*"); return registrationBean; } }
(3) B 服务接口
java
复制
import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.slf4j.MDC; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class BServiceController { private static final Logger logger = LoggerFactory.getLogger(BServiceController.class); @GetMapping("/b-service") public String bService() { // 获取当前请求的 traceId String traceId = MDC.get("traceId"); logger.info("B Service - Processing request with traceId: {}", traceId); return "Hello from B Service!"; } }
3. 日志配置
在 A 服务和 B 服务的 logback.xml
中,配置日志输出格式,确保 traceId
能够被打印出来。
xml
复制
<configuration> <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> <encoder> <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - [%X{traceId}] %msg%n</pattern> </encoder> </appender> <root level="info"> <appender-ref ref="STDOUT" /> </root> </configuration>
运行 HTML
测试流程
启动 A 服务和 B 服务。
访问 A 服务的
/call-b-service
接口。查看 A 服务和 B 服务的日志输出,确保
traceId
一致。
A 服务日志示例:
复制
2023-10-01 12:00:00.000 [http-nio-8080-exec-1] INFO AServiceController - [123e4567-e89b-12d3-a456-426614174000] A Service - Calling B Service with traceId: 123e4567-e89b-12d3-a456-426614174000
B 服务日志示例:
复制
2023-10-01 12:00:00.100 [http-nio-8081-exec-1] INFO BServiceController - [123e4567-e89b-12d3-a456-426614174000] B Service - Processing request with traceId: 123e4567-e89b-12d3-a456-426614174000
总结
通过上述实现,我们完成了 A 服务调用 B 服务时的 traceId
传递和日志跟踪。traceId
通过 HTTP 头在服务间传递,并通过 MDC
在日志中输出,确保整个请求链路的日志能够被追踪。