Jaeger / Zipkin — 分布式链路追踪
分布式追踪基础
核心概念
Trace(链路):一次完整的请求链路,由多个 Span 组成
└── Span(跨度):一次操作的时间段
├── SpanContext:Trace ID + Span ID(跨进程传播)
├── Tags:键值对标签(可索引)
├── Logs:时间点事件
└── References:父子关系
示例:
Trace ID: abc123
Span: frontend.handleRequest (0ms ~ 150ms)
└── Span: order-service.createOrder (5ms ~ 120ms)
├── Span: db.insert (10ms ~ 30ms)
└── Span: payment-service.charge (40ms ~ 110ms)
└── Span: db.update (45ms ~ 60ms)传播协议
HTTP Header 传播(W3C TraceContext 标准):
traceparent: 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01
版本 TraceID(16字节) SpanID(8字节) 标志
B3 格式(Zipkin):
X-B3-TraceId: 4bf92f3577b34da6a3ce929d0e0e4736
X-B3-SpanId: 00f067aa0ba902b7
X-B3-Sampled: 1Jaeger 架构
应用(Jaeger Client SDK)
└──► Jaeger Agent(UDP,本地收集)
└──► Jaeger Collector(处理、存储)
└──► Elasticsearch / Cassandra(存储)
└──► Jaeger Query(查询 API)
└──► Jaeger UI(可视化)部署(All-in-One,开发环境)
bash
docker run -d --name jaeger \
-e COLLECTOR_ZIPKIN_HOST_PORT=:9411 \
-p 5775:5775/udp \
-p 6831:6831/udp \
-p 6832:6832/udp \
-p 5778:5778 \
-p 16686:16686 \ # Jaeger UI
-p 14268:14268 \ # HTTP Collector
-p 14250:14250 \ # gRPC Collector
-p 9411:9411 \ # Zipkin 兼容
jaegertracing/all-in-one:latestSpring Boot 集成(OpenTelemetry)
xml
<dependency>
<groupId>io.opentelemetry.instrumentation</groupId>
<artifactId>opentelemetry-spring-boot-starter</artifactId>
<version>2.1.0-alpha</version>
</dependency>yaml
# application.yml
otel:
service:
name: order-service
exporter:
otlp:
endpoint: http://jaeger:4317 # OTLP gRPC
traces:
sampler:
arg: "0.1" # 采样率 10%
type: parentbased_traceidratio
propagators: tracecontext,baggage,b3手动创建 Span
java
@Autowired
private Tracer tracer;
public Order createOrder(OrderRequest request) {
Span span = tracer.spanBuilder("createOrder")
.setAttribute("order.user_id", request.getUserId())
.setAttribute("order.amount", request.getAmount())
.startSpan();
try (Scope scope = span.makeCurrent()) {
// 业务逻辑
Order order = orderRepository.save(buildOrder(request));
// 添加事件
span.addEvent("order.saved", Attributes.of(
AttributeKey.stringKey("order.id"), order.getId()
));
// 调用支付服务(自动传播 TraceContext)
paymentService.charge(order);
span.setStatus(StatusCode.OK);
return order;
} catch (Exception e) {
span.setStatus(StatusCode.ERROR, e.getMessage());
span.recordException(e);
throw e;
} finally {
span.end();
}
}跨服务传播
java
// RestTemplate 自动传播(通过 OTEL 自动插桩)
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
// Feign 自动传播
@FeignClient(name = "payment-service")
public interface PaymentClient {
@PostMapping("/api/payments")
PaymentResult charge(@RequestBody ChargeRequest request);
}
// Kafka 消息传播
@KafkaListener(topics = "order-events")
public void handleOrderEvent(ConsumerRecord<String, String> record) {
// OTEL 自动从 Kafka Header 提取 TraceContext
// 当前 Span 自动关联到生产者的 Trace
}Zipkin 架构
应用(Brave/OTEL SDK)
└──► Zipkin Server(收集、存储、查询)
└──► MySQL / Elasticsearch / CassandraSpring Cloud Sleuth + Zipkin(旧方案)
xml
<!-- Spring Boot 2.x 方案(3.x 已迁移到 OTEL) -->
<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>yaml
spring:
zipkin:
base-url: http://zipkin:9411
sleuth:
sampler:
probability: 0.1 # 10% 采样采样策略
java
// 头部采样(在入口处决定是否采样)
// 优点:简单,性能好
// 缺点:可能错过低频错误
// 尾部采样(收集完整 Trace 后决定是否保留)
// 优点:可以保留所有错误 Trace
// 缺点:需要缓存完整 Trace,内存开销大
// 推荐策略:
// - 正常请求:1%~10% 采样
// - 错误请求:100% 采样
// - 慢请求(>1s):100% 采样yaml
# OpenTelemetry Collector 尾部采样配置
processors:
tail_sampling:
decision_wait: 10s
policies:
- name: errors-policy
type: status_code
status_code: {status_codes: [ERROR]}
- name: slow-traces-policy
type: latency
latency: {threshold_ms: 1000}
- name: probabilistic-policy
type: probabilistic
probabilistic: {sampling_percentage: 5}故障处理案例
案例一:Trace 断链
现象:在 Jaeger UI 中,Trace 在某个服务处断开,无法看到完整链路。
排查:
java
// 检查 TraceContext 是否正确传播
// 在断链处的服务日志中查找 trace_id
// 确认 HTTP Header 是否包含 traceparent
// 常见原因:
// 1. 使用了自定义 HTTP 客户端,未集成 OTEL
// 2. 异步线程切换时 Context 丢失解决:
java
// 异步场景:手动传播 Context
ExecutorService executor = Context.taskWrapping(Executors.newFixedThreadPool(10));
// 或手动传播
Context context = Context.current();
executor.submit(() -> {
try (Scope scope = context.makeCurrent()) {
// 异步操作
}
});案例二:Jaeger 存储空间不足
现象:Jaeger 开始丢弃 Span,存储告警。
解决:
yaml
# 配置 Span 保留时间
# Elasticsearch 后端
PUT _ilm/policy/jaeger-policy
{
"policy": {
"phases": {
"delete": {
"min_age": "7d",
"actions": { "delete": {} }
}
}
}
}案例三:高采样率导致性能下降
现象:开启 100% 采样后,应用 CPU 和内存使用率明显上升。
解决:
- 降低采样率(生产环境 1%~10% 足够)
- 使用异步 Exporter(避免阻塞业务线程)
- 使用 OTEL Collector 作为中间层,减少应用侧压力
Jaeger vs Zipkin 对比
| 维度 | Jaeger | Zipkin |
|---|---|---|
| 开发者 | Uber | |
| 存储后端 | ES/Cassandra/Badger | MySQL/ES/Cassandra |
| UI | 功能丰富 | 简洁 |
| 协议 | Jaeger/Zipkin/OTLP | Zipkin/OTLP |
| 性能分析 | 支持 | 基础 |
| 依赖图 | 支持 | 支持 |
| 适用场景 | 大规模,功能需求多 | 轻量,快速上手 |