脏页积压主因是vm.dirty_background_ratio过低导致回写过早且不畅,而非vm.dirty_ratio未达阈值;需同步调高background_ratio(15–25)、dirty_ratio(30–40),并缩短dirty_expire_centisecs(1000–1500)以均衡回写节奏。

为什么 vm.dirty_ratio 调高了,脏页还是积压?
因为内核不会等脏页涨到 vm.dirty_ratio 才开始回写——它更早就会触发异步回写,而触发阈值由 vm.dirty_background_ratio 控制。如果这个值太低(比如默认 10),系统会频繁唤醒 pdflush(或现代内核的 writeback 线程),但线程本身可能被 I/O 延迟卡住、或受限于设备吞吐,导致脏页“产速>写速”,越积越多。
常见错误现象:iostat -x 显示 %util 持续低于 30%,但 /proc/meminfo 中 Dirty: 和 Writeback: 居高不下,应用 write() 延迟升高。
-
vm.dirty_background_ratio建议调至 15–25(视内存总量而定),避免过早、过碎的回写打断业务 IO -
vm.dirty_ratio可同步上调至 30–40,为突发写留出缓冲空间,但别超过 50,否则sync()或内存回收时容易卡死 - 必须配对调整
vm.dirty_background_bytes和vm.dirty_bytes(二者与 *_ratio 互斥),否则 ratio 设置会被忽略
vm.dirty_expire_centisecs 设太长,脏页就“赖着不走”
这个参数决定脏页在内存里最多“躺”多久才必须被回写(单位是厘秒,即 1/100 秒)。默认 3000(30 秒),看似宽松,但在高吞吐写场景下,大量脏页会在 30 秒内反复被标记为“可回写”,却因 writeback 线程调度或磁盘队列阻塞迟迟没发出,最终全部挤在 expire 临界点前集中冲刷,造成 I/O 尖峰。
使用场景:SSD 或 NVMe 后端、日志型写入(如 Kafka broker、数据库 WAL)、容器环境共享宿主机 page cache。
- 把
vm.dirty_expire_centisecs从 3000 降到 1000–1500(10–15 秒),让回写节奏更均匀 - 注意:设太短(如
- 该值不影响已进入
Writeback:状态的页,只约束“脏了但还没排队”的页
为什么 iostat %util 看着不高,磁盘其实已经饱和?
%util 是基于设备忙闲时间统计的,对 NVMe 或多队列 SCSI 设备意义很弱——它只看单个请求队列是否 busy,而现代存储能并行处理数百请求。实际瓶颈常在文件系统层(如 ext4 journal 锁)、块层调度器(cfq 已弃用,但 mq-deadline 的 deep queue 行为难预测),或 RAID 卡缓存策略上。
性能影响:%util 30% 时,await 可能已超 20ms,svctm 失真,avgqu-sz 持续大于 4 就说明队列深度压满。
- 优先看
iostat -x 1的avgqu-sz和await,而非%util - 确认存储栈:裸盘?LVM?mdadm?ZFS?不同层有各自的缓存和限流逻辑,page cache 回写会穿透所有层
- 用
perf record -e 'block:*' -a sleep 10抓块层事件,看block_bio_queue是否堆积
容器或 KVM 里改 vm.dirty_* 参数没效果?
因为 cgroup v1 的 memory 子系统默认不隔离 page cache 脏页控制参数;cgroup v2 虽支持 memory.pressure,但 vm.dirty_* 仍是全局 sysctl,容器内修改只作用于自身命名空间,宿主机内核仍按原值调度 writeback 线程。
兼容性影响:Kubernetes Pod 的 securityContext.sysctls 只允许 fs.*、net.* 等白名单,vm.* 默认禁止写入,强行加会启动失败。
- 必须在宿主机层面统一调优,容器内仅可通过
sync()、fsync()主动干预,或挂载noatime,nobarrier减少元数据写压力 - 若用 systemd-run 启动服务,可用
--scope --property=MemoryLimit=...配合vm.swappiness=1间接减少脏页生成 - 云厂商自研存储(如阿里云 ESSD、AWS io2)通常关闭 host 端 writeback,依赖实例内应用直写,此时调
vm.dirty_*完全无效
最易被忽略的一点:脏页堆积未必是回写慢,也可能是应用持续 write() + mmap(MAP_SHARED) 修改,且没调 msync(),导致 page cache 脏页生命周期完全脱离内核 writeback 控制节奏。










