在分布式系统中,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
在日志中输出,确保整个请求链路的日志能够被追踪。