trace 与 span
链路追踪把一次请求在多个服务之间的流转记录下来:整个调用过程称为一个 trace,内部每个工作单元称为一个 span。span 带有起止时间、状态与属性,并通过父子关系串成树,从而回答请求经过了哪些服务、在哪一跳变慢或失败。
span 里记录了什么
每个 span 记录四类信息:所在服务与操作名、由起止时间算出的耗时、状态与错误信息,以及自定义属性。父子关系用父 span 标识表达,根 span 代表入口请求;跨进程调用会创建子 span,并把上下文标识随请求头传下去。
为什么需要链路
指标能说明整体变慢,日志能给出某次失败的具体信息,但只有链路能指出慢在哪一跳。三者常配合使用:先由指标发现异常,拿到 trace 标识后打开链路定位环节,再回到日志查看该环节的详细输出。
基本信息
| Trace | 一次请求的全部 span 集合,共享同一 trace 标识 |
|---|---|
| Span | 基本工作单元,含名称、起止时间、状态与属性 |
| 父子关系 | 用父 span 标识串联成树 |
| 上下文传播 | 跨进程调用时通过请求头携带标识 |
| 采样 | 高流量下按比例采样,错误请求通常优先保留 |
典型使用场景
- 慢请求定位:按耗时排序找出最慢的那一跳
- 故障归因:判断错误发生在自身还是下游依赖
- 依赖梳理:观察服务之间的真实调用关系与调用量
- 发布对比:比较同一接口在发布前后的耗时分布
- 容量分析:识别高频且耗时的下游调用,指导优化排序
一次请求的 span 树
一次请求的 span 树
GET /checkout [root span, 812ms]
├── POST /cart/validate (client) [42ms]
├── SELECT * FROM orders (client) [310ms]
├── POST /payment/charge (client) [400ms]
│ └── POST /payment/charge (server) [380ms] # 下游服务内部
└── render checkout page [55ms]span 的关键字段
| 字段 | 含义 | 注意 |
|---|---|---|
| 操作名 | 表示这个 span 做了什么 | 命名要稳定,便于聚合 |
| 起止时间 | 用于计算耗时 | 跨主机时注意时钟偏差 |
| 状态与错误 | 标记是否失败 | 错误信息不要包含敏感数据 |
| 属性 | 补充维度,如状态码或库名 | 控制基数,避免高动态取值 |
| 父子标识 | 表达调用层级 | 缺失会退化成孤立记录 |
与指标日志的分工
| 信号 | 回答什么 | 特点 |
|---|---|---|
| 指标 | 整体趋势是否正常 | 成本低,适合长期留存 |
| 日志 | 单次到底发生了什么 | 明细完整,成本偏高 |
| 链路 | 慢在哪一跳、错在哪一环 | 受采样影响,适合定位 |
上下文传播示意
上下文传播示意
# 跨进程调用时把链路上下文放入请求头,下游据此创建子 span
traceparent: 00-<trace-id>-<span-id>-01
# 上游若丢弃该请求头,调用树会在此处断开,只能看到孤立 span落地时的三个次序
- 先保证核心链路的每一跳都能产出 span,再逐步扩大覆盖
- 服务命名与指标标签保持一致,便于从指标跳到链路
- 错误请求优先采样,避免故障时恰好没有样本可查
链路受采样影响,不适合做精确计数:统计请求量看指标,链路用来解释某个具体请求的路径。
只在入口埋点不会形成完整调用树:跨进程与跨服务的每一跳都要产出 span,否则拿到的只是零散记录。
链路的价值在排障时体现:平时几乎不看,故障时却是唯一能直接指出问题环节的数据,因此关键路径要提前打通。
排查一次慢请求的步骤
- 从告警或看板拿到请求的链路标识
- 打开链路查看耗时分布,找最慢的那一跳
- 看该 span 的状态与属性,判断是自身慢还是下游慢
- 回到日志按同一标识检索该环节的详细输出