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、到达方式与统计边界。然后逐个执行对照:
- 保持 device 行为,给 callback 增加有界延迟,检查入口驻留与后续请求延迟是否同时增加。
- 保持 callback,改变 device 服务等待,观察后端区间是否主导总延迟。
- 保持代码与负载,改变实验 cgroup 的 CPU 配额,关联 throttle 增量与调度等待。
- 保持额度,增加受控内存压力,记录 fault、回收与 pressure 指标。
这些都是待执行方案。若现象不符合假设,就撤回归因,不从一张火焰图直接推出应该换架构。决定采用异步交付、分进程或更大额度之前,先明确缩短了哪段等待、增加了多少资源、故障时多出了什么收尾责任。