Java 项目中实现统一的 追踪ID,traceId实现分布式系统追踪

在 Java 项目中实现分布式追踪系统时,traceId 的传递机制可以通过以下图示来说明:

  1. 分布式追踪系统架构图

    [用户请求]


    [API网关] → 生成全局 traceId


    [服务A] → 接收 traceId → 日志记录 → 调用服务B
    │ ▲
    │ │ 传递 traceId
    ▼ │
    [服务B] → 接收 traceId → 日志记录 → 调用数据库
    │ ▲
    │ │ 传递 traceId
    ▼ │
    [数据库] → 记录 traceId 到 SQL 日志


    [消息队列] → 传递 traceId


    [服务C] → 处理消息并记录 traceId

  2. traceId 传递机制图解

2.1 HTTP 请求中的传递

复制代码
[客户端] → GET /api/data
          Header: X-Trace-Id: abc123
          │
          ▼
[服务A] → 从 Header 获取 traceId
          │
          ▼
[服务A] → 调用服务B: POST /internal/data
          Header: X-Trace-Id: abc123 (保持相同)

2.2 RPC 调用中的传递

复制代码
[服务A] → Dubbo RPC 调用服务B
   ┌───────────────────────────┐
   │ Invocation                │
   │   - method: getData       │
   │   - attachments:          │
   │        traceId = "abc123" │
   └───────────────────────────┘
          │
          ▼
[服务B] → 从 attachments 获取 traceId

2.3 消息队列中的传递

服务A\] → 发送消息到 RabbitMQ/Kafka ┌───────────────────────────┐ │ Message Properties │ │ headers: │ │ traceId = "abc123" │ └───────────────────────────┘ │ ▼ \[服务C\] → 从消息属性获取 traceId 3. 线程上下文管理 3.1 同步请求中的上下文 ┌──────────────────────┐ │ ThreadLocal │ │ traceId = "abc123" │ │ MDC (日志上下文) │ └──────────────────────┘ 3.2 异步线程中的上下文传递 主线程 → 提交任务到线程池 ┌──────────────────────┐ ┌──────────────────────┐ │ TransmittableThread │ → │ 线程池线程 │ │ traceId = "abc123" │ │ traceId = "abc123" │ └──────────────────────┘ └──────────────────────┘ 任务执行前复制 任务执行时使用 4. 日志系统中的 traceId 集成 2023-08-15 10:30:25 \[http-nio-8080-exec-1\] \[traceId:abc123\] INFO c.e.s.ServiceA - 处理请求 2023-08-15 10:30:26 \[http-nio-8080-exec-1\] \[traceId:abc123\] DEBUG c.e.s.ServiceA - 调用服务B 2023-08-15 10:30:27 \[http-nio-8080-exec-3\] \[traceId:abc123\] INFO c.e.s.ServiceB - 接收请求 2023-08-15 10:30:28 \[task-pool-1\] \[traceId:abc123\] INFO c.e.s.AsyncService - 异步任务开始 5. 全链路追踪示意图 ┌────────────┐ ┌────────────┐ ┌────────────┐ ┌──────────┐ │ 网关 │ │ 服务A │ │ 服务B │ │ 数据库 │ │ traceId生成 │ ⇒⇒⇒ │ 记录日志 │ ⇒⇒⇒ │ 记录日志 │ ⇒⇒⇒ │ SQL日志 │ │ abc123 │ │ \[abc123\] │ │ \[abc123\] │ │ \[abc123\] │ └────────────┘ └────────────┘ └──────┬──────┘ └──────────┘ ⇓ ⇓ ┌────────────┐ │ ┌────────────┐ │ 消息队列 │⇐═⇘ │ 服务C │ │ \[abc123\] │ │ \[abc123\] │ └────────────┘ └────────────┘ 关键组件说明: 1. traceId 生成器: ◦ 在系统入口(网关或第一个服务)生成唯一 ID ◦ 格式:\[应用标识\]-\[时间戳\]-\[随机数\](例:APP1-1692081025-7d3f) 2. 上下文传播器: ◦ HTTP:通过 Header(X-Trace-Id) ◦ RPC:通过调用附件(Attachment) ◦ MQ:通过消息属性(Properties) ◦ 线程:通过 ThreadLocal + MDC 3. 日志集成: ◦ 日志框架配置中添加 %X{traceId} ◦ 所有日志自动包含当前 traceId 4. 异步支持: ◦ 使用 TransmittableThreadLocal 解决线程池上下文传递 ◦ 配合 TaskDecorator 包装 Runnable 5. 监控集成: ◦ 将 traceId 传递给监控系统(如 Zipkin) ◦ 实现全链路追踪可视化 这种设计确保了在分布式系统中,从一个请求进入系统开始,到经过多个服务、数据库操作、异步任务处理,再到最终响应,整个过程中的所有日志和操作都能通过同一个 traceId 关联起来,极大地方便了问题排查和系统监控。 在 Java 项目中实现统一的 traceId(请求追踪 ID)是分布式系统追踪的关键,以下是几种常用且可靠的实现方案: *** ** * ** *** #### 方案一:Servlet Filter + MDC(推荐) // TraceId 工具类 public class TraceIdUtil { public static final String TRACE_ID = "traceId"; public static String generateTraceId() { // 使用 UUID + 时间戳确保唯一性 return "TRC-" + System.currentTimeMillis() + "-" + UUID.randomUUID().toString().substring(0, 8); } } 或者: import java.util.UUID; public class TraceIdGenerator { // 生成标准UUID格式的TraceId public static String generateUUIDTraceId() { return UUID.randomUUID().toString().replace("-", ""); } // 生成更短的UUID变种(32位) public static String generateShortUUIDTraceId() { return UUID.randomUUID().toString().replace("-", ""); } // 生成带前缀的TraceId public static String generatePrefixedTraceId(String prefix) { return prefix + "-" + generateShortUUIDTraceId(); } } // 过滤器实现 @WebFilter("/*") public class TraceIdFilter implements Filter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { try { // 1. 生成/传递 traceId HttpServletRequest httpRequest = (HttpServletRequest) request; String traceId = Optional.ofNullable(httpRequest.getHeader("X-Trace-Id")) .orElseGet(TraceIdUtil::generateTraceId); // 2. 存入 MDC(日志上下文) MDC.put(TraceIdUtil.TRACE_ID, traceId); // 3. 设置到请求属性(供业务代码使用) httpRequest.setAttribute(TRACE_ID_ATTRIBUTE, traceId); // 4. 传递给下游(设置响应头) HttpServletResponse httpResponse = (HttpServletResponse) response; httpResponse.setHeader("X-Trace-Id", traceId); chain.doFilter(request, response); } finally { // 5. 请求结束后清除 MDC MDC.clear(); } } } **日志配置(logback.xml)**: %d{yyyy-MM-dd HH:mm:ss} [%thread] [%X{traceId}] %-5level %logger{36} - %msg%n *** ** * ** *** #### 方案二:Spring Interceptor + ThreadLocal // ThreadLocal 上下文 public class TraceContext { private static final ThreadLocal traceIdHolder = new ThreadLocal<>(); public static void setTraceId(String traceId) { traceIdHolder.set(traceId); } public static String getTraceId() { return traceIdHolder.get(); } public static void clear() { traceIdHolder.remove(); } } // 拦截器实现 @Component public class TraceInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { String traceId = request.getHeader("X-Trace-Id"); if (traceId == null) { traceId = TraceIdUtil.generateTraceId(); } TraceContext.setTraceId(traceId); response.setHeader("X-Trace-Id", traceId); return true; } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { TraceContext.clear(); // 清除ThreadLocal } } 注册拦截器: @Configuration public class WebConfig implements WebMvcConfigurer { @Autowired private TraceInterceptor traceInterceptor; @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(traceInterceptor); } } *** ** * ** *** #### 方案三:Spring Cloud Sleuth(微服务专用) org.springframework.cloud spring-cloud-starter-sleuth **自动实现**: * 自动生成 traceId(格式:application-\[traceId\]-\[spanId\]) * 通过 HTTP Headers 传递(**X-B3-TraceId**, **X-B3-SpanId**) * 集成日志框架(MDC 自动注入) * 支持 OpenZipkin 分布式追踪 *** ** * ** *** #### 方案四:异步线程支持(关键扩展) 使用 **TransmittableThreadLocal** 解决线程池 traceId 传递问题: // 增强版 ThreadLocal public class AsyncTraceContext { private static final TransmittableThreadLocal traceIdHolder = new TransmittableThreadLocal<>(); // 方法同方案二... } // 线程池包装器 @Bean public Executor asyncExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setTaskDecorator(runnable -> TtlRunnable.get(() -> { // 传递 traceId 到子线程 AsyncTraceContext.setTraceId(TraceContext.getTraceId()); runnable.run(); }) ); return executor; } *** ** * ** *** #### 方案五:RPC 调用传递 ##### 1. HTTP 客户端(RestTemplate) @Bean public RestTemplate restTemplate() { RestTemplate restTemplate = new RestTemplate(); restTemplate.getInterceptors().add((request, body, execution) -> { // 将当前 traceId 设置到请求头 request.getHeaders().add("X-Trace-Id", TraceContext.getTraceId()); return execution.execute(request, body); }); return restTemplate; } ##### 2. Dubbo 调用 // 消费者过滤器 public class DubboTraceConsumerFilter implements Filter { @Override public Result invoke(Invoker invoker, Invocation invocation) { invocation.setAttachment("traceId", TraceContext.getTraceId()); return invoker.invoke(invocation); } } // 提供者过滤器 public class DubboTraceProviderFilter implements Filter { @Override public Result invoke(Invoker invoker, Invocation invocation) { String traceId = invocation.getAttachment("traceId"); if (traceId != null) { TraceContext.setTraceId(traceId); } return invoker.invoke(invocation); } } *** ** * ** *** #### 最佳实践总结 1. **入口生成**:在请求入口(Filter/Interceptor)生成 traceId 2. **全链路传递**: * * HTTP:通过 Header 传递(**X-Trace-Id**) * RPC:通过调用附件传递 * MQ:通过消息头传递 1. **存储位置**: * * 同步请求:使用 **MDC** + **ThreadLocal** * 异步线程:使用 **TransmittableThreadLocal** 1. **日志集成** :在日志模板中添加 **%X{traceId}** 2. **异常处理**:确保在 finally 块中清理上下文 3. **ID 生成规则**: // 示例:服务前缀 + 时间戳 + 随机数 "SVC-" + System.currentTimeMillis() + "-" + ThreadLocalRandom.current().nextInt(10000) *** ** * ** *** #### 验证方式 1. 查看日志输出是否包含 traceId 2. 跨服务调用检查 Header 传递 3. 异步任务验证 traceId 一致性 @Async public void asyncTask() { log.info("Async task traceId: {}", MDC.get("traceId")); // 应非空 } 选择方案时: * 单体应用 → 方案一 * Spring MVC → 方案二 * 微服务架构 → 方案三 * 复杂异步场景 → 方案四 ### traceId实践: 生成traceId import com.alibaba.ttl.TransmittableThreadLocal; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.util.UUID; /** */ @Component @Slf4j public class LogInterceptor extends HandlerInterceptorAdapter { private final static ThreadLocal timeThreadLocal = new ThreadLocal<>(); public final static TransmittableThreadLocal requestIdThreadLocal = new TransmittableThreadLocal<>(); @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { timeThreadLocal.set(System.currentTimeMillis()); // 将当前 traceId 放入请求头 String traceId = request.getHeader("X-Trace-Id"); if (traceId == null) { traceId = UUID.randomUUID().toString(); } request.setAttribute("performanceRequestId", traceId); requestIdThreadLocal.set(traceId); String path = request.getServletPath(); log.info("==> 调用开始 访问服务uri:{}", path); return true; } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { try { long startTime = timeThreadLocal.get(); long endTime = System.currentTimeMillis(); log.info("==> 调用结束 uri:{} 耗时 {} ms", request.getServletPath(), endTime - startTime); }finally { timeThreadLocal.remove(); requestIdThreadLocal.remove(); } } } 对应转换类: import ch.qos.logback.classic.pattern.ClassicConverter; import ch.qos.logback.classic.spi.ILoggingEvent; /** */ public class MyLogBackConverter extends ClassicConverter { @Override public String convert(ILoggingEvent event) { // 获取你的参数,例如从 MDC 或者其他上下文中 // String myParam = event.getMDCPropertyMap().get("myParam"); String myParam = LogInterceptor.requestIdThreadLocal.get(); return myParam != null ? myParam : "TraceID"; } } 对应logback: INFO %d{yyyy-MM-dd HH:mm:ss} [%thread] [%myParam] %-5level %logger{36} - %msg %n ERROR %d{yyyy-MM-dd HH:mm:ss} [%thread] [%myParam] %-5level %logger{36} - %msg %n

相关推荐
JH30732 小时前
Java Stream API 在企业开发中的实战心得:高效、优雅的数据处理
java·开发语言·oracle
九月十九4 小时前
java使用aspose读取word里的图片
java·word
一 乐6 小时前
民宿|基于java的民宿推荐系统(源码+数据库+文档)
java·前端·数据库·vue.js·论文·源码
爱记录的小磊6 小时前
java-selenium自动化快速入门
java·selenium·自动化
鹏码纵横6 小时前
已解决:java.lang.ClassNotFoundException: com.mysql.jdbc.Driver 异常的正确解决方法,亲测有效!!!
java·python·mysql
weixin_985432116 小时前
Spring Boot 中的 @ConditionalOnBean 注解详解
java·spring boot·后端
Mr Aokey6 小时前
Java UDP套接字编程:高效实时通信的实战应用与核心类解析
java·java-ee
冬天vs不冷6 小时前
Java分层开发必知:PO、BO、DTO、VO、POJO概念详解
java·开发语言
hong_zc6 小时前
Java 文件操作与IO流
java·文件操作·io 流