当前位置: 首页 > backend >正文

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/dataHeader: X-Trace-Id: abc123│▼
[服务A] → 从 Header 获取 traceId│▼
[服务A] → 调用服务B: POST /internal/dataHeader: 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格式的TraceIdpublic static String generateUUIDTraceId() {return UUID.randomUUID().toString().replace("-", "");}// 生成更短的UUID变种(32位)public static String generateShortUUIDTraceId() {return UUID.randomUUID().toString().replace("-", "");}// 生成带前缀的TraceIdpublic static String generatePrefixedTraceId(String prefix) {return prefix + "-" + generateShortUUIDTraceId();}
}// 过滤器实现
@WebFilter("/*")
public class TraceIdFilter implements Filter {@Overridepublic void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {try {// 1. 生成/传递 traceIdHttpServletRequest 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. 请求结束后清除 MDCMDC.clear();}}
}

日志配置(logback.xml)

<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"><encoder><!-- 添加 %X{traceId} 输出 --><pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] [%X{traceId}] %-5level %logger{36} - %msg%n</pattern></encoder>
</appender>

方案二:Spring Interceptor + ThreadLocal

// ThreadLocal 上下文
public class TraceContext {private static final ThreadLocal<String> 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 {@Overridepublic 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;}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {TraceContext.clear(); // 清除ThreadLocal}
}

注册拦截器:

@Configuration
public class WebConfig implements WebMvcConfigurer {@Autowired private TraceInterceptor traceInterceptor;@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(traceInterceptor);}
}

方案三:Spring Cloud Sleuth(微服务专用)

<!-- pom.xml 添加依赖 -->
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>

自动实现

  • 自动生成 traceId(格式:application-[traceId]-[spanId])
  • 通过 HTTP Headers 传递(X-B3-TraceId, X-B3-SpanId
  • 集成日志框架(MDC 自动注入)
  • 支持 OpenZipkin 分布式追踪

方案四:异步线程支持(关键扩展)

使用 TransmittableThreadLocal 解决线程池 traceId 传递问题:

// 增强版 ThreadLocal
public class AsyncTraceContext {private static final TransmittableThreadLocal<String> 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 {@Overridepublic Result invoke(Invoker<?> invoker, Invocation invocation) {invocation.setAttachment("traceId", TraceContext.getTraceId());return invoker.invoke(invocation);}
}// 提供者过滤器
public class DubboTraceProviderFilter implements Filter {@Overridepublic 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实践:

生成traceIdimport 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<Long> timeThreadLocal = new ThreadLocal<>();public final  static TransmittableThreadLocal<String> requestIdThreadLocal = new TransmittableThreadLocal<>();@Overridepublic 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;}@Overridepublic 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 {@Overridepublic String convert(ILoggingEvent event) {// 获取你的参数,例如从 MDC 或者其他上下文中
//        String myParam = event.getMDCPropertyMap().get("myParam");String myParam = LogInterceptor.requestIdThreadLocal.get();return myParam != null ? myParam : "TraceID";}
}

对应logback:

<configuration><property name="LOG_HOME" value="logs/xxxx-service" /><conversionRule conversionWord="myParam" converterClass="com.xxx.config.MyLogBackConverter" /><appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"><filter class="ch.qos.logback.classic.filter.ThresholdFilter"><!-- 只接受INFO级别的日志 --><level>INFO</level></filter><encoder><pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] [%myParam] %-5level %logger{36} - %msg %n</pattern></encoder></appender><appender name="STDOUT_ERROR" class="ch.qos.logback.core.ConsoleAppender"><filter class="ch.qos.logback.classic.filter.ThresholdFilter"><!-- 只接受INFO级别的日志 --><level>ERROR</level></filter><encoder><pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] [%myParam] %-5level %logger{36} - %msg %n</pattern></encoder></appender><root level="INFO"><appender-ref ref="STDOUT" /><appender-ref ref="STDOUT_ERROR" /></root></configuration>
http://www.xdnf.cn/news/14150.html

相关文章:

  • 贵州建筑安全员C证理论考试题库
  • CHS和LBA的地址与的磁盘关联
  • C# 中委托和事件的深度剖析与应用场景
  • 求解偏微分方程组的通解
  • 小智AI为何要用MQTT+UDP?怎么接入MQTT?
  • Spring Boot 启动原理(SpringApplication.run(...) 流程)
  • 【Playwright MCP 实战分享:AI时代的浏览器自动化测试】
  • 销售预测的方法与模型(三)丨安全库存与再订货(补货)
  • AndroidMJ-基础-05
  • 数字人分身系统之数字人克隆功能板块开发,支持OEM
  • 一文了解sonar的搭建和使用
  • 基于openlayers开发北斗应用支撑平台
  • 1.2、SDH的复用结构
  • 2025年真实面试问题汇总(三)
  • 开启奇妙的 VR 刀剑博物馆之刀剑世界​
  • 大模型及agent开发1——基础知识及实现具备Funcation Calling功能的智能电商客服
  • 在C#中的锁
  • druid 数据库密码加密
  • FEMFAT许可与软件版本对应关系
  • 深度解析一下 llama.cpp 的源代码
  • 每日算法刷题Day30 6.13:leetcode二分答案2道题,用时1h10min
  • 打印机共享问题一键解决,附带设置维护工具
  • Python Day50 学习(仍为日志Day19的内容复习)
  • kafka版本升级3.5.1-->3.9.1(集群或单体步骤一致)
  • B/S架构
  • 上海市计算机学会竞赛平台2022年4月月赛丙组步步高
  • Qoppa Software提供的15款PDF产品组件科学学习
  • HarmonyOS 组件复用面试宝典 [特殊字符]
  • 【技术工具】源码管理 - GIT工具
  • Java 传输较大数据的相关问题解析和面试问答