本文目录导读:

在Java中实现链路追踪,主要有两种方式:使用成熟的开源框架(推荐)或手动实现简易版本(用于理解原理)。
以下从原理和最佳实践两个角度给出具体案例。
核心原理(理解基础)
链路追踪的核心是给每个请求分配一个全局唯一的 Trace ID,并在每个服务内部调用时生成 Span ID 来记录调用层级,这些信息会通过HTTP头或RPC上下文在服务间传递。
// 核心数据结构
public class TraceContext {
private String traceId; // 全局唯一,一次请求不变
private String spanId; // 当前服务/方法的唯一标识
private String parentSpanId; // 父服务/方法的ID
}
成熟方案:Spring Cloud Sleuth + Zipkin(推荐)
这是最常用的Java链路追踪方案,可以无侵入地集成到Spring Boot/Cloud项目中。
添加依赖(Spring Boot项目)
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-sleuth-zipkin</artifactId>
</dependency>
<!-- Zipkin数据发送到Sleuth Collector -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>
配置application.yml
spring:
application:
name: service-a
sleuth:
sampler:
probability: 1.0 # 采样率,生产环境建议0.1
zipkin:
base-url: http://localhost:9411 # Zipkin服务端地址
sender:
type: web # 通过HTTP发送追踪数据
启动Zipkin服务端
使用Docker快速启动:
docker run -d -p 9411:9411 openzipkin/zipkin
编写服务调用示例
ServiceA(调用ServiceB):
@RestController
public class ServiceAController {
@Autowired
private RestTemplate restTemplate;
@GetMapping("/service-a")
public String callServiceB() {
// 注意:RestTemplate需要声明为Bean,Sleuth会自动注入Trace信息
String result = restTemplate.getForObject("http://localhost:8082/service-b", String.class);
return "ServiceA -> " + result;
}
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
ServiceB:
@RestController
public class ServiceBController {
@GetMapping("/service-b")
public String serviceB() {
return "ServiceB Response";
}
}
结果验证
访问:http://localhost:8081/service-a
打开Zipkin UI:http://localhost:9411,可以看到完整的调用链路,包括:
- Trace ID(全局唯一)
- 每个服务的耗时
- 调用关系拓扑图
手动实现简易链路追踪(理解原理)
如果你想理解底层实现,可以手动实现一个微型版本。
上下文存储(使用ThreadLocal)
public class TraceContext {
private static final ThreadLocal<TraceInfo> TL = new ThreadLocal<>();
public static void setTraceInfo(TraceInfo info) {
TL.set(info);
}
public static TraceInfo getTraceInfo() {
return TL.get();
}
public static void clear() {
TL.remove();
}
// 生成Trace ID和Span ID
public static String generateId() {
return UUID.randomUUID().toString().replace("-", "").substring(0, 16);
}
public static class TraceInfo {
private String traceId;
private String spanId;
private String parentSpanId;
// getters/setters...
}
}
Filter自动追踪入口请求
@WebFilter("/*")
public class TraceFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
// 从请求头获取Trace信息(或创建新的)
String traceId = httpRequest.getHeader("X-Trace-Id");
if (traceId == null) {
traceId = TraceContext.generateId();
}
// 生成当前Span
String spanId = TraceContext.generateId();
String parentSpanId = httpRequest.getHeader("X-Span-Id");
TraceContext.TraceInfo info = new TraceContext.TraceInfo();
info.setTraceId(traceId);
info.setSpanId(spanId);
info.setParentSpanId(parentSpanId);
try {
TraceContext.setTraceInfo(info);
// 在响应头中传递Trace信息
HttpServletResponse httpResponse = (HttpServletResponse) response;
httpResponse.setHeader("X-Trace-Id", traceId);
httpResponse.setHeader("X-Span-Id", spanId);
chain.doFilter(request, response);
} finally {
TraceContext.clear();
}
}
}
RestTemplate自动传递Trace信息(AsyncClientHttpRequestInterceptor)
public class TraceInterceptor implements ClientHttpRequestInterceptor {
@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body,
ClientHttpRequestExecution execution) throws IOException {
TraceContext.TraceInfo info = TraceContext.getTraceInfo();
if (info != null) {
request.getHeaders().add("X-Trace-Id", info.getTraceId());
request.getHeaders().add("X-Span-Id", TraceContext.generateId()); // 生成子Span
}
return execution.execute(request, body);
}
}
更完整的方案对比
| 方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| Sleuth + Zipkin | 无侵入,功能完整,可视化好 | 依赖Spring Boot,有一定学习成本 | Spring Cloud微服务 |
| SkyWalking | 高性能,无侵入,支持多种语言 | 需要部署Agent和Server | 大规模分布式系统 |
| Jaeger | CNCF项目,兼容OpenTracing标准 | 需要部署Collector+UI | 容器化/K8s环境 |
| 手动实现 | 理解原理,控制力强 | 开发工作量大,不完整 | 学习目的或极简单场景 |
关键注意事项
-
异步场景:使用
Async注解或线程池时需要手动传递Trace上下文@Async public CompletableFuture<String> doAsync() { // 在异步方法中Trace上下文会丢失,需要显式传递 TraceContext.setTraceInfo(currentTraceInfo); } -
消息队列:发送MQ时需要将Trace ID作为消息头传递
// RabbitMQ示例 MessageProperties props = new MessageProperties(); props.setHeader("X-Trace-Id", traceId); -
生产环境采样率:不要设为1.0,否则会产生大量存储开销,建议0.1或更低
-
存储选型:Zipkin默认使用内存存储,生产推荐Elasticsearch
- 快速入门:使用Spring Cloud Sleuth + Zipkin,配置简单,功能完整
- 深入学习:手动实现一个简易版本,理解ThreadLocal和Filter机制
- 生产选择:根据技术栈选择SkyWalking(推荐Java生态)或Jaeger(容器友好)