name: 分布式追踪 description: 在实现分布式追踪、理解跟踪传播或调试跨服务问题时使用。涵盖OpenTelemetry、跨度上下文和跟踪关联。 allowed-tools: Read, Glob, Grep
分布式追踪
在微服务中实现分布式追踪并理解分布式系统中请求流的模式和实践。
何时使用此技能
- 在微服务中实现分布式追踪
- 调试跨服务请求问题
- 理解跟踪传播
- 选择追踪基础设施
- 关联日志、指标和追踪
为什么需要分布式追踪?
问题:请求流经多个服务
当出现故障时如何调试?
没有追踪:
用户 → API → ??? → ??? → 某处错误
有追踪:
用户 → API (50ms) → OrderService (20ms) → PaymentService (错误: 超时)
└── 请求流的完整可见性
核心概念
跟踪、跨度和上下文
跟踪:端到端请求旅程
├── 跨度:服务内的单个操作
│ ├── SpanID:唯一标识符
│ ├── ParentSpanID:指向父跨度的链接
│ ├── TraceID:在所有跨度中共享
│ ├── 操作名称:正在执行的操作
│ ├── 开始/结束时间:持续时间
│ ├── 状态:成功/错误
│ ├── 属性:键值元数据
│ └── 事件:时间点注释
│
└── 上下文:在服务边界传播
├── TraceID
├── SpanID
├── 跟踪标志
└── 跟踪状态
跟踪可视化
TraceID: abc123
服务 A (API Gateway)
├──────────────────────────────────────────────────────┤ 200ms
│
└─► 服务 B (Order Service)
├───────────────────────────────────┤ 150ms
│
├─► 服务 C (Inventory)
│ ├───────────────┤ 50ms
│
└─► 服务 D (Payment)
├───────────────────────┤ 80ms
│
└─► 外部 API
├─────────┤ 60ms
OpenTelemetry
概述
OpenTelemetry = 统一的观测性框架
组件:
┌─────────────────────────────────────────────────────┐
│ 应用程序 │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ SDK │ │ 跟踪器 │ │ 计量器 │ │
│ │ │ │ 提供者 │ │ 提供者 │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
└─────────────────────────────────────────────────────┘
│ │ │
└───────────────┼───────────────┘
▼
┌─────────────────────────┐
│ OTLP 导出器 │
└─────────────────────────┘
│
▼
┌─────────────────────────┐
│ 收集器 │
│ (可选) │
└─────────────────────────┘
│
┌───────────────┼───────────────┐
▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌─────────┐
│ Jaeger │ │ Zipkin │ │ Tempo │
└─────────┘ └─────────┘ └─────────┘
跟踪上下文传播
HTTP 头部 (W3C 跟踪上下文):
traceparent: 00-{trace-id}-{span-id}-{flags}
tracestate: vendor1=value1,vendor2=value2
示例:
traceparent: 00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01
│ │ │ └─ 已采样
│ │ └─ 父跨度 ID
│ └─ 跟踪 ID (128位)
└─ 版本
跨服务传播:
┌─────────────┐ ┌─────────────┐
│ 服务 A │ ─── HTTP ──────────►│ 服务 B │
│ │ traceparent: 00-... │ │
│ 创建跨度 │ │ 提取 │
│ 注入 │ │ 创建跨度 │
└─────────────┘ └─────────────┘
跨度属性
语义约定 (标准属性):
HTTP:
- http.method: GET, POST 等
- http.url: 完整 URL
- http.status_code: 200, 404, 500
- http.route: /users/{id}
数据库:
- db.system: postgresql, mysql
- db.statement: SELECT * FROM...
- db.operation: query, insert
RPC:
- rpc.system: grpc
- rpc.service: OrderService
- rpc.method: CreateOrder
自定义:
- user.id: 12345
- order.total: 99.99
- feature.flag: experiment_v2
追踪后端
Jaeger
特性:
- 开源 (CNCF)
- 内置 UI
- 多种存储后端
- OpenTelemetry 原生支持
架构:
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ 代理 │─►│ 收集器 │─►│ 存储 │
│ (可选) │ │ │ │ (Cassandra/ │
└─────────────┘ └─────────────┘ │ Elasticsearch)
│ └─────────────┘
▼
┌─────────────┐
│ 查询服务 │
└─────────────┘
│
▼
┌─────────────┐
│ UI │
└─────────────┘
Zipkin
特性:
- 成熟,经过实战检验
- 简单架构
- 低资源开销
- 良好生态系统支持
最适合:
- 较简单设置
- 低资源环境
- 熟悉 Zipkin 的团队
Grafana Tempo
特性:
- 对象存储后端 (成本低)
- 深度 Grafana 集成
- 基于日志的跟踪发现
- 支持示例
最适合:
- 重度使用 Grafana 的环境
- 成本敏感部署
- 大规模跟踪
云原生选项
| 提供商 | 服务 | 集成 |
|---|---|---|
| AWS | X-Ray | 原生 AWS 服务 |
| GCP | Cloud Trace | 原生 GCP 服务 |
| Azure | Application Insights | 原生 Azure 服务 |
| Datadog | APM | 全栈观测性 |
采样策略
为什么采样?
高流量系统生成数百万个跨度。
存储所有跨度成本高且通常不必要。
采样:收集跟踪子集
目标:保持足够数据以调试问题
同时管理成本
采样类型
1. 头部采样 (在跟踪开始时):
- 跟踪开始时做出决策
- 跨服务一致
- 简单但可能错过罕见事件
2. 尾部采样 (在跟踪完成后):
- 看到完整跟踪后做出决策
- 可以保留有趣跟踪 (错误、慢速)
- 需要缓冲跨度
- 更复杂基础设施
3. 优先级采样:
- 基于属性分配优先级
- 保留所有错误,采样正常流量
采样策略
基于速率:
- 采样 10% 的所有跟踪
- 简单,可预测成本
基于优先级:
- 100% 的错误
- 100% 的慢请求 (>1s)
- 5% 的正常请求
自适应:
- 根据流量调整速率
- 目标特定跟踪/秒
- 处理流量峰值
关联模式
日志-跟踪-指标
观测性三大支柱:
日志 ◄──────────► 跟踪 ◄──────────► 指标
│ │ │
│ trace_id │ 示例 │
│ span_id │ │
└──────────────────┴───────────────────┘
关联:
1. 添加 trace_id/span_id 到日志条目
2. 添加示例 (跟踪链接) 到指标
3. 从指标 → 跟踪 → 日志点击
日志关联
结构化日志带跟踪上下文:
{
"timestamp": "2024-01-15T10:30:00Z",
"level": "ERROR",
"message": "支付失败",
"trace_id": "abc123def456",
"span_id": "789xyz",
"service": "payment-service",
"user_id": "12345",
"error": "卡被拒绝"
}
日志聚合器查询:
trace_id:"abc123def456"
→ 查看此请求的所有日志
示例 (指标到跟踪)
带示例的指标:
http_request_duration{service="api"} = 2.5s
└── 示例: trace_id=abc123
当延迟飙升时:
1. 在仪表板看到指标飙升
2. 点击数据点
3. 直接跳转到慢跟踪
4. 查看导致延迟的确切原因
仪器化模式
自动仪器化
零代码仪器化:
- HTTP 客户端/服务器
- 数据库客户端
- 消息队列
- gRPC
优点:简单,全面
缺点:控制较少,更多噪音
手动仪器化
为业务逻辑添加跨度:
with tracer.start_span("process_order") as span:
span.set_attribute("order.id", order_id)
span.set_attribute("order.items", len(items))
result = process(order)
if result.error:
span.set_status(Status(StatusCode.ERROR))
span.record_exception(result.error)
优点:精确,业务相关
缺点:更多代码,维护
混合方法 (推荐)
1. 自动仪器化基础设施:
- HTTP、数据库、队列调用
2. 手动仪器化业务逻辑:
- 关键操作
- 业务指标
- 错误上下文
最佳实践
跨度设计
好的跨度名称:
- HTTP GET /api/orders/{id}
- ProcessPayment
- db.query users
坏的跨度名称:
- Handler (太通用)
- /api/orders/12345 (基数爆炸)
- doStuff (无意义)
属性指南
做:
- 使用语义约定
- 添加上下文 (user_id, order_id)
- 保持基数低
- 包含错误详情
不做:
- 添加 PII (个人可识别信息)
- 使用高基数值作为属性
- 添加大负载
- 包含敏感数据
性能考虑
1. 使用异步跨度导出
2. 适当采样
3. 限制属性数量
4. 使用跨度处理器批处理
5. 考虑跨度限制
用跟踪故障排除
常见模式
找到慢请求:
1. 按持续时间 > 阈值查询跟踪
2. 识别慢跨度
3. 检查跨度属性获取上下文
找到错误:
1. 按状态 = ERROR 查询跟踪
2. 查看错误跨度和上下文
3. 检查异常详情
找到依赖:
1. 从跟踪查看服务地图
2. 识别关键路径
3. 找到隐藏依赖
相关技能
observability-patterns- 三大支柱概述slo-sli-error-budget- 使用跟踪进行 SLIincident-response- 在事件中使用跟踪