分布式链路追踪原理
约 1636 字大约 5 分钟
distributedtracing
2025-06-05
概述
分布式链路追踪(Distributed Tracing)是微服务架构中不可或缺的可观测性工具。它通过记录请求在多个服务间的流转路径和耗时,帮助开发者理解系统行为、定位性能瓶颈和排查故障。本文深入解析分布式追踪的核心模型、上下文传播机制和主流实现方案。
核心数据模型
Trace、Span、SpanContext
- Trace:代表一个完整请求的生命周期,由唯一的Trace ID标识
- Span:Trace中的一个操作单元(如一次RPC调用、数据库查询),包含操作名、开始时间、持续时间等
- SpanContext:跨进程传播的上下文信息,包含Trace ID、Span ID、采样标志等
时间线视图
|--- Span A: API Gateway (0-350ms) --------------------------------|
|--- Span B: Order Service (20-300ms) ----------------------|
|--- Span C: Payment Service (40-190ms) -------|
|--- Span E: DB Query (60-100ms) ---|
|--- Span D: Inventory Service (50-150ms) ----|Span数据结构
public class Span {
String traceId; // 全局唯一的追踪ID
String spanId; // 当前Span的ID
String parentSpanId; // 父Span的ID(根Span为null)
String operationName; // 操作名(如 "GET /api/orders")
long startTime; // 开始时间(微秒精度)
long duration; // 持续时间(微秒)
String serviceName; // 服务名
SpanKind kind; // SPAN_KIND: CLIENT, SERVER, PRODUCER, CONSUMER, INTERNAL
Map<String, String> tags; // 标签(key-value,用于查询过滤)
List<LogEntry> logs; // 事件日志
List<SpanLink> links; // 关联的其他Span
SpanStatus status; // 状态:OK, ERROR
// 常用Tags:
// http.method, http.url, http.status_code
// db.system, db.statement
// rpc.system, rpc.service, rpc.method
// error (boolean)
}上下文传播(Context Propagation)
W3C TraceContext标准
W3C TraceContext是当前推荐的标准传播格式,通过HTTP头传递:
traceparent: 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01
│ │ │ │
│ │ │ └─ 标志位(01=采样)
│ │ └─ Parent Span ID (16hex)
│ └─ Trace ID (32hex)
└─ 版本号
tracestate: vendor1=value1,vendor2=value2
供应商特定的附加信息B3传播格式
Zipkin使用的传播格式,支持单头和多头两种形式:
# 多头形式
X-B3-TraceId: 4bf92f3577b34da6a3ce929d0e0e4736
X-B3-SpanId: 00f067aa0ba902b7
X-B3-ParentSpanId: 0020000000000001
X-B3-Sampled: 1
# 单头形式(紧凑)
b3: 4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-1-0020000000000001传播实现
// 使用OpenTelemetry实现上下文传播
public class TracingFilter implements Filter {
private final Tracer tracer;
private final TextMapPropagator propagator;
@Override
public void doFilter(ServletRequest req, ServletResponse resp,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpReq = (HttpServletRequest) req;
// 从HTTP头中提取上下文
Context extractedContext = propagator.extract(
Context.current(), httpReq, new HttpHeaderGetter());
// 创建新Span
Span span = tracer.spanBuilder(httpReq.getMethod() + " " + httpReq.getRequestURI())
.setParent(extractedContext)
.setSpanKind(SpanKind.SERVER)
.setAttribute("http.method", httpReq.getMethod())
.setAttribute("http.url", httpReq.getRequestURL().toString())
.startSpan();
try (Scope scope = span.makeCurrent()) {
chain.doFilter(req, resp);
span.setAttribute("http.status_code",
((HttpServletResponse) resp).getStatus());
} catch (Exception e) {
span.setStatus(StatusCode.ERROR, e.getMessage());
span.recordException(e);
throw e;
} finally {
span.end();
}
}
}
// 发起下游调用时注入上下文
public class TracingHttpClient {
public HttpResponse call(String url) {
HttpRequest request = HttpRequest.newBuilder().uri(URI.create(url)).build();
Span span = tracer.spanBuilder("HTTP " + url)
.setSpanKind(SpanKind.CLIENT)
.startSpan();
try (Scope scope = span.makeCurrent()) {
// 将当前上下文注入HTTP头
propagator.inject(Context.current(), request, new HttpHeaderSetter());
return httpClient.send(request);
} finally {
span.end();
}
}
}采样策略
全量采集所有Trace会带来巨大的存储和网络开销,因此需要采样。
// OpenTelemetry采样器配置
SdkTracerProvider tracerProvider = SdkTracerProvider.builder()
// 头部采样:10%概率
.setSampler(Sampler.traceIdRatioBased(0.1))
// 或:父级采样(遵循上游的采样决定)
.setSampler(Sampler.parentBased(Sampler.traceIdRatioBased(0.1)))
.build();头部采样 vs 尾部采样
| 特性 | 头部采样 | 尾部采样 |
|---|---|---|
| 采样决策时机 | 请求入口 | 请求完成后 |
| 资源开销 | 低 | 高(需缓存完整Trace) |
| 错误覆盖 | 可能遗漏错误请求 | 可保证错误请求被采集 |
| 实现复杂度 | 低 | 高 |
| 代表实现 | OpenTelemetry默认 | OpenTelemetry Collector |
OpenTelemetry SDK
OpenTelemetry(OTel)是CNCF的可观测性标准,统一了OpenTracing和OpenCensus。
// Go OpenTelemetry示例
import (
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp"
"go.opentelemetry.io/otel/sdk/trace"
)
func initTracer() func() {
exporter, _ := otlptracehttp.New(context.Background(),
otlptracehttp.WithEndpoint("otel-collector:4318"),
)
tp := trace.NewTracerProvider(
trace.WithBatcher(exporter),
trace.WithResource(resource.NewWithAttributes(
semconv.SchemaURL,
semconv.ServiceNameKey.String("order-service"),
semconv.ServiceVersionKey.String("1.0.0"),
)),
trace.WithSampler(trace.TraceIDRatioBased(0.1)),
)
otel.SetTracerProvider(tp)
return func() { tp.Shutdown(context.Background()) }
}
func handleRequest(ctx context.Context) {
tracer := otel.Tracer("order-handler")
ctx, span := tracer.Start(ctx, "ProcessOrder",
oteltrace.WithSpanKind(oteltrace.SpanKindServer),
)
defer span.End()
span.SetAttributes(
attribute.String("order.id", "12345"),
attribute.Int("order.items", 3),
)
// 调用下游服务(自动传播上下文)
callPaymentService(ctx)
}主流追踪系统架构
Jaeger
Uber开源,CNCF毕业项目。支持多种存储后端(Cassandra、Elasticsearch、Kafka)。
Zipkin
Twitter开源的老牌追踪系统。架构简单,适合中小规模。
Grafana Tempo
Grafana Labs开源,专注于大规模Trace存储。使用对象存储(S3/GCS),成本低。与Grafana深度集成,支持TraceQL查询语言。
| 特性 | Jaeger | Zipkin | Tempo |
|---|---|---|---|
| 存储 | Cassandra/ES/Kafka | MySQL/ES/Cassandra | 对象存储(S3) |
| 查询语言 | 标签过滤 | 标签过滤 | TraceQL |
| 成本 | 中 | 低 | 最低 |
| 生态集成 | Kubernetes/OTel | Spring Cloud | Grafana |
| 采样 | 头部+自适应 | 头部 | 头部+尾部(Collector) |
Trace分析实践
关键实践:
- 为每个Span添加有意义的标签(如用户ID、订单ID)
- 在错误Span中记录异常堆栈
- 使用Span Events记录关键业务事件
- 将Trace ID传递到日志系统,实现日志-追踪关联
- 合理设置采样率,平衡可观测性和成本
总结
分布式链路追踪通过Trace-Span层次模型和上下文传播机制,实现了对请求在微服务间流转的全链路可视化。OpenTelemetry作为统一标准,提供了语言无关的SDK和Collector,支持与Jaeger、Zipkin、Tempo等后端对接。采样策略的选择需要在成本和可观测性之间取得平衡,尾部采样虽然开销较大但能确保关键请求(错误、慢请求)不被遗漏。
贡献者
更新日志
2026/3/14 13:09
查看所有更新日志
9f6c2-feat: organize wiki content and refresh site setup于