当有人报告追踪里看不到这个请求
一句话总结
跨度缺失的原因有四个,症状却只有一个,所以不要凭猜测去修,要把请求记录和转储文件对照起来,按每个原因留下的不同痕迹来区分。
为什么需要它
反馈总是同一句话。“我用这个订单号找跟踪,没找到。”我曾因为这一句话花了两天。头两天怀疑的是采样设置。把比例调高后等了一天,反馈照样进来。接着怀疑 exporter,加大了批大小,也不是。到第三天,用请求标识符把日志和转储文件对在一起,才发现缺失的全都是同一条路径上的请求。
回头看,第一天就能做到。我们没做的有两件。第一,没有把缺失了多少条变成数字。只拿着“看不到”这句话行动,修完之后也不知道有没有好转,所以同一个怀疑做了两遍。第二,没有看缺失之物的共同点。全部缺失与只有一条路径缺失,原因完全不同,却没有做这个区分,就去动了全局设置。
工作原理
诊断是四步。对照出清单,用共同点缩小范围,排除候选,确认剩下的。
第一步是缺失项清单。应用日志里每个请求留有一行,转储文件的服务端跨度上,同样的请求标识符作为属性附着。求两个集合的差,就得到“日志里有、转储文件里没有的请求”。有了这个数字,后面所有的话才是真的。
第二步是缩小范围。把缺失的请求按路径数、按时间段数。集中在一条路径上,就去看那条路径的处理代码;集中在某个时间段,就去看那个时刻发生了什么;均匀散布,就得看全局设置。这一张表能把调查范围缩小到十分之一。
第三步是排除。候选有四个,它们在转储文件里留下的痕迹各不相同。
| 候选原因 | 根跨度 | 子跨度 | 缺失请求的分布 |
|---|---|---|---|
| 被采样丢弃 | 没有 | 没有 | 分散 |
| 没有结束跨度 | 没有 | 留着,指向的父级却不在转储文件里 | 分散(通常集中在一条路径) |
| 进程提前结束 | 没有 | 没有 | 从日志末尾开始连续 |
| 父级上下文断开 | 有 | 留着,但已成为另一条跟踪的根 | 没有缺失的请求 |
采样和提前退出都不留下任何痕迹,所以只看转储文件分不出来。区分它们的是分布。采样会散布在整个区间,进程一死,那个时刻之后就整体缺失。没有结束的跨度正相反,会留下非常鲜明的痕迹——子级被导出了,那个子级所指向的父级却在转储文件的任何地方都找不到。父级上下文断开的情形是完全不同的症状。没有一个请求缺失,却另外有一些跟踪,里面没有一个带请求标识符的跨度。在界面上看起来是“跟踪里只有一个跨度”。
第四步是把同样的判定固化成脚本。如果每次都由人画表,下次反馈来时又要花两天。把规则连顺序一起写成代码,下一个人只用一行命令就能得到同样的答案。规则之所以要有顺序,是因为痕迹可能重叠——没有结束的跨度还会同时产生“没有请求标识符的跟踪”,所以要排在父级上下文判定之前。
采样究竟丢弃什么、子级为什么要服从父级的决定,整理在 采样概念文档 中;进程结束之前导出为什么必须是显式调用,见 Trace SDK 规范 的 ForceFlush 和 Shutdown 两节。父级上下文传入传出的规格,以 W3C Trace Context 为准。
要明确说明这里不讨论的内容。找到有缺陷的 SDK 接线并修复,是 SDK 生命周期模块的事。本模块教的是在此之前——在多个原因中,凭数据辨别是哪一个的流程,产出的不是修好的代码,而是分类表和诊断脚本。
也写一下这个环境无法判定的事项。实验 Pod 里既没有 OpenTelemetry Collector,也没有跟踪后端。所以那条跟踪在后端界面上怎么画、Collector 在中途丢弃了什么,在这里都无法确认。我们看到的只有两样东西:把 SDK 导出的跨度原样记下来的 JSONL 转储文件,以及服务留下的日志,判定全部靠这两者的对照。
在现场相遇的样子
最常见的是第三种。批处理作业或短命的命令行程序里,最后几个请求总是丢失,反馈却是“偶尔会漏”。与日志对照一下,马上就能看出缺失的总是在末尾。仅仅“不是散布的”这一个事实,就立刻排除了采样这个候选。
第二常见的是第四种。在队列 worker 或回调里不传递上下文,里面的跨度就成了新跟踪的根。没有缺失的东西,所以对照表上什么都抓不到,而人们反馈的是“跟踪只有一半”。这时要数的不是缺失的请求,而是没有请求标识符的跟踪。
下一项实验要做什么
从被反馈的一起事件的两份证据(请求日志和跨度转储文件)开始。先对照,数出缺失的请求,按路径、按时间段拆分,看缺失集中在哪里。接着亲手复现四个原因,确认每一个在转储文件里留下的痕迹,把它们的差别整理成分类表。把整理好的规则搬进诊断脚本,在事件上运行,最后对原因不同的第二起事件运行同一个脚本,确认得出不同的答案,然后留下调查记录。