第二章 全链路监控基础:分布式追踪原理、Trace/Span概念、OpenTracing标准
好,咱们正式开始聊全链路监控的核心——分布式追踪。说实话,我刚接触这个领域时,也被一堆新概念砸得头晕。但搞懂了最基础的几个东西,后面就顺了。
2.1 为什么需要分布式追踪?
先问个问题:你负责的系统,一个请求从用户点击到最终返回,中间经过了多少个服务?
我见过不少团队,服务拆了十几个,但排查问题还是靠「猜」。A服务说「我调了B」,B说「我调了C和D」,D又说「我超时了」……一圈下来,谁都不知道根因在哪。
这就是典型的「分布式困境」——请求像一串珍珠,但珍珠散落在各个服务里,没人能把它们串起来。
分布式追踪要解决的,就是这个串珍珠的问题。
2.2 Trace 和 Span:最核心的两个概念
说白了,一个 Trace 就是一次完整的请求旅程。从用户点击,到网关、微服务、数据库、缓存,最后返回结果——整条链路就是一个 Trace。
而 Span,是 Trace 里的一个片段。每个服务、每个操作,都可以是一个 Span。
举个例子:
你下单买了个东西。整个下单过程是一个 Trace。其中「调用库存服务」、「扣减余额」、「生成订单」这三个步骤,各自是一个 Span。
每个 Span 都记录着:
- Span ID:自己的唯一标识
- Parent Span ID:父 Span 是谁(形成树状结构)
- Trace ID:属于哪个 Trace
- 开始时间、结束时间:算耗时用的
- Tags、Logs:附加信息,比如 HTTP 状态码、异常堆栈
我在项目中遇到过最典型的坑:有人把 Span 的 Parent ID 传错了,结果整个链路图变成了一团乱麻。嗯,调试这种问题比写代码还痛苦。
2.3 分布式追踪的工作原理
原理其实不复杂,就三步:
- 注入:请求进来时,生成或传递 Trace ID 和 Span ID
- 传播:通过 HTTP 头、RPC 元数据等方式,把上下文传到下游服务
- 收集:每个服务把 Span 数据上报到后端,后端按 Trace ID 组装成完整链路
你想想看,这就像快递包裹上的运单号。每个中转站都扫一下码,记录时间和状态。最后你查物流时,就能看到完整的运输路径。
我的习惯:在网关层就生成 Trace ID,然后通过 HTTP Header 传递。推荐用 uber-trace-id 或 X-B3-TraceId 这样的标准头,别自己发明格式。
2.4 OpenTracing 标准:让不同系统能对话
早些年,各家 APM 厂商各玩各的。Jaeger 的格式、Zipkin 的格式、SkyWalking 的格式……互不兼容。你换一个工具,埋点代码就得重写。
OpenTracing 就是来解决这个问题的。它定义了一套标准接口,不管你底层用 Jaeger 还是 Zipkin,埋点代码几乎不用改。
核心接口就几个:
| 接口 | 作用 |
|---|---|
Tracer |
创建 Span,管理上下文 |
Span |
代表一个操作单元 |
SpanContext |
传递 Trace ID、Span ID 等上下文 |
Scope |
管理 Span 的生命周期(自动关闭) |
用代码看更直观。这是一个典型的 OpenTracing 埋点:
// 创建 Tracer(以 Jaeger 为例)
Tracer tracer = new Configuration("my-service")
.withSampler(Configuration.SamplerConfiguration.fromEnv())
.withReporter(Configuration.ReporterConfiguration.fromEnv())
.getTracer();
// 开始一个 Span
Span span = tracer.buildSpan("handleRequest")
.withTag("http.method", "GET")
.withTag("http.url", "/api/order")
.start();
// 模拟业务逻辑
try {
// 调用下游服务
Span childSpan = tracer.buildSpan("callInventoryService")
.asChildOf(span)
.start();
// ... 调用代码
childSpan.finish();
} catch (Exception e) {
span.setTag("error", true);
span.log(Map.of("event", "error", "error.object", e));
} finally {
span.finish();
}
我曾经踩过的坑:忘记在 finally 里调用 span.finish()。结果 Span 一直不关闭,后端看到的链路全是「进行中」状态。排查了半天才发现是资源泄漏。
2.5 采样:别什么都往上报
高并发系统里,每秒几万个请求。如果每个请求都生成 Trace 并上报,后端存储扛不住,网络带宽也扛不住。
所以必须采样。常见的策略有:
- 固定采样:比如只采样 1% 的请求
- 动态采样:根据请求延迟、错误率动态调整采样率
- 头部采样:只采样前 N 个 Span(不推荐,容易丢失关键信息)
我个人习惯用动态采样。正常时采样 1%,一旦发现某个接口错误率飙升,自动提升到 100%。这样既省钱,又不漏问题。
2.6 从理论到实践:你需要知道的几件事
讲完原理,说点实际的。搭建全链路监控,有几个关键点绕不开:
- 统一 Trace ID 生成规则:所有服务用同一套算法,别各搞各的
- 上下文传递不能丢:异步调用、消息队列、线程池——这些地方最容易丢上下文
- 存储选型要提前想好:Elasticsearch 适合全文检索,Cassandra 适合高写入,各有优劣
- UI 展示要直观:甘特图、火焰图、调用拓扑——用户一眼能看出问题在哪
一个小技巧:在日志里也带上 Trace ID。这样你查日志时,直接搜 Trace ID 就能把整个请求的所有日志串起来。排查效率翻倍。
2.7 本章小结
分布式追踪的核心,说白了就是三个东西:Trace(完整链路)、Span(操作片段)、上下文传递(把 ID 传下去)。OpenTracing 让不同系统能互相理解,采样让系统不至于被数据淹死。
下一章,咱们会深入讲 APM 系统的整体架构设计。到时候你会看到,这些基础概念是怎么搭建成一个完整系统的。
嗯,先消化这些。有问题随时翻回来看看。