自动埋点给你的只有一条网络边界
一句话总结
启用自动计量,准确得到的只有一个东西:网络边界。HTTP 服务端与客户端、gRPC、DB 驱动、Redis 和消息队列客户端会产生 span;进程内部工作不会表现为任何 span,只会成为父 span 的 self time 空白。
为什么需要它
计量失败的团队几乎都以同样方式失败:先在代码里埋手工 span,两周后有 300 个 span,追踪却仍在服务边界断裂。有效顺序是固定的。
| 顺序 | 要做的事 | 跳过后的结果 |
|---|---|---|
| 1 | 启用自动计量并确认数据到达 | 后续所有调试都靠猜测 |
| 2 | 确定资源属性 | 日后修改会与历史数据断开 |
| 3 | 验证跨服务传播 | 增加 span 也只会得到碎片 |
| 4 | 只在 self time 大的区间添加手工 span | 自动计量的空白永远存在 |
| 5 | 把处理与采样迁移到 Collector | 每次改策略都要重新部署所有服务 |
关键是第 3 步先于第 4 步。传播已断时增加手工 span,只会把破碎追踪切得更碎。
工作原理
启用自动计量后会得到这样的追踪。
SERVER checkout-api POST /v1/orders 1421ms
├─ CLIENT GET http://auth.internal/verify 31ms
├─ CLIENT SELECT carts WHERE id = ? 6ms
├─ CLIENT redis GET promo:rules:t-8871 2ms
├─ CLIENT POST http://payment.internal/charge 74ms
└─ (나머지 1308ms 는 어떤 스팬에도 속하지 않음)
最后一行就是全部线索。自动计量用 1,308ms 的空白告诉我们哪里不是问题。这段空白是 self time,手工 span 只应加在这里。
自动计量绝对看不到:进程内 CPU 工作(序列化、压缩、模板渲染、加密)、锁与连接池等待、GIL 竞争和事件循环延迟、没有计量包的第三方 SDK 调用,以及业务逻辑分支。
手工 span 应放在五处。
- 循环和批处理边界——把迭代次数记为属性
- 缓存查询——记录是否命中,可直接观察缓存效率
- 没有计量包的第三方 SDK 调用
- 锁、队列、连接池等待
- 长时间占用 CPU 的区间——序列化、压缩、生成报告
加入后,空白会这样被填充。
├─ INTERNAL checkout.apply_promotions 1298ms
│ ├─ INTERNAL promotion.load_rules cache.hit=false 1241ms <-- 여기
│ └─ INTERNAL promotion.evaluate evaluated=812 54ms
只需遵守一条span 命名规则:名称必须低基数。不应写 GET /v1/orders/A-99183,而应写 GET /v1/orders/:id;具体值全部作为属性。后端按 span 名称分组生成延迟统计和服务图,名称含 ID 会摧毁整个聚合视图。
资源属性以后无法修复。 与 span 属性不同,资源属性附在进程输出的所有信号上;修改 service.name 会断开仪表板、告警、服务图和历史数据。
| 属性 | 示例 | 能否修改 |
|---|---|---|
service.name |
checkout-api | 实际上不能 |
service.namespace |
commerce | 很困难 |
service.version |
2.7.1 | 每次部署变化 |
deployment.environment.name |
prod | 不能 |
service.instance.id |
Pod 名称 | 每次重启变化 |
常见错误有两个。第一,环境属性名是 deployment.environment.name;旧名 deployment.environment 已不再使用。名称不同会形成两个独立属性,仪表板变量只会读取其中一个。第二,service.name 表示服务单位,不是部署单位。把 canary 命名为 checkout-api-canary,会在服务图中产生幽灵节点。
采样器建议从 parentbased_always_on 开始。如果一开始就启用比例采样,看不到追踪时无法区分是计量问题还是采样导致。切换到比例后也必须使用 parentbased_traceidratio。若不遵循父级决定、各服务独立随机判断,追踪会在中间断裂。
现场会遇到的情况
计量破坏应用的方式反复出现。
| 症状 | 原因 | 应对 |
|---|---|---|
| 部署后内存持续增长 | 后端延迟导致 exporter 队列塞满 | 明确队列上限,改为经过 Collector |
| 只到达部分 span | 退出时未 flush | 调用 shutdown,延长终止宽限期 |
| 单个 span 数百 KB | 把完整请求正文放入属性 | 设置属性值长度上限 |
| 服务图出现幽灵节点 | canary 使用单独 service.name | service.name 按服务固定 |
SDK 默认没有属性值长度上限。属性数量默认限制为 128,但值长度无限,因此完整请求正文进入后,传输与存储成本会完整跟随。显式设置上限更安全。
下一实验要做什么
在 /root/otca-sdk/ 下编写 SDK 环境变量文件,按约定设置资源属性,并创建使用 Kubernetes Downward API 注入 Pod 名称和命名空间的 Deployment,实际应用。最后整理 span 名称列表,并亲自编写捕获名称中 ID 的 linter。