Linux 基础体系 · 第 39/85 篇。示例面向现代主流 Linux 发行版;发行版差异、权限和生产风险会明确说明。
Linux PSI 资源压力:CPU、内存、I/O Stall、告警和容量判断
Linux 的资源监控通常从“使用了多少资源”开始:CPU 使用率、内存使用率、磁盘吞吐量、I/O 延迟。但这些指标不能直接回答一个更接近用户体验的问题:
有多少时间,任务因为资源不足而无法继续执行?
Linux Pressure Stall Information(PSI,压力停顿信息)就是围绕这个问题设计的内核观测机制。它统计任务因为等待 CPU、内存或 I/O 而无法前进的时间,并将结果暴露为时间比例、累计停顿时间以及可触发的告警事件。
PSI 适合回答以下问题:
- CPU 很忙时,是否真的有任务排队等待 CPU?
- 内存还没有耗尽时,是否已经出现回收、换入或直接内存分配停顿?
- 磁盘吞吐量不高时,是否有大量任务被 I/O 阻塞?
- 某个 cgroup 是否比整台机器更早出现资源压力?
- 当前资源压力是短暂尖峰,还是持续性的容量不足?
- 应该扩容、限流、调整 cgroup 配额,还是先修复单个 I/O 或内存异常任务?
PSI 不是资源利用率,也不是延迟直方图,更不是容量规划的单一答案。它提供的是“资源争用导致任务停顿”的证据。
一、理解 PSI 之前:使用率和压力不是同一个概念
设一台机器有 8 个逻辑 CPU。
如果只有一个线程持续运行,它可能占用一个 CPU:
- CPU 利用率约为 12.5%;
- 没有其他任务因为 CPU 排队;
- CPU PSI 可能接近 0。
如果有 32 个 CPU 密集型线程:
- 8 个线程同时运行;
- 其余线程处于可运行但未运行状态;
- CPU 利用率接近 100%;
- CPU PSI 会反映任务等待 CPU 的时间。
因此:
前者描述 CPU 执行时间占比,后者描述任务因为无法获得 CPU 而停顿的时间占比。
反过来也成立。CPU 利用率可能不高,但任务仍然有明显的 CPU PSI,例如:
- 任务被 cgroup CPU 配额限制;
- 大量线程频繁唤醒,产生调度竞争;
- CPU 被中断、软中断或不可抢占内核代码占用;
- 任务的可运行时间集中在短时间窗口内;
- 应用只需要一个 CPU,但该 CPU 被其他任务竞争。
同样,内存使用率达到 80% 不一定表示内存压力很高;如果剩余内存足够,分配和访问都没有停顿,memory PSI 可以接近 0。相反,内存使用率还没有达到 100%,但频繁回收、页面抖动或直接回收已经让应用停顿,memory PSI 会先上升。
二、PSI 的核心定义:任务因资源停顿了多久
2.1 Stall 的含义
PSI 中的 stall 可以译为“停顿”或“受阻”。
它不是简单统计资源是否忙,而是统计:
一个任务本来可以继续推进,但因为目标资源条件不满足,暂时无法推进。
不同资源的“无法推进”状态不同:
| 资源 | 典型 stall 状态 |
|---|---|
| CPU | 任务处于可运行状态,但暂时没有获得 CPU |
| 内存 | 任务因内存回收、换页、内存分配等原因无法继续 |
| I/O | 任务等待块设备或相关 I/O 操作完成 |
PSI 记录的是任务停顿时间的聚合结果,而不是每个系统调用的延迟明细。因此,它不能直接替代:
perf sched的调度延迟分析;iostat的块设备延迟;vmstat的换页统计;- eBPF 记录的具体请求生命周期;
- 应用自身的请求延迟直方图。
PSI 的价值在于先回答“是否已经影响到任务推进”,然后再选择更细的工具寻找原因。
2.2 some 和 full
PSI 对内存和 I/O 通常提供两类状态:
some:至少有一个非空闲任务正在因为该资源停顿;full:所有非空闲任务都正在因为该资源停顿。
这里的“非空闲任务”不是“系统中所有进程”,而是当前参与该压力统计的、具有执行需求的任务。睡眠等待定时器、等待网络事件或正常阻塞的任务不应被简单视为资源压力。
设某个时间点有 4 个活跃任务:
| 时间区间 | 停顿任务 | some |
full |
|---|---|---|---|
| 0~2 ms | 1 个 | 是 | 否 |
| 2~5 ms | 2 个 | 是 | 否 |
| 5~7 ms | 4 个 | 是 | 是 |
| 7~10 ms | 0 个 | 否 | 否 |
那么:
some停顿时间为 ms;full停顿时间为 ms。
full 的意义不是“资源使用率达到 100%”,而是:
在那段时间内,所有有执行需求的任务都被该资源阻塞,系统没有可推进的工作。
这通常比 some 更接近系统级抖动、吞吐下降和整体停顿。
CPU PSI 通常只提供 some。原因是系统级 CPU pressure 主要描述可运行任务等待 CPU,而系统总会有至少一个任务获得 CPU 执行;内核文档和具体内核版本的接口应作为最终依据。不要假设所有资源文件都必然同时有 some 和 full。
三、PSI 的数学含义:平均值是时间比例,不是资源占用率
典型 PSI 文件包含三类窗口平均值:
some avg10=0.00 avg60=0.00 avg300=0.00 total=123456
full avg10=0.00 avg60=0.00 avg300=0.00 total=123456
各字段含义如下:
avg10:最近约 10 秒内的压力比例;avg60:最近约 60 秒内的压力比例;avg300:最近约 300 秒内的压力比例;total:从统计开始以来累计的压力时间,单位为微秒。
如果最近 10 秒内,some 状态累计持续 1.2 秒,则:
这表示最近 10 秒内,有 12% 的墙上时钟时间至少存在一个任务因该资源停顿。它不表示:
- 该资源被使用了 12%;
- 所有任务平均被阻塞了 12%;
- 某个请求必然增加了 12% 延迟;
- 设备吞吐量下降了 12%。
3.1 some 中的重叠时间不会按任务数重复计算
假设两个任务同时因 I/O 停顿 100 ms:
- 任务 A 停顿 100 ms;
- 任务 B 停顿 100 ms;
some不是 200 ms,而是这段时间的并集,即 100 ms;- 如果这两个任务是该压力域中全部活跃任务,
full也是 100 ms。
因此 PSI 的 some 反映“受影响时间段”,而不是“受影响任务数乘以停顿时长”。
这也是 PSI 与逐任务阻塞时间的一个重要区别:PSI 更适合观察系统或 cgroup 是否出现资源推进能力下降,而不是计算每个任务总共损失了多少 CPU 时间。
3.2 用累计值计算任意时间区间的压力
窗口平均值适合实时观察,但容量分析通常需要用累计值计算指定时间段。
设两次读取间隔为 秒:
- 第一次读取
total_1; - 第二次读取
total_2; - 两次读取间隔为 ;
total单位为微秒。
则该区间的压力比例为:
例如,某 memory PSI:
第一次:some total=8000000
第二次:some total=15800000
两次读取间隔 10 秒,则:
这说明在这 10 秒的时间线上,至少有一个任务处于 memory stall 的时间占比达到 78%。这是严重的推进能力下降,即使 MemAvailable 还没有归零,也应立即调查。
注意 total 是累计计数器,不应直接当作当前压力百分比。系统启动、cgroup 创建或 PSI 统计生命周期的具体起点,应结合文件和内核版本验证;监控系统应使用相邻采样点的差值,而不是保存一个固定的“启动时基线”并假定其永不重置。
四、PSI 暴露在哪里
4.1 系统级接口
现代 Linux 通常通过以下文件提供 PSI:
/proc/pressure/cpu
/proc/pressure/memory
/proc/pressure/io
查看示例:
$ cat /proc/pressure/cpu
some avg10=0.00 avg60=0.00 avg300=0.00 total=1234567
$ cat /proc/pressure/memory
some avg10=0.02 avg60=0.01 avg300=0.00 total=234567
full avg10=0.00 avg60=0.00 avg300=0.00 total=12345
$ cat /proc/pressure/io
some avg10=0.15 avg60=0.08 avg300=0.03 total=4567890
full avg10=0.04 avg60=0.02 avg300=0.01 total=1234567
输出格式的具体字段和可用资源类型受内核版本、配置和发行版影响。首先检查文件是否存在:
for f in /proc/pressure/cpu \
/proc/pressure/memory \
/proc/pressure/io; do
if [ -r "$f" ]; then
echo "== $f =="
cat "$f"
else
echo "unavailable: $f"
fi
done
如果这些文件不存在,可能原因包括:
- 内核未启用
CONFIG_PSI; - 使用了较老的内核;
- 内核通过启动参数关闭了 PSI;
- 容器的
/proc视图或安全策略没有提供该接口; - 当前环境并非完整宿主机。
可以检查内核配置:
grep -E 'CONFIG_PSI|CONFIG_PSI_DEFAULT_DISABLED' \
/boot/config-"$(uname -r)" 2>/dev/null
常见结果可能是:
CONFIG_PSI=y
# CONFIG_PSI_DEFAULT_DISABLED is not set
不同发行版对内核配置和启动参数的处理不同,不能仅凭某一台机器的结果推断所有发行版都支持同样的 PSI 行为。
4.2 cgroup v2 接口
在 cgroup v2 中,某个 cgroup 目录通常可以看到:
cpu.pressure
memory.pressure
io.pressure
例如:
mountpoint -q /sys/fs/cgroup && \
cat /sys/fs/cgroup/my-service/memory.pressure
cgroup PSI 用来观察该 cgroup 压力域中的任务,而不是只看整台机器。它对于多租户主机尤其重要:
- 整机 CPU PSI 很低,但某个容器受到 CPU 配额限制;
- 整机内存尚可,但某个服务的
memory.max太小; - 整机磁盘压力一般,但某个服务的 I/O 请求集中在慢设备或受限层;
- 多个服务共享宿主机,只有一个 cgroup 出现
full压力。
cgroup PSI 与 cgroup 控制器不是一回事:
- 不启用
cpu控制器,也可能观察 CPU pressure; - 不启用
memory控制器,也可能观察 memory pressure; - PSI 是观测机制,不是限额机制;
memory.max、cpu.max、io.max决定控制行为,pressure 文件描述任务是否因此停顿。
cgroup v2 的层级、委派和权限会影响谁能读取或写入哪些文件。将 /sys/fs/cgroup 直接写入容器、让不可信租户拥有创建子 cgroup 或配置监控触发器的权限,都可能造成资源管理和拒绝服务风险。
五、CPU PSI:可运行任务为什么没有立即执行
5.1 CPU pressure 的状态链路
CPU PSI 关注的是调度等待:
flowchart LR
A[任务需要继续执行] --> B{任务是否可运行}
B -- 否 --> C[正常睡眠/等待事件]
B -- 是 --> D{是否立即获得CPU}
D -- 是 --> E[运行]
D -- 否 --> F[进入CPU some stall]
F --> D
CPU some 上升意味着在观测窗口中,至少有一个任务处于:
- 可运行;
- 尚未获得 CPU;
- 等待时间被 PSI 计入。
这与 /proc/stat 中的 idle、user、system 等 CPU 时间分类不同。一个 CPU 可以接近 100% 利用率,却没有明显的 CPU pressure,例如只有一个线程独占一个 CPU;也可以在平均利用率不高时出现明显 pressure,例如任务集中在少量 CPU 上排队。
5.2 一个具体算例
假设只有 2 个 CPU,有 4 个 CPU 密集型任务:
| 时间 | 正在运行 | 等待 CPU | CPU some |
|---|---|---|---|
| 0~10 ms | 2 | 2 | 是 |
| 10~20 ms | 2 | 2 | 是 |
| 20~30 ms | 2 | 2 | 是 |
在这 30 ms 中:
- CPU 利用率可能接近 100%;
- 至少一个任务等待 CPU 的时间也是约 30 ms;
- 因此这段时间对 CPU
some的贡献约为 30 ms; - 但不能据此说“CPU pressure 是 200%”,因为 PSI 按时间并集统计,不按等待任务数相加。
如果只有 1 个任务运行在 8 CPU 主机上:
- CPU 使用率约为 12.5%;
- 没有任务排队;
- CPU
some可以接近 0。
这说明“CPU 空闲很多”并不能排除单线程任务遇到局部 CPU 争用,例如 CPU 亲和性、cpuset、实时任务或虚拟化调度造成的局部瓶颈。
5.3 CPU PSI 的边界
CPU PSI 上升可能来自:
- CPU 核数不足;
- 线程数过多;
- CPU affinity 或 cpuset 将任务限制在少数 CPU;
- cgroup CPU 配额或权重造成的竞争;
- 虚拟机中的 vCPU 被宿主机调度延迟;
- 高优先级任务、软中断或中断占用执行机会;
- 应用线程模型导致大量可运行任务争用。
CPU PSI 不能独自区分这些原因。应结合以下证据:
pidstat -u -w 1
vmstat 1
mpstat -P ALL 1
ps -eo pid,psr,stat,pri,ni,comm --sort=psr
进一步可以使用:
perf sched record -a -- sleep 10
perf sched latency
或使用 eBPF 工具观察调度延迟、运行队列、cgroup throttling 和 CPU 迁移。
一个常见误判是把 CPU PSI 当作 CPU 利用率的另一种写法。正确的因果链应是:
任务产生执行需求
-> 任务可运行
-> 暂时没有获得执行机会
-> CPU PSI some 增加
而不是:
CPU 使用率高
-> CPU PSI 必然同样高
六、内存 PSI:内存还没用完,为什么任务已经卡住
6.1 memory stall 的典型来源
内存压力主要来自任务为了获得或访问内存而触发的内核工作,例如:
- 直接内存回收;
- 页面换入;
- 内存压缩或整理;
- 文件页回收后重新读取;
- cgroup 内存限制导致的回收;
- 内存分配路径上等待可用页;
- 内存回收进一步引发 I/O 等待。
memory PSI 的关键不是“已分配内存占总内存多少”,而是:
内存管理路径是否已经让任务不能顺畅地继续执行。
因此,下列现象可能同时发生:
MemAvailable 尚未耗尽
memory PSI 已经升高
应用延迟已经恶化
原因是 Linux 会尽量使用空闲内存之外的 page cache,且可回收缓存不等于真正不可用的内存。另一方面,某个 cgroup 的 memory.max 可能很小,导致它先于宿主机进入回收和分配停顿。
6.2 some 与 full 的区别
假设某个服务有 10 个活跃工作线程:
- 只有 1 个线程因内存回收暂停:
memory some增加; - 5 个线程同时暂停:
memory some继续增加,但full仍可能为 0; - 10 个线程全部因内存问题暂停:
memory full增加。
memory full 长时间持续,通常说明该压力域几乎没有可推进的工作,常见于:
- 内存抖动;
- 大量页面换入换出;
- 极小的 cgroup 内存上限;
- 工作集明显超过可用内存;
- 多个线程同步等待同一批内存回收或 I/O。
6.3 内存 PSI 与 swap 的关系
swap 活跃不等于必然发生 memory pressure。
例如:
- 后台线程主动换出冷页面;
- 内存仍有较大余量;
- 前台任务没有因为换入而明显停顿。
此时 swap I/O 可能存在,但 memory PSI 不一定高。
反之,如果工作集持续大于可用内存:
- 任务访问已换出的页面;
- 触发页面换入;
- 任务等待 I/O;
- 多个任务重复触发换入;
- memory
some和full可能上升; - 应用请求延迟和吞吐同时恶化。
所以诊断内存压力至少应同时查看:
free -h
vmstat 1
cat /proc/pressure/memory
cat /sys/fs/cgroup/<group>/memory.current
cat /sys/fs/cgroup/<group>/memory.events
memory.events 中的 high、max、oom、oom_kill 等事件可以帮助判断 cgroup 限制是否触发。PSI 说明“任务被内存系统拖慢”,而 memory.events 更接近“哪些内存控制边界被碰到了”。
6.4 反例:内存使用率高但没有 memory pressure
一台机器长期缓存大量文件:
free显示 used 很高;MemAvailable仍然充足;- 应用访问命中 page cache;
- 没有显著回收和换入;
- memory PSI 接近 0。
此时直接根据“内存使用率 90%”触发扩容,可能是误判。
另一个反例是:
MemAvailable看起来还不少;- 一个容器设置了较低的
memory.max; - 容器内部频繁回收页面;
- 容器的 memory PSI 明显高于宿主机 PSI。
这时应先检查 cgroup 限额和工作集,而不是只看整机内存。
七、I/O Stall:磁盘不一定忙,但任务可能一直在等
7.1 I/O PSI 的含义
I/O PSI 统计任务因为 I/O 路径而无法继续执行的时间。典型情况包括:
- 等待块设备读写完成;
- 等待文件系统提交或回写;
- 等待换页 I/O;
- 存储设备队列拥塞;
- 网络文件系统或其他文件系统路径上的等待。
I/O PSI 不是磁盘利用率,也不是吞吐量:
- 设备利用率高,但请求短、队列管理良好,任务不一定长时间停顿;
- 设备利用率不高,但单个请求延迟很长,任务仍可能出现 I/O stall;
- 吞吐量较低,但大量同步读请求集中在关键路径,I/O PSI 可能很高。
“磁盘没有跑满”不能推出“没有 I/O 压力”。
7.2 I/O some 与 full 算例
假设某 cgroup 有 3 个活跃任务:
| 时间区间 | 因 I/O 停顿的任务 | io some |
io full |
|---|---|---|---|
| 0~100 ms | A | 100 ms | 0 |
| 100~180 ms | A、B | 80 ms | 80 ms |
| 180~230 ms | B、C | 50 ms | 50 ms |
| 230~300 ms | 无 | 0 | 0 |
总计:
io some: ms;io full: ms。
在 300 ms 观察区间内:
这表示该 cgroup 在大部分时间里至少有一个任务被 I/O 阻塞,并且有相当长时间所有活跃任务都无法推进。即使设备总吞吐量不高,也足以造成服务级别的明显延迟。
7.3 I/O PSI 不能直接告诉你是哪块盘
I/O PSI 聚合的是任务侧压力,不会直接给出:
- 哪个块设备;
- 哪个进程发起的请求;
- 哪个文件;
- 哪种 I/O 类型;
- 读还是写;
- 请求在队列中等待了多久。
因此 I/O PSI 上升后,应继续建立证据链:
iostat -xz 1
pidstat -d 1
vmstat 1
cat /proc/pressure/io
如果使用 eBPF,可以进一步关联:
进程/容器
-> 文件或块设备请求
-> request 发出时间
-> request 完成时间
-> 任务阻塞时间
-> PSI 时间线
这样才能区分:
- 设备本身延迟高;
- I/O 队列过深;
- 文件系统回写导致阻塞;
- swap I/O 引发内存抖动;
- 某个服务执行大量同步随机读;
- 网络文件系统或远端存储延迟。
7.4 反例:I/O PSI 高不一定是本地磁盘坏了
以下情况都可能导致 I/O PSI 上升:
- 数据库同步提交等待持久化;
- 日志系统频繁
fsync; - 容器使用共享存储;
- 文件系统发生回写;
- 内存压力触发 swap;
- 网络文件系统服务端变慢;
- 存储设备延迟增加但吞吐量没有达到峰值。
因此,直接因为 io.full 上升就替换磁盘或扩大磁盘带宽,可能把应用同步写、内存不足或文件系统配置问题误判为设备容量问题。
八、从读取到告警:PSI 的事件触发机制
窗口平均值适合轮询,但轮询有一个问题:短暂尖峰可能被采样间隔错过。Linux PSI 还提供基于文件描述符的触发机制。
常见触发器格式为:
some 100000 1000000
它表示:
- 监控
some; - 在约 1 秒窗口内;
- 如果累计 stall 时间达到约 100000 微秒,即 100 ms;
- 触发一次事件。
换算为比例:
full 也可以使用类似格式,但前提是对应资源和内核版本提供 full 统计。
8.1 一个最小的 Python 触发器示例
下面的程序展示 Linux PSI 的基本事件生命周期:
#!/usr/bin/env python3
import os
import select
import time
PATH = "/proc/pressure/memory"
TRIGGER = "some 100000 1000000\n"
fd = os.open(PATH, os.O_RDWR | os.O_NONBLOCK)
try:
os.write(fd, TRIGGER.encode())
poller = select.poll()
poller.register(fd, select.POLLPRI)
print(f"watching {PATH}: {TRIGGER.strip()}")
while True:
events = poller.poll(5000)
if events:
now = time.strftime("%Y-%m-%d %H:%M:%S")
print(now, "memory PSI trigger fired")
else:
print("no trigger in the last 5 seconds")
finally:
os.close(fd)
运行前需要满足:
- 内核支持 PSI;
- 文件存在;
- 当前用户拥有打开和写入该接口所需权限;
- Python 能够访问
/proc/pressure/memory; - 当前内核支持对该文件注册触发器。
启动:
python3 psi-watch.py
可能输出:
watching /proc/pressure/memory: some 100000 1000000
no trigger in the last 5 seconds
2025-01-01 12:00:03 memory PSI trigger fired
8.2 触发器的生命周期
触发器的操作顺序是:
- 打开 PSI 文件;
- 向文件描述符写入触发器规则;
- 将文件描述符注册到
poll、select或epoll; - 等待
POLLPRI等事件; - 触发后读取或记录当前 PSI;
- 文件描述符关闭后,触发器失效。
触发器是绑定到这个文件描述符的,不是永久写入内核配置。监控进程退出、文件描述符关闭或容器被重启后,规则需要重新注册。
触发器不是计数队列。它适合通知“窗口内发生了达到阈值的压力”,不适合精确记录每一个任务的每一次阻塞。若监控程序处理事件过慢,应配合读取累计 total,用累计差值检查是否丢失了多个事件。
具体可用的事件标志、权限要求和触发器行为会随内核版本变化。生产部署时应在目标内核上验证:
uname -r
test -r /proc/pressure/memory
test -w /proc/pressure/memory
不能因为某台发行版允许普通用户注册触发器,就推断其他发行版也允许。
九、告警设计:不要只对一个 avg10 数值报警
一个压力告警至少要解决三个问题:
- 压力是否真实存在;
- 压力是否持续;
- 压力是否已经影响关键工作负载。
9.1 只看 avg10 的失败表现
假设每 15 秒采集一次:
12:00:00 avg10=0.00
12:00:15 avg10=0.00
12:00:30 avg10=18.00
12:00:45 avg10=0.00
如果压力只持续了几秒,应用可能已经发生一次严重请求超时,但轮询数据几乎看不出问题。
反过来,单次 avg10=20% 也可能只是一次部署、备份或批处理尖峰,不应立即触发扩容。
9.2 使用多时间窗口区分尖峰和持续压力
可以把窗口解释为:
avg10:近期是否正在恶化;avg60:一分钟内是否有持续影响;avg300:是否已经形成容量趋势。
例如:
cpu.pressure:
some avg10=35.00 avg60=12.00 avg300=2.00
memory.pressure:
some avg10=8.00 avg60=5.00 avg300=1.00
full avg10=1.00 avg60=0.40 avg300=0.05
io.pressure:
some avg10=25.00 avg60=18.00 avg300=10.00
full avg10=8.00 avg60=4.00 avg300=1.00
合理的解释是:
- CPU 近期突然拥塞,但长期平均尚不严重;
- 内存有稳定的回收压力,近期还出现过所有任务同时停顿;
- I/O 压力已经持续数分钟,并且存在全体活跃任务受阻的时段。
这里不能脱离业务 SLO 直接说“25% 一定严重”。对在线 API、离线转码、消息消费和数据库写入而言,同一 PSI 数值的含义不同。
9.3 告警应绑定服务对象
整机 PSI 告警无法自动说明哪个服务受影响。更有价值的监控维度包括:
主机 CPU PSI
主机 memory PSI
主机 io PSI
服务 cgroup CPU PSI
服务 cgroup memory PSI
服务 cgroup io PSI
服务请求延迟
服务错误率
服务吞吐量
cgroup 限额事件
例如,一个容器的 memory full 持续升高,同时:
memory.events: high 计数持续增加
请求 p99 升高
吞吐量下降
这比“整机内存使用率 90%”更能支持“该容器内存上限过低或工作集过大”的判断。
十、用 PSI 做容量判断:从压力到扩容的推导
PSI 最适合做容量判断的方式不是设定一个通用阈值,而是将资源压力与业务产出关联起来。
10.1 先定义资源容量是否足够
设某个时间窗口为 ,资源的 some 压力比例为:
设业务吞吐量为 ,业务延迟为 。
如果在逐步增加负载的实验中得到:
| 并发 | 吞吐量 | p99 延迟 | CPU some | Memory some | I/O some |
|---|---|---|---|---|---|
| 100 | 1000 req/s | 20 ms | 0.5% | 0% | 1% |
| 200 | 1980 req/s | 24 ms | 2% | 0.1% | 3% |
| 300 | 2700 req/s | 60 ms | 12% | 1% | 15% |
| 400 | 2750 req/s | 180 ms | 35% | 8% | 40% |
从 300 到 400 并发:
- 吞吐量几乎不再增加;
- 延迟显著恶化;
- 多种 PSI 同时上升。
这说明系统接近或超过有效容量。这里的“有效容量”不是 CPU 使用率达到某个固定百分比,而是:
在满足业务延迟和错误率目标的条件下,系统还能承载的最大稳定负载。
10.2 根据 PSI 区分容量瓶颈
CPU 瓶颈
典型证据:
CPU some 持续升高
CPU 利用率较高
run queue 增长
应用 CPU time 增加
I/O PSI 和 memory PSI 不明显
可能措施:
- 优化热点代码;
- 减少无效线程和频繁唤醒;
- 调整 CPU affinity;
- 增加 CPU 配额或核数;
- 将批处理与在线服务隔离。
内存瓶颈
典型证据:
memory some 持续升高
memory full 也出现
major fault、swap-in 或 reclaim 增加
memory.events 中 high/max 增加
应用延迟变长
可能措施:
- 减少工作集;
- 修复缓存无界增长;
- 调整 cgroup 内存上限;
- 增加物理内存;
- 限制并发,避免同时触发大量分配;
- 检查是否错误地将 page cache、匿名内存和 swap 混为一谈。
I/O 瓶颈
典型证据:
io some 持续升高
io full 出现
iostat 显示 await 或队列等待上升
请求路径包含同步读写或 fsync
可能措施:
- 减少同步 I/O;
- 合并小请求;
- 调整日志和刷盘策略;
- 优化索引与访问模式;
- 将不同工作负载放到不同设备;
- 增加存储 IOPS 或降低设备延迟。
PSI 只能说明“任务被 I/O 拖住”,不能直接证明“需要更大带宽”。如果瓶颈是单次同步写的尾延迟,增加顺序带宽可能没有效果。
十一、cgroups v2 场景:主机压力和服务压力可能完全不同
cgroup v2 的资源控制和 PSI 结合后,可以观察“谁正在承受压力”。
假设宿主机有足够内存:
主机 memory some avg60=0.5
服务 A memory some avg60=12
服务 B memory some avg60=0
可能原因是服务 A 的 cgroup 受到了:
memory.max
memory.high
限制。服务 A 内部频繁回收,而其他服务没有受到影响。
同样,某服务的 CPU PSI 高可能不是整机 CPU 不足,而是:
cpu.max 设置过低
cpuset.cpus 只允许使用少数 CPU
cpu.weight 很低
服务自身的 pressure 文件能够揭示这种局部容量不足。
一个典型排查流程:
CG=/sys/fs/cgroup/my-service
cat "$CG/cpu.pressure"
cat "$CG/memory.pressure"
cat "$CG/io.pressure"
cat "$CG/cpu.max" 2>/dev/null
cat "$CG/memory.max" 2>/dev/null
cat "$CG/memory.current" 2>/dev/null
cat "$CG/memory.events" 2>/dev/null
cat "$CG/io.max" 2>/dev/null
如果目录不属于当前用户、文件不可读或不存在,应先确认:
stat -fc %T /sys/fs/cgroup
mount | grep cgroup
findmnt -t cgroup2
不要在生产环境随意修改 cpu.max、memory.max 或 io.max 来验证假设。修改资源上限可能立即导致:
- 任务节流;
- 内存回收加剧;
- OOM kill;
- I/O 延迟扩大;
- 服务连锁超时。
验证应优先通过只读观察、压测环境或可回滚的配置变更完成。
十二、PSI 与其他工具的证据链
PSI 提供“结果性证据”,但故障定位需要把它与原因性证据连接起来。
12.1 CPU
cat /proc/pressure/cpu
mpstat -P ALL 1
pidstat -u -w 1
vmstat 1
重点观察:
- 是否只有某些 CPU 忙;
- 运行队列是否增长;
- 上下文切换是否异常;
- 某个进程是否长期可运行但得不到调度;
- cgroup 是否发生 CPU quota throttling。
12.2 内存
cat /proc/pressure/memory
free -h
vmstat 1
sar -B 1
cat /proc/vmstat
重点观察:
- reclaim、pgscan、pgsteal;
- major fault;
- swap in/out;
- direct reclaim;
- cgroup
memory.events; - 应用工作集是否超过限制。
12.3 I/O
cat /proc/pressure/io
iostat -xz 1
pidstat -d 1
vmstat 1
重点观察:
await;- 队列深度;
- 设备
%util; - 读写模式;
- 哪些进程产生 I/O;
- 是否存在 swap 或回写诱发的 I/O。
12.4 eBPF 的位置
当 PSI 已经证明任务受压,但常规统计无法定位原因时,可以使用 eBPF 建立更细的关联:
PSI 上升
-> 找到受影响的 cgroup
-> 找到任务阻塞时间
-> 关联调度、内存回收或块 I/O 事件
-> 关联文件、设备和请求延迟
-> 回到具体服务和请求类型
eBPF 工具的名称、探针和字段高度依赖内核版本、BTF、发行版和工具包版本,不能把某个版本的脚本当作所有主流发行版都支持的稳定 API。生产环境应先验证工具与当前内核的兼容性,并评估采样开销。
十三、常见误解和失败诊断
13.1 “CPU 使用率 100%,所以 CPU PSI 一定是 100%”
错误原因:CPU 使用率按执行时间统计,CPU PSI 按等待执行的任务时间统计。
- 单线程占满一个 CPU:利用率可以很高,pressure 很低;
- 多线程在 8 个 CPU 上排队:利用率接近 100%,pressure 也可能很高;
- 多个 cgroup 受配额限制:整机利用率不高,某个 cgroup pressure 很高。
13.2 “内存使用率超过 90%,所以一定有内存压力”
错误原因:Linux 会使用 page cache,使用率不等于不可回收内存,也不等于任务停顿。
应同时看:
MemAvailable
memory PSI
swap 活动
回收统计
memory.events
应用延迟
13.3 “I/O PSI 高,就是磁盘吞吐不够”
错误原因:PSI 表示任务等待 I/O 的时间,不说明瓶颈是带宽、IOPS、延迟、队列、文件系统还是远端存储。
必须结合 iostat、进程 I/O、文件系统和请求级追踪。
13.4 “full 越高,说明机器用了越多资源”
错误原因:full 表示所有相关活跃任务都处于该资源 stall,不是资源利用率。
一个任务组只有一个活跃任务时,该任务一旦等待 I/O,some 和 full 都可能同时增加;这并不代表整台机器所有任务都被 I/O 阻塞。
13.5 “PSI 数值超过 10% 的机器都应该扩容”
错误原因:没有业务上下文的固定阈值通常不可靠。
对批处理任务,短时高 pressure 可能可以接受;对低延迟服务,即使 1% 的 full 也可能对应明显的尾延迟问题。阈值应来自:
- 服务 SLO;
- 压测曲线;
- 历史基线;
- 资源隔离策略;
- 负载类型。
十四、生产使用时的权限、版本和风险
14.1 内核与发行版差异
PSI 的可用性依赖:
- 内核版本;
CONFIG_PSI;- 是否默认启用;
- cgroup v2 是否挂载;
- 容器是否能看到对应
/proc或 cgroup 文件; - 文件权限和安全策略。
先在目标机器确认,而不要直接把文章中的文件路径当作部署保证:
uname -a
test -e /proc/pressure/cpu
test -e /proc/pressure/memory
test -e /proc/pressure/io
findmnt -t cgroup2
14.2 权限
读取系统级 PSI 通常只需要读取相应文件,但写入触发器、访问某个 cgroup 或委派 cgroup 子树可能受到权限限制。
在容器中还要注意:
- 容器看到的
/proc可能是受限视图; - 容器可能只能读取自己的 cgroup;
- systemd、容器运行时或编排系统可能管理 cgroup 层级;
- 直接修改运行时创建的 cgroup 文件可能被覆盖。
14.3 监控系统的实现风险
监控 PSI 时应避免:
- 把
total当作瞬时值; - 用固定采样间隔假定窗口平均值精确代表整个区间;
- 只采集主机 PSI,不采集服务 cgroup PSI;
- 只设置
avg10告警; - 忽略容器迁移、cgroup 重建导致的计数器生命周期变化;
- 频繁创建大量触发器;
- 在高压状态下执行会进一步增加 I/O 或内存负担的诊断动作。
更稳妥的采集方式是:
- 读取并解析
avg10/60/300; - 保存
total和采样时间; - 对相邻
total做差; - 检测 cgroup 或主机重启、计数器回退;
- 将 PSI 与业务延迟和资源控制事件关联;
- 对告警设置恢复条件和抑制策略。
十五、一个可操作的诊断顺序
当服务延迟升高时,可以按以下因果顺序检查:
第一步:确认是否存在任务级资源压力
cat /proc/pressure/cpu
cat /proc/pressure/memory
cat /proc/pressure/io
如果所有 PSI 都接近 0,问题可能不在这三类资源的系统级争用上,还应检查:
- 网络;
- 锁竞争;
- 应用线程池;
- GC;
- 上游依赖;
- 数据库连接池;
- 业务限流。
第二步:区分短时尖峰和持续容量不足
比较:
avg10
avg60
avg300
- 只有
avg10高:近期尖峰或突发故障; avg10、avg60都高:当前正在持续;avg300也高:容量或长期配置问题的可能性增加。
第三步:定位资源类型
- CPU
some高:检查运行队列、亲和性、配额和调度; - memory
some/full高:检查回收、swap、cgroup 上限和工作集; - io
some/full高:检查设备延迟、队列、同步 I/O 和回写。
第四步:从主机缩小到 cgroup
比较:
主机 PSI
服务 cgroup PSI
同机其他服务 cgroup PSI
如果只有一个 cgroup 高,优先检查该服务的配额和访问模式;如果多个 cgroup 同时高,则更像主机级容量或共享设备问题。
第五步:验证业务影响
将压力时间与以下指标对齐:
请求 p50/p95/p99
超时率
错误率
吞吐量
队列长度
消费延迟
数据库提交延迟
只有当“资源压力”和“业务退化”在时间上、对象上都能对应,才能较有把握地把 PSI 作为根因链的一环。
十六、总结:PSI 的正确定位
Linux PSI 的核心不是告诉你“CPU、内存或磁盘用了多少”,而是告诉你:
任务有多少时间因为资源争用或资源管理路径而无法继续推进。
需要记住几个关键关系:
some:至少一个相关活跃任务停顿;full:所有相关活跃任务都停顿;avg10/60/300:不同时间窗口的压力比例;total:累计停顿时间,应通过差值计算任意区间压力;- CPU PSI 通常关注
some,不能假定所有资源都有full; - cgroup PSI 用于发现局部资源压力,不能被主机平均值替代;
- PSI 是结果性指标,需要与调度、内存回收、块 I/O、cgroup 事件和业务 SLO 组成证据链;
- 容量判断应依据“负载增加后吞吐、延迟和 PSI 的变化”,而不是采用脱离业务的固定阈值。
当 CPU 利用率、内存使用率或磁盘吞吐量无法解释服务延迟时,PSI 往往能提供一个更接近实际用户影响的答案:系统是否已经因为资源不足,让任务停止前进。
系列导航与关联阅读
- 系列入口:Linux 完整学习路线:从内核与文件系统到网络、性能和生产运维
- 上一篇:Linux NUMA 与硬件拓扑:节点、内存亲和、跨节点访问和诊断
- 下一篇:Linux 文件命令体系:ls、cp、mv、rm、stat、file 和安全操作
- 延伸:Linux cgroups v2 深入:CPU、Memory、IO、PID、Pressure 和委派
- 延伸:Linux 性能分析方法:CPU、内存、磁盘、网络与 eBPF 证据链
官方资料
本文依据 Linux 内核、systemd 与主流发行版官方文档重新梳理;正文与实验由 WR BLOG 编写。

评论
0 条讨论