167 lines
5.4 KiB
Markdown
167 lines
5.4 KiB
Markdown
|
|
---
|
|||
|
|
tags: [microservice, tracing, opentelemetry, context-propagation]
|
|||
|
|
create time: 2026-05-05
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
# 链路追踪
|
|||
|
|
|
|||
|
|
## 概述
|
|||
|
|
|
|||
|
|
Tracing 回答的问题是:**请求在哪一步慢了 / 失败了?**——通过完整的调用链定位瓶颈和故障。
|
|||
|
|
|
|||
|
|
一次请求在多个服务中的完整调用路径:
|
|||
|
|
|
|||
|
|
```mermaid
|
|||
|
|
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 标准
|
|||
|
|
|
|||
|
|
```json
|
|||
|
|
// 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 传播
|
|||
|
|
|
|||
|
|
```go
|
|||
|
|
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 消息头传播
|
|||
|
|
|
|||
|
|
```go
|
|||
|
|
// 生产者 - 手动注入 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)))
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 采样策略
|
|||
|
|
|
|||
|
|
```mermaid
|
|||
|
|
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`,便于快速定位问题链路
|
|||
|
|
|
|||
|
|
## 关联笔记
|
|||
|
|
|
|||
|
|
- [[04-可观测性/01-Metrics监控]] — Tracing 与 Metrics 互补
|
|||
|
|
- [[04-可观测性/02-日志系统]] — trace_id 串联日志和追踪
|
|||
|
|
- [[04-可观测性/04-告警管理]] — 基于 Trace 数据的异常检测告警
|