vm.dirty_ratio和vm.dirty_background_ratio引发卡顿的直接原因是:脏页超vm.dirty_background_ratio时后台刷盘启动,超vm.dirty_ratio(默认20%)则写操作被阻塞;现代大内存与磁盘吞吐不匹配导致100GB脏页刷盘耗时数分钟,引发wa%飙升、D状态进程堆积。

为什么 vm.dirty_ratio 和 vm.dirty_background_ratio 会引发卡顿
Linux 内核用这两个参数控制脏页(被修改但尚未写入磁盘的内存页)的积压上限。当脏页总量超过 vm.dirty_background_ratio(默认 10),内核在后台启动 pdflush 或 writeback 线程开始异步刷盘;一旦突破 vm.dirty_ratio(默认 20),所有新写操作会被阻塞,直到脏页降到该阈值以下——这就是“突然卡住”的直接原因,尤其在突发大量小文件写、日志刷盘或数据库 checkpoint 场景下极易触发。
- 典型现象:
top中wa%暴涨,iostat -x 1显示%util接近 100% 且await超高,ps aux | grep "D"可见大量D状态进程(不可中断睡眠) - 根本矛盾:默认值按总内存比例计算,但现代服务器内存大(如 512GB)、而磁盘吞吐有限(如单块 SATA SSD 约 500MB/s),20% 就是 100GB 脏页,全刷完可能耗时数分钟
-
vm.dirty_ratio是硬上限,触达即阻塞;vm.dirty_background_ratio是软启动点,差值越小,后台刷盘越激进,但也越容易抢占 I/O 资源
怎么设才不卡:看负载类型选策略
不能只盯着百分比,得结合实际写负载节奏和存储能力。关键不是“调低”,而是让脏页生成速度 ≤ 后台持续刷盘能力。
- 高吞吐顺序写(如 Kafka 日志、视频转码输出):
→ 适当提高vm.dirty_background_ratio(如 15)+vm.dirty_ratio(如 25),配合增大vm.dirty_expire_centisecs(如 6000,即 60 秒),避免频繁触发刷盘打断写流 - 随机小写 + 高延迟敏感(如 OLTP 数据库、容器化微服务):
→ 压低两参数(如vm.dirty_background_ratio=5,vm.dirty_ratio=10),并缩短vm.dirty_writeback_centisecs(如 500,即 5 秒),让脏页更早、更碎地刷出,减少单次阻塞风险 - 混合负载(如 K8s 节点跑多种工作负载):
→ 折中设为vm.dirty_background_ratio=10,vm.dirty_ratio=15,并务必启用vm.swappiness=1(抑制 swap,防止脏页和 swap 争内存)
调参后必须验证的三件事
改完 sysctl 不等于生效,也不代表问题消失。要确认内核真正按预期行为运行。
- 检查是否加载:
sysctl vm.dirty_background_ratio vm.dirty_ratio,确保输出值与配置一致;若未生效,执行sysctl -p - 观察实时脏页压力:
grep -i dirty /proc/meminfo,重点关注Dirty:、Writeback:、PagesDirty:,对比MemTotal看是否长期贴近vm.dirty_ratio限值 - 验证刷盘节奏:
echo 1 > /proc/sys/vm/drop_caches后做一次可控写测试(如dd if=/dev/zero of=/tmp/test bs=1M count=2000 oflag=direct),用iostat -x 1看 write 出现是否平滑、有无突刺式长 await
容易被忽略的关联项
单独调这两个参数常治标不治本,以下三点不处理,调了也白调。
-
vm.dirty_ratio的单位是“占MemTotal的百分比”,但若启用了 cgroup v2 memory controller,容器内看到的MemTotal是其 memory.max 限制值,此时脏页上限按容器限额算——K8s 默认不限制 memory,但很多生产环境会设 limit,导致容器内脏页更快触顶 - SSD/NVMe 场景下,
vm.dirty_background_ratio设太低(如 3)反而增加 I/O 次数,降低吞吐;应优先调vm.dirty_writeback_centisecs控制刷盘频率,而非一味压低比例 - 某些发行版(如 RHEL 8+/CentOS 8+)默认启用
systemd的MemoryMax,会覆盖全局vm.dirty_*设置,需在/etc/systemd/system.conf中显式设置DefaultLimitMEMLOCK=infinity并重启systemd
logbsize)联动变化。调参只是入口,背后得连着 I/O 栈一起看。









