跳转至

CPU 不忙,p99 却变差:沿请求路径定位等待

将额度从 4 提高到 16,吞吐提高了,偶发请求却更慢。此时继续加线程或换更快的 CPU,可能没有改善:时间也许花在入口、调度、socket、设备或同步 callback 中。

已有 额度基准记录端到端样本和轮次吞吐。本章讨论怎样从这些现象形成可证伪的诊断,再决定是否需要改变结构。下面的分段埋点与 trace 对照是进一步诊断方案,当前公开 CSV 尚未包含这些字段。

先确定用户等待的起点

现有基准从调用 submit 之前开始计时,到 terminal callback 采样结束。producer 等待可用额度的时间在这次请求计时之外。

这意味着额度 16 的成功样本 p99 即使符合某个目标,也没有证明业务从产生需求到得到结果的总等待符合目标。架构评审应同时区分:

业务意图产生 → 等待提交机会 → 调用 submit → 进入入口
    → ACCEPT → 后端发送 → 设备处理 / 回复 → owner 接收
    → terminal callback → 业务消费者使用结果

首先选择一个对用户有意义的 SLO 起点和终点,再确认每份数据覆盖了哪一段。错误、拒绝和超时作为独立结果展示;只看 completed 的分位,会遗漏慢请求已经失败的情况。

用不重叠的阶段构成总时间

单台机器上的单调时钟可以用来记录本地边界。拟增加如下时间点:

标记 采样位置 可以计算什么
t0 业务准备请求 提交前等待
t1 submit 开始 API 自身与入队等待
t2 API 入队成功 入口驻留
t3 core ACCEPT 接受后的运行时路径
t4 实际发送成功 输出排队完成
t5 owner 收到响应 后端往返
t6 terminal callback 入口 解码、状态转换与交付安排
t7 callback 返回 回调执行成本

T_业务交付=t6−t0;若业务完成的定义还包含 callback 工作,则使用 t7−t0。将相邻差值相加才构成同一请求的总路径,不把嵌套区间再次计入。

t5−t4 仍混合 socket、后端调度、设备与回复等待,不能直接命名为“设备执行时间”。跨芯片的时钟未经同步时,不直接相减;可以分别报告本地耗时,并用完整请求身份关联两端事件。

聚合记录中保留 instance、generation、id 与终态。恢复之后 id 可重复,只按 id join 日志会把两个会话拼成一条虚假的长请求。

看 on-CPU,也看为什么没有运行

先提出两个不同假设:owner 在运行,但执行太慢;owner 需要运行,却在等待 CPU、锁或 IO。CPU profile 主要解释前者,调度与等待证据解释后者。

现象 优先补充的证据 尚不能直接下的结论
owner CPU 高,循环推进慢 热点栈、每轮成本、字节与条数 一定是算法复杂度
owner CPU 低,入口持续积压 runnable 等待、锁等待、callback 时间 必须加线程
backend 往返增大 后端时间点、设备队列、输出水位 kernel 网络栈有问题
一批请求一起变慢 公共暂停、quota、回收或长 callback 每笔请求分别处理慢

Linux event tracing 能从 tracefs 选择调度事件,并为事件设置过滤。内核事件追踪文档

在配置好 tracing 的专用实验 VM 中,可以先只读确认接口与事件:

cat /sys/kernel/tracing/available_events
cat /sys/kernel/tracing/events/sched/sched_wakeup/format
cat /sys/kernel/tracing/events/sched/sched_switch/format

采集方案选择 owner 的 TID,关联唤醒与随后切入该线程的调度记录。不是每次切入都有新的 wakeup:线程可能已经 runnable,或先被抢占。线程迁移、事件丢失和 trace clock 选择也会影响关联,不能机械地把任意相邻两行当作调度延迟。

当前镜像未保证包含全部 tracing / perf 工具和内核选项,接口不存在时先确认配置。采集需要控制采样范围、持续时间和 buffer 大小,报告丢失事件;本章不声称已得到实际调度 trace。

不把各阶段的 p99 直接相加

假设 100 个请求中,前一个阶段的最慢一笔是 A,后一个阶段的最慢一笔是 B。它们可能是不同请求。两个阶段分别取 p99 再相加,不能得到端到端 p99。

保留每笔请求的时间点,先计算其完整路径,再对总时间求分位。同时查看慢请求由哪一段主导、是否集中在同一时间窗口或同一代际。

小样本的尾部分位对少数样本非常敏感。比较时保留样本量、算法、原始数据、失败数和各轮变化。当前历史数据每轮 10000 个成功样本,汇总采用 floor(q×(n−1)) 的位置,不使用插值;新的报告继续注明算法,避免工具变化造成假差异。

闭环基准会改变到达过程

现有 producer 由在途窗口约束:完成变慢以后,它也更晚提交下一笔。这是闭环负载,适合验证受控并发下的流水线,但没有模拟业务持续按固定速率产生新需求。

考虑设计算例:服务暂停 100 ms,外部业务仍以 1000 requests/s 产生工作,约 100 笔需求会在服务之外积累。若压测端跟着完成速度停下来,并且只记录 submit 后的时间,就可能遗漏这段排队。

进一步诊断可以同时做两种负载:

负载 控制变量 必须额外记录
闭环窗口 并发数 producer 等待额度的时间
定时产生需求 到达计划与持续时长 计划时间、实际提交、拒绝、外部队列上限

固定到达率的工具本身也可能跟不上计划。它应记录迟到或丢弃,不在恢复后无界补发并把补发尖峰解释成原始业务。

让每轮实验只回答一个问题

先固定源码 SHA、配置、CPU / 内存限制、payload、到达方式与统计边界。然后逐个执行对照:

  1. 保持 device 行为,给 callback 增加有界延迟,检查入口驻留与后续请求延迟是否同时增加。
  2. 保持 callback,改变 device 服务等待,观察后端区间是否主导总延迟。
  3. 保持代码与负载,改变实验 cgroup 的 CPU 配额,关联 throttle 增量与调度等待。
  4. 保持额度,增加受控内存压力,记录 fault、回收与 pressure 指标。

这些都是待执行方案。若现象不符合假设,就撤回归因,不从一张火焰图直接推出应该换架构。决定采用异步交付、分进程或更大额度之前,先明确缩短了哪段等待、增加了多少资源、故障时多出了什么收尾责任。

资源隔离与压力继续解释共享环境中的等待,容量推导把到达和服务边界写进系统契约。