分布式链路追踪:从日志拼查到 Trace 一键#
单体时代排查:一个请求,一个日志文件,顺序看下来就行。微服务时代:一个请求经过 8 个服务,日志散在 8 个地方,“拼日志"是每个排查人的噩梦。链路追踪(Distributed Tracing)就是解决这个问题的。
一、痛点#
二、核心概念#
Trace 与 Span#
三个 ID#
三、实现:上下文传递#
关键:trace_id 必须在服务间传递(HTTP header / gRPC metadata / MQ 消息头)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
// 入口:生成 trace_id
func Middleware() gin.HandlerFunc {
return func(c *gin.Context) {
traceID := c.GetHeader("X-Trace-Id")
if traceID == "" {
traceID = uuid.New() // 入口生成
}
c.Set("trace_id", traceID)
// 下发给下游
c.Header("X-Trace-Id", traceID)
c.Next()
}
}
// 下游:从 header 取(没有就新生成——链路断了要告警)
traceID := c.GetHeader("X-Trace-Id")
|
gRPC 传递:
1
2
3
4
5
6
7
|
// 用 metadata 传递
md := metadata.Pairs("x-trace-id", traceID)
ctx = metadata.NewOutgoingContext(ctx, md)
// 服务端
if md, ok := metadata.FromIncomingContext(ctx); ok {
traceID = md.Get("x-trace-id")[0]
}
|
MQ 传递:消息头带 trace_id(RabbitMQ headers / Kafka headers)。
四、采集与存储#
采样策略(成本控制)#
存储选型#
五、落地效果#
六、踩坑记录#
- 链路断裂:下游没接 header → trace_id 丢失 → 入口生成了,下游自己建新 ID → 所有服务必须统一 SDK 传递
- 异步链路丢失:goroutine 里没传 context → 异步任务不在链路上 → 协程创建时带上 trace context
- 采样把错误丢了:只采样 10%,错误请求恰好没采到 → 错误必须全量采集
- Span 字段太多:采集 30 个字段 → 存储翻倍 → 只留关键字段(service/操作/耗时/状态/错误)
七、与日志/指标的配合#
链路追踪的核心认知:
- Trace = 一次请求的完整树:trace_id 贯穿 + span 记录每个环节
- 上下文传递是地基:HTTP/gRPC/MQ 全链路传 trace_id,断了就是白做
- 采样要有策略:正常 10% + 错误全采 + 核心接口全量
- 三层配合:指标触发告警、链路定位、日志看细节
微服务排查的分水岭:会不会用 Trace。 有了全链路视图,“这个请求到底经历了什么"就不再是拼出来的,是看出来的。
微信公众号「福清而不淡」
后端架构 · AI 工程 · 云原生的一线实践,扫码关注,不错过更新。
本文已同步发布到公众号,欢迎留言交流。