LabHub
学习 学习路径 课程

分布式链路断掉的地方

traceparent 那一行的结构

在 LabHub 中继续学习

一句话总结

分布式追踪的全部要点,就是traceparent header 传递给下一次调用。自动 instrumentation 会代为处理,但代码新建 request object 的瞬间,链路就会断开。

流程图: 把 traceparent header 传递给下一次调用 · 不知道服务 A 收到的请求,与 A 发给 B 的请求属于同一条 trace。 · 采样 flag · queue

为什么需要了解这一点

“安装了 Istio,追踪自然就能工作”是常见但错误的说法。sidecar 可以为自己看到的请求创建 span,但它不知道服务 A 收到的请求,与 A 发给 B 的请求属于同一条 trace。 将两者连接起来的是应用程序。

W3C 标准 header 如下。

traceparent: 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01
             ^^ ^------------ trace-id ---------^ ^-- span-id --^ ^^
             버전            32자리(트레이스 전체)   16자리(이 구간)  플래그

最后的 01采样 flag。1 表示“记录这条 trace”,0 表示“不记录”。这个决定只在最前端作出一次,再向后传播——这样才不会只留下半条 trace。

工作原理

服务 A 调用 B 时,只需完成一件事。

  1. 读取传入请求的 traceparent
  2. 创建新的 span-id,保留 trace-id 不变,并附加到传出请求

自动 instrumentation(auto-instrumentation)会代为完成。但在以下情况下会断开。

断开位置 原因
在代码中直接创建新的 HTTP client 路径未被 instrumentation library 包装
通过 queue、message 传递 没有 HTTP header——必须自行放入 message attribute
新建 thread/coroutine context 位于 thread local,不会随之传递
batch processing 并非一个请求对应一条 trace

尤其常见的是 queue。传入 Kafka 时,要把 traceparent 放入 message header,consumer 再取出它恢复 context。否则 producer 侧 trace 与 consumer 侧 trace 会变成彼此毫无关系的两条记录。

采样

全部记录成本很高,所以需要采样。方式有两种。

头部采样——在最前端决定“记录这一条”。成本低且简单,但无法只选择慢请求查看。 因为开始时还不知道它是否会变慢。

尾部采样——trace 结束后再判断,例如“只保存出现错误或超过 1 秒的 trace”。它能准确选择所需内容,但 collector 必须先把整条 trace 收集到内存中再判断,所以成本高;同时还必须路由,让同一 trace 的 span 到达同一个 collector。

实际组合通常是这样:在头部降到 10~20%,错误强制保留 100%,仅对重要 endpoint 应用尾部采样。

常见误解

“span 越多越好”——如果为每个函数都创建 span,一条 trace 会包含数千个 span,不仅 UI 无法阅读,存储成本也会暴涨。span 应放在网络边界和慢操作上。

把个人信息放进 attribute。 span attribute 会被原样存储并可供搜索。如果写入用户邮箱或 token,它们就会以明文积累在 tracing backend 中。

Span 中应记录什么

span 只有名称和时间,对调查帮助不大。必须附带能够区分该 span 做了什么的 attribute,才能找出慢操作之间的共同点。但随意添加内容又会同时带来存储成本与个人信息问题,因此需要标准。

通常值得加入的是能够据此划分结果的值

规模和条件尤其有价值。 与“这条 query 很慢”相比,“这条 query 只有在结果为一万行时才慢”能更快触及原因;而要作出这种区分,必须把行数记录为 attribute。

反过来,不应放入的内容也很明确。除了个人信息和 credential,取值种类近乎无限的内容同样要谨慎。tracing backend 为了支持按 attribute 搜索会建立 index,如果放入完整 request body 或未经规范化的路径,index 就会爆炸。这与前面介绍 metric label 时所说的是同一个问题。

最好也预先确定错误记录方式。span 有单独表示成功与失败的 status,所以首先要准确设置该值。缺少它,“只查看失败 trace”就无法工作。exception 内容应作为 span event 保留,但是否写入完整 stack trace,要结合存储成本判断。通常错误类型和一行 message 已经足够,详细内容留在日志中即可。 如前所述,只要日志与 trace 相连,就可以跳转过去查看。

实际工作中真正重要的事项

把 trace 与日志连接起来,才是它实际价值的主要来源。只要在每行日志中加入 trace_id,发现慢 trace 时,就能一次找出该请求留下的日志。缺少这种连接,tracing 就只是一幅漂亮的图。