This repository has been archived on 2026-05-24. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
all-in-kingsoft/hzh/MS/02-服务治理/03-分布式追踪.md
T
2026-05-15 16:26:14 +08:00

210 lines
6.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
tags: [microservice, distributed-tracing, opentelemetry, jaeger, skywalking]
create time: 2026-05-05
---
# 分布式链路追踪
## 概述
单个服务的日志只能告诉你局部信息。分布式链路追踪将一次请求跨越多个服务的完整调用链串起来,形成全局视图。
> [!question] 定位问题的困难
> 用户反映下单慢,你的系统由订单、支付、库存、会员 4 个服务串联而成。没有工具的情况下,你要怎么知道是哪个服务拖慢了整体响应时间?
## 核心概念
```mermaid
flowchart TB
Req["请求 trace_id=abc123"] --> Span1["Span #1<br/>API Gateway<br/>5ms"]
Span1 --> Span2["Span #2<br/>Order Service<br/>70ms"]
Span1 --> Span3["Span #3<br/>User Service<br/>15ms"]
Span2 --> Span4["Span #4<br/>Inventory DB Query<br/>60ms"]
style Span2 fill:#ff9999
style Span4 fill:#ffcc99
```
| 概念 | 说明 |
|------|------|
| **Trace** | 一次完整请求的调用链,由一个唯一的 `trace_id` 标识 |
| **Span** | 链路中的一个执行片段(如一次 HTTP 调用、一次 SQL 查询),有独立的 `span_id` |
| **Parent-Child** | Span 之间通过 `parent_span_id` 建立父子关系,形成树状结构 |
| **Context Propagation** | 通过 Header 传递 `trace_id`/`span_id`,贯穿整条链路 |
| **Sampling** | 不是每条请求都采样,按比例或策略选择,降低存储开销 |
### Trace/Span 数据结构
```json
{
"traceId": "abc123def456...",
"spans": [
{
"spanId": "span-001",
"parentId": null,
"operationName": "POST /orders",
"startTime": "2026-05-05T10:00:00.000Z",
"durationMs": 120,
"tags": {
"http.method": "POST",
"http.url": "/api/orders",
"http.status_code": 200
},
"logs": [
{
"timestamp": "2026-05-05T10:00:00.050Z",
"fields": [{"key": "event", "value": "order.created"}]
}
]
},
{
"spanId": "span-002",
"parentId": "span-001",
"operationName": "GetUserById (gRPC)",
"startTime": "2026-05-05T10:00:00.010Z",
"durationMs": 15,
"tags": {
"rpc.system": "grpc",
"rpc.service": "UserService",
"rpc.method": "GetUser"
}
}
]
}
```
## 上下文传播 (Context Propagation)
`trace_id` 如何从上游服务传递到下游?
### HTTP 场景:W3C Trace Context 标准
```json
// HTTP Header 中实际传递的字段
{
"traceparent": "00-abc123def456...-789ghi012jkl-01",
"tracestate": "congo=t61rcWkgMzE"
}
```
格式:`version-trace_id-span_id-flags`
```go
// OpenTelemetry SDK 自动处理 context 注入和提取
func Middleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// 从入站请求提取 trace context
ctx := propagator.Extract(r.Context(), headerReader{r.Header})
// 创建新的 span
ctx, span := tracer.Start(ctx, "handleOrder")
defer span.End()
// 将 trace context 注入到出站请求
r = r.WithContext(ctx)
propagator.Inject(ctx, headerWriter{r.Header})
next.ServeHTTP(w, r)
})
}
```
### MQ 场景:Message Header 透传
```go
// RocketMQ 生产者 - 自动注入 trace context
msg := rocketmq.NewMessage("order-created", payload)
msg.WithProperty("trace_id", currentTraceID) // 手动注入
msg.WithProperty("span_id", currentSpanID)
// RocketMQ 消费者 - 恢复 trace context
traceID := msg.GetProperty("trace_id")
span, _ := tracer.Start(ctx, "processOrderCreated",
trace.WithSpanKind(trace.SpanKindConsumer))
```
## 主流方案对比
| 方案 | 协议 | 存储后端 | 侵入程度 | 特色能力 |
|------|------|---------|---------|---------|
| **OpenTelemetry** | OTLP | Prometheus/Jaeger/Zipkin | SDK + Auto-Instrumentation | CNCF 标准,厂商中立,未来趋势 |
| **Jaeger** | Jaeger native | Cassandra/Elasticsearch | Agent / SDK | Uber 开源,UI 友好,支持业务 Tags |
| **SkyWalking** | SkyWalking | MySQL/Elasticsearch/ES | 零侵入 Java Agent | 国产,中文文档完善,APM 一体 |
> [!tip] 选型建议
> 新项目优先选 **OpenTelemetry**——它是行业标准,未来会被所有工具支持。如果团队需要开箱即用的 APM,**SkyWalking** 的 Java Agent 零侵入方案是快速上手的最佳选择。
## OpenTelemetry Go 实战
```go
// 初始化 TracerProvider
provider := sdktrace.NewTracerProvider(
sdktrace.WithBatcherExporter(exporter), // 异步批量上报,不阻塞
)
defer provider.Shutdown(context.Background())
tracer := provider.Tracer("order-service")
func handleOrder(w http.ResponseWriter, r *http.Request) {
ctx, span := tracer.Start(r.Context(), "handleOrder")
defer span.End()
// 设置丰富的属性,方便查询和过滤
span.SetAttributes(
attribute.String("http.method", r.Method),
attribute.Int("http.status_code", http.StatusOK),
attribute.String("user.id", getUserID(r)),
)
// 调用下游服务(trace context 自动传播)
resp, err := callInventoryService(ctx)
if err != nil {
span.RecordError(err)
span.SetStatus(codes.Error, err.Error())
w.WriteHeader(http.StatusBadGateway)
return
}
w.Write(resp.Body)
}
```
## 采样策略
```mermaid
flowchart LR
AllReq["全部请求"] --> Sampler{"采样决策"}
Sampler -->|"100%"| Core["核心链路全量采集"]
Sampler -->|"5~10%"| Normal["普通路径随机采样"]
Sampler -->|"0%"| Health["健康检查/内部心跳"]
Core --> Storage["Trace 存储"]
Normal --> Storage
Health --> Drop["丢弃"]
```
| 环境 | 采样率 | 理由 |
|------|--------|------|
| **开发 / 测试** | 100% | 方便调试,无存储压力 |
| **生产 - 核心链路** | 100% | 下单、支付等关键路径必须全量 |
| **生产 - 普通路径** | 5~10% | 平衡成本和覆盖率 |
| **生产 - 错误链路** | 100% | 出错时的请求优先保留 |
> [!note] 基于错误的智能采样
>
> 高级做法:**正常路径低采样,一旦检测到错误立即提升当前请求的采样率**。这样既省了存储,又能在出问题时有足够的数据回溯。
## 渐进式落地路线
> [!tip] 不要试图一开始就采集全部 Span
>
> 1. **第一步**:先接 Tracing,覆盖核心链路(下单、支付),rate=100%
> 2. **第二步**:加入 Metrics 监控(Prometheus + Grafana)
> 3. **第三步**:集中 Logging(Loki / ELK),与 trace_id 关联
> 4. **第四步**:全量上 OpenTelemetry Collector,统一管理
## 关联笔记
- [[02-服务治理/01-API网关]] — API Gateway 可以在入口处注入 trace_id
- [[04-可观测性/03-链路追踪]] — 更详细的链路追踪设计方法论
- [[02-服务治理/07-配置管理]] — 配置中心的动态刷新可以联动调整采样率