用 timeout 限制的压测进程复现单核 CPU 打满,练习定位热点与区分节流

演练目标

环境准备

Linux 虚拟机、测试机或一次性容器,安装 procps、sysstat、stress-ng(可选)。实验必须带 timeout,默认上限 120 秒。

mkdir -p /tmp/lab-cpu
nproc
uptime
which stress-ng || echo "stress-ng 未安装,将使用 yes 作为替代"

如果使用容器,建议加 --cpus=1,这样只会影响容器自己的 CPU 配额:

docker run -d --rm --name lab-cpu --cpus=1 alpine:3.20 \
  sh -c 'timeout 120s sh -c "while :; do :; done"'

注入故障

优先用 stress-ng 启动两个满载 worker,并用 timeout 兜底:

timeout 120s stress-ng --cpu 2 --cpu-load 100 --timeout 110s --metrics-brief &
echo $! > /tmp/lab-cpu/stress.pid

没有 stress-ng 时,用 yes 制造单核满载,同样必须加 timeout:

timeout 120s yes > /dev/null 2>&1 &
echo $! > /tmp/lab-cpu/stress.pid

观察现象

uptime
top -b -n1 | head -20
ps -eo pid,ppid,comm,%cpu --sort=-%cpu | head -10
pidstat -p "$(pgrep -n stress-ng || pgrep -n yes)" 1 5
cat /proc/pressure/cpu

预期 top 中 stress-ng 或 yes 占用接近 100%(多核机器上可能是若干核),%Cpu(s) 的 us 明显升高,load average 上升,/proc/pressure/cpu 出现 some 或 full 压力值。timeout 到期后进程自动退出。

判断与定位

  1. top -Hp <pid> 看线程级占用;单线程打满和多线程打满的处理方式不同。
  2. us 高通常是用户态计算;sy 高看系统调用和内核路径;wa 高是 IO 等待,不是 CPU 忙;st 高是虚拟化被宿主机抢占。
  3. 容器里先看 cgroup:
cat /sys/fs/cgroup/cpu.max 2>/dev/null || cat /sys/fs/cgroup/cpu/cpu.cfs_quota_us
cat /sys/fs/cgroup/cpu.stat

nr_throttled 和 throttled_usec 增长说明被 CPU 配额限制,表现为“CPU 使用率不高但延迟很高”。

  1. 容易误判:load average 高不等于 CPU 高。大量进程处于不可中断睡眠(D 状态)也会推高负载,此时 CPU 可能很空闲。另一个误判是容器里 top 看到的 100% 是相对一个核还是整机,必须结合 cpu.max 判断。
  2. 如果只有单个请求变慢,还要排查锁竞争和 GC,而不是只盯 CPU 总量。

修复

kill "$(cat /tmp/lab-cpu/stress.pid)" 2>/dev/null || true
pkill -f 'stress-ng --cpu' 2>/dev/null || true
pkill -x yes 2>/dev/null || true
pgrep -a -f 'stress-ng|yes' || echo "已清理"

清理与回滚

docker rm -f lab-cpu 2>/dev/null || true
rm -rf /tmp/lab-cpu

如果使用宿主机进程,确认 pgrep -a stress-ng 和 pgrep -a yes 都没有残留后再结束演练。

复盘

延伸阅读

读完自测

  1. load average 很高但 CPU 使用率很低,可能是什么原因?
  2. 容器 CPU 使用率不高但接口延迟很高,应该检查什么指标?
  3. 为什么 CPU 压测必须配 timeout?
答案
  1. 可能有大量 D 状态进程在等 IO、锁或内核资源,负载统计的是可运行和不可中断睡眠任务,不只是 CPU 占用。
  2. 检查 cpu.stat 的 nr_throttled 和 throttled_usec,确认是否被 CPU 配额节流。
  3. 防止实验进程失控长期占满宿主机,影响其他服务和演练环境。