Files
cs-note/hzh/MS/04-可观测性/03-链路追踪.md
T
2026-05-24 11:42:38 +08:00

5.4 KiB
Raw Blame History

tags, create time
tags create time
microservice
tracing
opentelemetry
context-propagation
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 最高。

  1. 第一步:接入 Tracing(OpenTelemetry),覆盖核心链路(下单、支付),rate=100%
  2. 第二步:加 Metrics(Prometheus + Grafana),建立基础监控面板
  3. 第三步:集中 Logging(Loki / ELK),日志携带 trace_id 与 Tracing 关联
  4. 第四步:全量上 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,便于快速定位问题链路

关联笔记