第 2 / 21 篇

第 1 章:从“很慢”到可以反驳的假设

收到慢请求反馈后,最危险的第一步往往是直接改参数。一次请求可能在网关等待连接、在应用排队、访问缓存,也可能已经返回给用户而后台消息仍未完成。如果没有把这些区间分开,“慢”就不是一个可用于决策的描述。

1.1 先定义观察对象

本书把用户提交的一次 HTTP 请求,与由它触发的一次消息处理分开。前者的结果可以是 202 已接受,也可以是 503 实验失败;后者的结果是消费者写入专用 Redis 处理标记。两者通过订单标识与追踪上下文联系,但具有不同的完成条件。用户等待时间是网关入口到响应返回的时间,异步完成时间还要覆盖消息传递和消费者处理。

这种区分决定了监控是否会说真话。假设接口始终在 30 毫秒返回 202,而消费者停滞十分钟,HTTP 延迟图仍然很好看。此时继续优化 HTTP 并不能恢复业务结果;我们还需要观察消费进度、处理结果和业务等待时间。反过来,消费者处理正常也不能证明入口没有大量拒绝。

实验把遥测分成三类。lab.requests 是可聚合的请求计数,适合比较一段时间内的错误比例;一个 span 描述某次工作从开始到结束的区间,适合寻找这次工作的组成;日志记录 order.queued、worker.processed 等事件,适合解释具体决定。三者的分母、保留和采样方式不同,不能直接把数量当作应当相等的数字。

1.2 将假设写成可失败的检查

“可能是 Redis 慢”过于宽泛。更可检查的说法是:“在这次网关请求对应的 trace 中,订单服务的 Redis 客户端区间占据了大部分同步耗时。”这个说法可以被 trace 反驳:如果长区间在 HTTP 客户端而 Redis span 很短,就应该继续查看连接、上游服务或未被仪表化的区间。

“消息上下文可能丢失”也需要具体化。在默认传播实验中,消费者的 trace ID 应当与入口响应中的 ID 相同,消费者父 span 应当是发布 span。关闭传播后,订单仍会被消费,但工作者建立新的 trace。两种情况都要运行,才能证明我们的检查确实区分了传播与业务成功,而不只是看到了一个绿色页面。

这里的 order_id 是业务关联字段,trace ID 是一次执行的技术身份。同一订单可能被读取很多次,也可能在重试中产生多个工作区间。把订单号直接当 trace ID,会把独立执行硬塞到同一棵树里,反而妨碍理解。

1.3 一份最小证据表

运行后,evidence/results.json 保存场景与响应,trace-*.json 保存 Tempo 原始返回,events.json 保存应用事件,metrics-requests.json 与 alerts.json 保存 Prometheus 查询。读者应能够从一条响应里的 trace ID,走到 span 树,再走到同一 span ID 的日志。

我们不要求每个问题都同时查看三种信号。指标用于判断范围,trace 用于定位一次执行,日志用于解释决定。最有效的排查通常是逐步缩小范围,而不是先把所有后台打开。

练习。 为“用户说订单成功了,但统计没有变化”写两个互相竞争的假设,分别说明需要哪个完成条件、哪份证据,以及出现什么结果时应当放弃该假设。不要把“多打一些日志”作为完整答案。

本章关于上下文和信号边界的基础定义可对照 OpenTelemetry 上下文传播说明。