5.4 KiB
5.4 KiB
tags, create time
| tags | create time | ||||
|---|---|---|---|---|---|
|
2026-05-05 |
链路追踪
概述
Tracing 回答的问题是:请求在哪一步慢了 / 失败了?——通过完整的调用链定位瓶颈和故障。
一次请求在多个服务中的完整调用路径:
flowchart LR
RID["Request<br/>trace_id: abc-123"]
subgraph SPANS["调用链 (Trace)"]
direction TB
S1["Span #1<br/>API Gateway<br/>5ms"]
S2["Span #2<br/>Order Service<br/>70ms"]
S3["Span #3<br/>Payment RPC<br/>160ms"]
S4["Span #4<br/>Inventory RPC<br/>70ms"]
S1 --> S2 --> S3
S2 --> S4
end
RID --> S1
style S3 fill:#ff9999
note["Span #3 耗时最长 → Payment 是瓶颈"]
S3 -.-> note
Trace/Span 核心概念
| 概念 | 说明 |
|---|---|
| Trace | 一次请求的完整调用链,全局唯一 trace_id |
| Span | 调用链中的一个执行单元,有独立的 span_id |
| Parent-Child | Span 树状结构,子 Span 继承父 Span 的 trace_id |
| Tags / Attributes | 键值对标注(HTTP method、status code) |
| Logs | Span 级别的时间戳事件(如 "DB query started") |
| Sampling | 按比例采样,降低存储开销 |
OpenTelemetry W3C Trace Context 标准
// HTTP Header
{
"traceparent": "00-{trace_id}-{span_id}-01",
"tracestate": "vendor=value"
}
格式解析:version(2字节) - trace_id(32字节) - span_id(16字节) - flags(2字节)
示例: 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01
│ └─── trace_id ───┘ └─ span_id ─┘ └flags─┘
上下文传播详解
trace_id 必须从上游传递到下游。不同通信场景有不同的传播方式:
HTTP 传播
func Middleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// 1. 提取入站 trace context
ctx := propagator.Extract(r.Context(), headerReader{r.Header})
// 2. 创建新的 Span
ctx, span := tracer.Start(ctx, "handleRequest")
defer span.End()
// 3. 将 trace context 注入出站请求
r = r.WithContext(ctx)
propagator.Inject(ctx, headerWriter{r.Header})
next.ServeHTTP(w, r)
})
}
MQ 消息头传播
// 生产者 - 手动注入 trace context
msg.Properties.Set("trace_id", extractTraceID(ctx))
msg.Properties.Set("span_id", extractSpanID(ctx))
// 消费者 - 恢复 trace context
traceID := msg.Properties.Get("trace_id")
span, _ := tracer.Start(traceContext, "consumeMessage",
trace.WithAttributes(attribute.String("mq.message.id", traceID)))
采样策略
flowchart TD
AllReq["全部请求"] --> Sampler{"采样决策"}
Sampler -->|"100%"| Core["核心链路全量采集"]
Sampler -->|"5~10%"| Normal["普通路径随机采样"]
Sampler -->|"100%"| Error["错误链路优先保留"]
Sampler -->|"0%"| Health["健康检查/内部心跳"]
Core --> Storage["Trace 存储"]
Normal --> Storage
Error --> Storage
Health --> Drop["丢弃"]
| 环境 | 采样率 | 说明 |
|---|---|---|
| 开发 / 测试 | 100% | 方便调试,无存储压力 |
| 生产 - 核心链路 | 100% | 下单、支付等关键路径 |
| 生产 - 普通路径 | 5~10% | 平衡成本和覆盖率 |
| 生产 - 错误链路 | 100% | 出错时优先保留 |
[!tip] 基于错误的智能采样
高级做法:正常路径低采样,一旦检测到 HTTP 5xx 或 DB 超时,立即提升当前请求的采样率。这样既省了存储,又能在出问题时有足够的数据回溯。
主流方案对比
| 方案 | 存储后端 | 侵入程度 | 中文支持 | 推荐理由 |
|---|---|---|---|---|
| OpenTelemetry | 任意 Jaeger/Prometheus/Loki | SDK + Auto-instrumentation | 良好 | CNCF 行业标准,未来趋势 |
| Jaeger | Cassandra / Elasticsearch | Agent / SDK | 社区支持 | Uber 开源,UI 优秀 |
| SkyWalking | MySQL / ES | 零侵入 Agent | ⭐ 完善 | 国产首选,开箱即用 |
渐进式落地路线
[!tip] 不要试图一步到位——先从 Tracing 开始,ROI 最高。
- 第一步:接入 Tracing(OpenTelemetry),覆盖核心链路(下单、支付),rate=100%
- 第二步:加 Metrics(Prometheus + Grafana),建立基础监控面板
- 第三步:集中 Logging(Loki / ELK),日志携带 trace_id 与 Tracing 关联
- 第四步:全量上 OpenTelemetry Collector,统一管理三件套
性能影响评估
| 指标 | 预期影响 |
|---|---|
| CPU 增加 | < 3%(异步批量上报) |
| 内存增加 | ~10MB(Span buffer) |
| 网络带宽 | 每条 Trace 约 2~5KB(取决于 Span 数量) |
| 延迟增加 | < 0.1ms(SDK 内处理不阻塞业务) |
[!note] 关键实践
- 使用 BatchProcessor 异步上报,不阻塞业务线程
- Span 不要嵌套过深,一个 HTTP 调用或 DB 查询对应一个 Span
- 给 Span 设置丰富的 Attributes,方便查询和过滤
- 错误 Span 标记
isError=true,便于快速定位问题链路
关联笔记
- 04-可观测性/01-Metrics监控 — Tracing 与 Metrics 互补
- 04-可观测性/02-日志系统 — trace_id 串联日志和追踪
- 04-可观测性/04-告警管理 — 基于 Trace 数据的异常检测告警