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

相关推荐
维尔切14 分钟前
Linux中基于Centos7使用lamp架构搭建个人论坛(wordpress)
linux·运维·架构
他日若遂凌云志20 分钟前
深入剖析 Fantasy 框架的消息设计与序列化机制:协同架构下的高效转换与场景适配
后端
快手技术36 分钟前
快手Klear-Reasoner登顶8B模型榜首,GPPO算法双效强化稳定性与探索能力!
后端
二闹1 小时前
三个注解,到底该用哪一个?别再傻傻分不清了!
后端
用户49055816081251 小时前
当控制面更新一条 ACL 规则时,如何更新给数据面
后端
林太白1 小时前
Nuxt.js搭建一个官网如何简单
前端·javascript·后端
码事漫谈1 小时前
VS Code 终端完全指南
后端
知白守黑2671 小时前
lamp架构部署wordpress
架构
该用户已不存在1 小时前
OpenJDK、Temurin、GraalVM...到底该装哪个?
java·后端
怀刃2 小时前
内存监控对应解决方案
后端