生产延迟尖刺排查:用 bpftrace 抓系统调用与调度延迟

线上 Java 或 Go 服务 P99 从 12ms 变成 200ms,指标盘却很干净:CPU 40%,内存稳定,GC 正常,下游 RT 也没变。这种尖刺经常出在内核路径:线程被调度器挂起、futex 等待、或一次 fsync 卡住写路径。bpftrace 能在不改代码、几乎不增加负载的前提下,把这些事件按进程和堆栈捞出来。

先用 30 秒判断战场

是调度延迟还是系统调用阻塞

在目标节点以 root 跑:

bash 复制代码
bpftrace -e '
profile:hz:99 /comm == "order-api"/ {
  @[kstack, ustack] = count();
}
interval:s:30 { exit(); }
'

如果热点全是 schedule、finish_task_switch,优先看 CPU 限流、CFS、cgroup。如果热点是 futex_wait、blk_mq_start_request、tcp_recvmsg,再往锁、磁盘、网络拆。

对照表:

栈顶特征 方向
futex_wait 用户态锁或线程池排队
ep_poll / io_uring_wait 正常等待,不一定是问题
blk_mq_start_request 磁盘或云盘带宽
hrtimer + 长空隙 停机迁移或 CPU throttle

抓系统调用耗时分布

不要一上来就 dump 全量栈

全量 tracepoint:syscalls:sys_enter_* 在高 QPS 节点上会把自己打满。先按进程过滤,只看超过阈值的调用:

bash 复制代码
bpftrace -e '
tracepoint:syscalls:sys_enter_fsync /comm == "order-api"/ { @start[tid] = nsecs; }
tracepoint:syscalls:sys_exit_fsync /@start[tid]/ {
  $d = nsecs - @start[tid];
  if ($d > 5000000) { printf("fsync %d us pid %d\\n", $d/1000, pid); }
  delete(@start[tid]);
}
'

5ms 以上的 fsync 在云盘上很常见。业务线程一旦同步刷盘,P99 直接被单次 IO 拖走。修复通常是把刷盘交给专门的 WAL 线程,或把 synchronous=full 改成组提交。

对 futex 同样处理:

bash 复制代码
bpftrace -e '
tracepoint:syscalls:sys_enter_futex /comm == "order-api"/ { @s[tid] = nsecs; }
tracepoint:syscalls:sys_exit_futex /@s[tid]/ {
  @us = hist((nsecs - @s[tid]) / 1000);
  delete(@s[tid]);
}
interval:s:15 { print(@us); clear(@us); }
'

直方图如果在 1000us 附近有长尾,说明锁竞争已经进内核。再结合 offcputime 或 Go 的 mutex profile 定位具体锁。

容器里还有一层 cgroup

CPU throttle 会伪装成"偶发卡顿"

K8s 里 cpu.limit 设成 500m 时,CFS 配额用完线程会被挂起,表现和死锁很像,但栈里是 throttle。

bash 复制代码
bpftrace -e '
kprobe:throttle_cfs_rq {
  @[comm] = count();
}
interval:s:10 { print(@); clear(@); }
'

某容器持续出现在这个计数里,先把 limit 去掉或改成只设 request。很多"优化了代码但 P99 不变"的案例,最后是 limit 卡住了突发计算。

操作纪律

  1. 先过滤 comm 或 pid,禁止全机跟踪所有 syscall。
  2. 采样优于追踪:profile:hz:99 比逐次 kprobe 便宜。
  3. 生产窗口控制在 30 到 60 秒,看完立刻停。
  4. 结论必须能对应到改动:去掉 fsync、放大 CPU limit、拆锁,而不是留下一堆火焰图。

总结

延迟尖刺优先用 bpftrace 区分调度、锁、磁盘三条路径。30 秒栈采样定方向,再对 fsync 和 futex 做耗时直方图,容器环境额外看 CFS throttle。工具本身很便宜,贵的是没有过滤条件的全量探针。定位到具体系统调用后,修复通常是工程约束,而不是再加一台机器。

相关推荐
Ruiery1 小时前
Linux 6.6内核并发与同步(一):原子操作与内存屏障 — 内核同步的硬件底座
linux·运维·服务器·1024程序员节
码完就睡1 小时前
Linux——IP协议与路由选择
linux·网络·tcp/ip·1024程序员节
H.莓飛2 小时前
【数据结构】排序_OJ题(二):划分与选择
linux·服务器·c语言·开发语言·数据结构·visual studio
艸肅2 小时前
Docker 国内镜像源配置
linux·ubuntu·docker·1024程序员节
猿粪已尽2 小时前
mac 中 iTerm2 使用 rz、sz 上传下载文件详解,附 scp 操作指南
linux·macos·scp·lrzsz
宁波阿成2 小时前
CentOS 7.5 升级 PHP 至 8.3.36 完整实操记录
linux·centos·php
djjjx.2 小时前
Linux进程篇(七):环境变量的底层溯源、核心获取机制与安全应用
linux·运维·服务器
FriendshipT2 小时前
Vim 完整指南:从入门到高效编辑
linux·人工智能·编辑器·vim·1024程序员节
Zevalin爱灰灰2 小时前
Linux入门第一部分(Ubuntu入门篇) 第二章 浅尝Ubuntu(一)
linux·嵌入式·1024程序员节