生产延迟尖刺排查:用 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。工具本身很便宜,贵的是没有过滤条件的全量探针。定位到具体系统调用后,修复通常是工程约束,而不是再加一台机器。

相关推荐
星恒随风31 分钟前
MySQL 从零入门(六):复合查询详解——多表查询、自连接、子查询与 UNION
linux·服务器·数据库·mysql
少司府41 分钟前
嵌入式入门篇(二):初识STM32F103ZET6
linux·python·stm32·单片机·嵌入式硬件·mcu·嵌入式软件
无足鸟ICT1 小时前
【RHCA+】IO调度算法
linux·服务器
wacsii2 小时前
【Linux进程(四)】命令行参数与环境变量
linux·服务器
无奈笑天下2 小时前
银河麒麟上lsblk命令教程详解
linux·运维·服务器·数据库
科研邪修法3 小时前
培训PDF保护配置:把需求转换成可验收权限
linux·服务器·网络
Felven4 小时前
中科驭数100G网卡RDMA性能测试
linux·rdma·中科驭数·100g网卡
雪落漂泊6 小时前
Liunux进程(七):程序控制篇(中)
linux·运维·windows
俊俊几年的6 小时前
动态规划(Dynamic Programming)详解:从入门到 LeetCode Hot 100
linux·c++·算法·leetcode·嵌入式·动态规划·c
星恒随风15 小时前
MySQL 从零入门(五):MySQL 内置函数详解——日期、字符串、数学函数与实战案例
linux·数据库·笔记·sql·mysql