一、为什么需要感知资源压力?
在传统 Linux 运维中,系统 "变慢" 往往要等到用户投诉或故障发生后才被察觉。CPU/内存/IO 的饱和度可以通过 top、iostat 等工具观察,但这些工具只反映 瞬时状态,无法揭示 等待排队导致的延迟恶化。
Linux 4.20 引入的 PSI (Pressure Stall Information) 正是为了解决这个盲点:它能衡量 "多少任务因为等待某种资源而被阻塞了多久",提供了一个可量化、可感知(pollable)、可触发(triggerable)的资源压力指标。
二、PSI 核心概念
2.1 压力 = 时间 × 阻塞任务数
PSI 的核心思想是计算资源争夺的时间开销:
压力 = (总时间 - 实际工作时间) / 总时间
# 举例: 若 10 个任务在 10 秒内等待了 3 秒资源
# 压力 = (10 任务 × 10 秒 - 所有任务实际运行时间) / (10 × 10)
# = 3 / 10 = 30%
输出的值范围是 0-100%,表示 "过去 X 秒内,至少有一个任务因该资源停滞的时间占比"。
2.2 三类资源指标
| 资源 | 度量对象 | 含义 |
|---|---|---|
| CPU | 任务在运行队列中等待调度的时间 | CPU 饱和/过载 |
| Memory | 任务因内存分配失败/回收阻塞的时间(包括 direct reclaim、swap) | 内存紧张/受限 |
| IO | 任务因同步 IO 阻塞的时间 | 存储带宽/延迟瓶颈 |
2.3 "some" vs "full" 压力
PSI 为每类资源提供两个维度:
- some:至少有一个任务被阻塞的时间占比——反映 "是否有人受影响"
- full:几乎所有 任务同时被阻塞的时间占比——反映 "系统全面停滞"
例:4 核 CPU,10 个 CPU 密集任务
some avg10=80% → 过去 10 秒内平均有任务排队
full avg10=20% → 过去 10 秒内平均 20% 时间全部 4 核心都在排队等待
CPU 压力: some=80%, full=20%
说明: 系统有排队但未全面受阻 → 还能运转,但延迟变高了
2.4 三个时间窗口
每类压力都提供 10秒、60秒、300秒 三个时间窗口:
- avg10:10 秒均值——用于触发即时告警
- avg60:1 分钟均值——用于趋势判断
- avg300:5 分钟均值——用于容量规划
三、PSI 数据解读指南
| 压力值 | 含义 | 建议动作 |
|---|---|---|
| < 5% | 基本无压力 | 无需干预 |
| 5-20% | 轻度压力 | 密切观察 |
| 20-50% | 中度压力 | 考虑扩容或优化 |
| > 50% | 重度压力 | 必须立即处理 |
3.1 典型压力模式
# 模式 1:CPU 饱和
CPU: some=85%, full=30% → CPU 扩容或优化热路径
Mem: some=2%, full=0% → 内存充足
IO: some=5%, full=0% → IO 良好
# 模式 2:内存瓶颈
CPU: some=30%, full=10% → CPU 尚可
Mem: some=75%, full=45% → 内存严重不足!
IO: some=20%, full=5% → IO 因 swap 上升
# 模式 3:IO 瓶颈
CPU: some=15%, full=5% → CPU 空闲
Mem: some=10%, full=3% → 内存尚可
IO: some=80%, full=60% → IO 严重受限!升级存储或调整 IO 模式
四、PSI 内核实现原理
4.1 内核数据结构
// kernel/sched/psi.c
struct psi_group {
/* 全局统计器——所有 CPU 共享 */
u64 avg_total[NR_PSI_STATES]; // 原子累计
u64 avg_last_update; // 上次更新时间
u64 avg_next_update; // 下次更新时间
/* 每 CPU 贡献器 */
struct psi_group_cpu *pcpus; // 每 CPU 本地累计
};
enum psi_states {
PSI_IO_SOME = 0,
PSI_IO_FULL,
PSI_MEM_SOME,
PSI_MEM_FULL,
PSI_CPU_SOME,
// 注意:CPU 没有 FULL(多核总有空闲)
NR_PSI_STATES
};
4.2 采样与更新流程
PSI 采用 定时器 + 每 CPU 对比 的更新策略:
1. 每个 CPU 在内核态关键路径记录:
- 从 "开始等待资源" → "获得资源" 的时间戳差值
- 该时间差计入对应资源的 "停滞时间"
2. 定时器每 2 秒触发一次(定时窗口):
- 收集所有 CPU 的停滞时间
- 计算增量 delta = 本次采样 - 上次采样
- 更新三个时间窗口的指数移动平均(EMA)
3. EMA 公式:
avg = avg * exp(-Δt/period) + delta * (1 - exp(-Δt/period))
其中 period = {10s, 60s, 300s}
4.3 任务状态追踪
PSI 通过 task_struct.flags 中的标志位追踪任务状态:
// include/linux/psi_types.h
#define TSK_IOWAIT (1 << 0) // 任务在等待 IO
#define TSK_MEM_SCHED (1 << 1) // 任务在内核内存分配路径中等待
#define TSK_RUNNING (0) // 任务正在运行
// 内核在调度点判断:
// - 任务进入等待 → 记录开始时间
// - 任务恢复运行 → 累计停滞时间到 per-CPU 计数器
五、PSI 用户态接口
5.1 procfs 全局接口
# 读取全局 PSI 压力
cat /proc/pressure/cpu
# 输出:
# some avg10=0.00 avg60=0.00 avg300=0.00 total=1234567
# full avg10=0.00 avg60=0.00 avg300=0.00 total=2345678
cat /proc/pressure/memory
# 输出:
# some avg10=5.23 avg60=12.34 avg300=8.56 total=9876543210
# full avg10=2.10 avg60=6.78 avg300=4.32 total=4567890123
cat /proc/pressure/io
# some avg10=1.00 avg60=3.00 avg300=5.00 total=1111111111
# full avg10=0.50 avg60=1.50 avg300=2.50 total=2222222222
5.2 cgroup v2 PSI 接口
# cgroup v2 中的 PSI 文件
cat /sys/fs/cgroup/mycontainer/cpu.pressure
cat /sys/fs/cgroup/mycontainer/memory.pressure
cat /sys/fs/cgroup/mycontainer/io.pressure
# —— cgroup v2 专属:可注册触发器 -- #
# 当压力超过阈值时唤醒等待进程
echo "some avg10=50" > /sys/fs/cgroup/mycontainer/cpu.pressure
5.3 Polling 感知接口(重点!)
cgroup v2 的 *.pressure 文件支持 poll(),可实现低延迟的压力感知:
// 示例:监听内存压力超过 50% 超过 10 秒
#include <poll.h>
int fd = open("/sys/fs/cgroup/myapp/memory.pressure", O_RDWR);
// 写入触发器:some 窗口,10s 内平均 ≥ 50%
write(fd, "some avg10=50", strlen("some avg10=50"));
// 等待触发
struct pollfd pfd = { .fd = fd, .events = POLLPRI | POLLERR };
poll(&pfd, 1, -1); // 阻塞直到压力超阈值
// 触发后的动作:暂停任务、缩减缓存、触发扩容...
5.4 systemd 集成
systemd 2.45+ 自动生成 service 单元的 PSI 度量:
# 查看 service 压力
systemctl show nginx.service --property=MemoryPressureUSec,CPUPressureUSec,IOPressureUSec
# 在 unit 文件中设置压力限制:
# /etc/systemd/system/myapp.service.d/limits.conf
[Service]
MemoryMax=4G
IOReadBandwidthMax=/dev/sda 100M
# 压力限制——超过即触发 OOM-kill 或 CPU 节流
MemoryPressureThresholdSec=10s
CPUPressureThresholdSec=30s
六、生产级实践:监控集成
6.1 Prometheus 监控方案
# Node Exporter 已支持 PSI 指标
# 指标名:node_pressure_cpu_waiting_seconds_total
# node_pressure_memory_stalled_seconds_total
# node_pressure_io_stalled_seconds_total
# 压力百分比指标(更直观)
node_pressure_cpu_some_ratio # 0-1 范围
node_pressure_memory_some_ratio
node_pressure_io_some_ratio
6.2 告警规则推荐
# Prometheus AlertManager 规则
groups:
- name: psi_alerts
rules:
- alert: HighMemoryPressure
expr: node_pressure_memory_some_ratio > 0.6
for: 5m
labels:
severity: critical
annotations:
summary: "节点 {{ $labels.instance }} 内存压力超过 60% 持续 5 分钟"
- alert: HighCPUPressure
expr: node_pressure_cpu_some_ratio > 0.8
for: 10m
labels:
severity: warning
- alert: HighIOPressure
expr: node_pressure_io_full_ratio > 0.5
for: 3m
labels:
severity: critical
6.3 容器化场景最佳实践
在 Kubernetes/Docker 中,PSI 提供了全新的资源调优视角:
# 1. Limit 调优
# 症状:容器 PSI Mem some=60%,OOMKill 未触发但延迟激增
# 方案:提升 memory.limit_in_bytes 或优化应用内存使用
# 2. CPU 分配调优
# 症状:CPU some=85%,full=25%
# 方案:考虑增加 CPU request 或优化锁争用
# 3. IO 调度调优
# 症状:IO some=70%,full=5%
# 方案:检查是否 swap 导致间接 IO 压力,升级存储介质
# 4. 混合指标决策树
if mem_full > 40%:
→ 内存瓶颈 → 扩容内存 / 优化缓存 / 限流
elif cpu_full > 30%:
→ CPU 瓶颈 → 扩容核心 / 异步化 / 锁优化
elif io_full > 30%:
→ IO 瓶颈 → 升级SSD / 调整 IO 模式 / 限流
else:
→ 系统健康 → 继续观察
七、PSI vs 传统指标对比
| 指标 | PSI | 传统工具 (top/iostat) |
|---|---|---|
| 度量维 | 任务等待时间的时间占比 | 资源使用率/饱和度 |
| 时间窗口 | 10s/60s/300s 三窗口 | 瞬时快照 |
| 预测价值 | avg10 高 → avg60 将更高(趋势) | 无法预测 |
| 可触发 | 支持 poll() 阻塞等待 | 需轮询 |
| 粒度 | 可为每个 cgroup 度量 | 通常只能全局 |
| 开销 | 极低(每 CPU 计数器 + 定时器) | 根据采集频率而定 |
7.1 PSI 的局限
- CPU 没有
full指标(多核总有空闲核) - 压力高不一定是坏事(NUMA 本地争抢可能不影响总体吞吐)
- 短时突发可能被 avg60 "稀释"
- 需结合其他指标(内存用量、CPU 利用率)综合判断
八、PSI 内核调优参数
8.1 采样频率
# 默认每 2 秒采样一次(无需调整)
# 通过 /etc/sysctl.conf 不可配置
# 编译时可调整 PSI_FREQ (kernel/sched/psi.c)
8.2 根 cgroup PSI(旧系统)
# 旧内核可能需要在根 cgroup 启用 PSI
# 新内核默认启用
8.3 Trip 点计算
PSI 的 "触发器阈值" 是相对于时间窗口计算的:
例: "some avg10=50"
→ 过去 10 秒中至少有一个任务停滞的时间 ≥ 5 秒时触发
用例优先级:
1. 即时告警:some avg10 > 70% → 1 秒内响应
2. 自动扩容:full avg60 > 40% → 1 分钟内扩容
3. 容量回顾:some avg300 > 20% → 需要中长期规划
九、实战调优案例
9.1 案例:Redis 内存压力感知
# 问题:Redis 在内存接近 limit 时响应延迟急剧上升
# 现象:PSI Mem some=65%,但系统内存用量仅 70%
# 原因分析:
# Redis 频繁触发 direct reclaim(分配时回收)
# → 每次分配都等待一小段时间 → 累计起来 avg10=65%
# 但整体内存尚未耗尽 → 传统指标未告警
# 解决方案:
# 1. 配置 vm.overcommit_memory = 2(禁止超额承诺)
# 2. 设置容器 memory limit = Redis 工作的 1.25 倍
# 3. 启用 THP(透明大页)减少 PTE 碎片
# 4. 通过 PSI 触发器在 mem_full > 30% 时主动淘汰缓存
9.2 案例:Kafka IO 压力诊断
# 消息吞吐下降但 CPU/内存正常
# PSI 数据:IO some=78%, full=42%
# iostat:%util=60%
# iostat 显示磁盘使用率 "仅60%"
# 但 PSI IO full=42% 说明几乎全部 IO 请求都在等待
# → 这是 IO 调度器不公平或设备队列深度不足导致的等待
# 调整方案:
# 1. echo deadline > /dev/sda/queue/scheduler
# 2. echo 256 > /dev/sda/queue/nr_requests
# 3. 若使用 NVMe,echo none > /dev/nvme0n1/queue/scheduler
十、总结
| 维度 | PSI 带来的变革 |
|---|---|
| 从被动到主动 | avg10 捕获早期信号 → 在宕机前行动 |
| 从整体到细粒度 | cgroup 级别 PSI → 精准定位مشكلة容器 |
| 从轮询到事件驱动 | pressure poll → 毫秒级响应,无空转 |
| 从使用率到体验感 | 困惑于 "CPU 仅 70%" → 直接看到 "任务等了 80%" |
| 从人工到自动化 | systemd + K8s + PSI → 自动伸缩与恢复 |
PSI 不只是一个内核特性,它是一种全新的资源健康管理范式。从 Kubernetes 到嵌入式系统,从数据库到实时应用,提供了一种统一的、低开销的、可编排的资源压力度量手段。在容器化和云原生时代,掌握 PSI = 掌握系统可观测性的下一程。

发表评论 取消回复