内核参数调优:vm.dirty_ratio、nr_requests 等关键参数
聊到 I/O 性能调优,很多人第一反应是换 SSD、加内存条。但说实话,硬件砸钱谁都会,真正见功夫的地方,是操作系统内核那几十个参数怎么调。我见过太多项目,硬件配置拉满,结果跑个数据库还是卡成幻灯片——查到最后,就是几个内核参数没动过。
今天咱们就聊聊 I/O 栈里最关键的几个旋钮:vm.dirty_ratio、vm.dirty_background_ratio、nr_requests,还有它们的小伙伴们。这些东西调好了,你的磁盘吞吐能翻倍;调错了,数据丢了都不知道怎么丢的。
1. 脏页回写:vm.dirty_ratio 与 vm.dirty_background_ratio
先说说脏页。你写文件时,数据不会直接落盘,而是先写到内存里的 Page Cache。这些被修改过、还没写回磁盘的内存页,就叫「脏页」。内核有一整套机制,在后台默默地把脏页刷回磁盘。
这两个参数就是控制这个刷盘行为的阀门:
| 参数 | 默认值 | 含义 |
|---|---|---|
vm.dirty_background_ratio | 10 | 当脏页占内存比例达到此值时,内核后台线程开始异步刷盘 |
vm.dirty_ratio | 20 | 当脏页占内存比例达到此值时,写操作会被同步阻塞,强制刷盘 |
说白了,dirty_background_ratio 是「温柔提醒」,内核说:脏页有点多了,我派后台线程慢慢刷。而 dirty_ratio 是「拍桌子」——脏页太多了,所有写进程都给我停下来,等刷完再说。
核心原则:这两个值设得越大,写操作的突发吞吐越高,但一旦触发同步阻塞,延迟会剧烈抖动。设得太小,磁盘频繁刷写,吞吐上不去。
我在项目中遇到过一台日志服务器,白天高峰期写延迟动不动飙到 500ms 以上。查了一圈,发现 dirty_ratio 还是默认的 20%,但机器内存有 256GB。你想想看,20% 就是 51GB 的脏页!一旦触发同步阻塞,那得刷多久?后来我把 dirty_ratio 降到 5%,dirty_background_ratio 降到 2%,延迟瞬间稳定在 10ms 以内。
我的调优建议:
- 对于数据库、Redis 等对延迟敏感的应用:
dirty_background_ratio设 2-5%,dirty_ratio设 5-10% - 对于大文件写入、批量导入等吞吐优先的场景:可以适当调高,比如 10% 和 20%
- 如果磁盘是 SSD,建议把值设低一些——SSD 随机写性能好,频繁刷盘影响不大,但能换来更平滑的延迟
2. 块设备队列:nr_requests
接下来是 nr_requests。这个参数控制的是块设备请求队列的深度。你可以把它想象成磁盘门口的一个缓冲区——应用层发来的 I/O 请求先在这里排队,然后由 I/O 调度器决定谁先谁后。
查看当前值:
# 查看某个块设备的 nr_requests
cat /sys/block/sda/queue/nr_requests
# 通常默认是 128 或 256
这个值设大了,队列能容纳更多请求,磁盘可以更好地合并相邻请求,提升吞吐。但代价是单个请求的排队延迟会增加。设小了,队列容易满,新请求会被直接拒绝,应用层就得重试或阻塞。
我记得有一次帮一个视频平台调优,他们的转码服务器写入 HDD 阵列时吞吐死活上不去。我一看 nr_requests 只有 64,而磁盘是 7200 转的机械盘,寻道时间本来就长。把 nr_requests 调到 512 后,吞吐直接翻了一倍。为什么?因为队列深了,I/O 调度器有更多机会做合并和排序,减少了磁头来回移动的次数。
注意:对于 NVMe SSD,nr_requests 不宜设得过大。NVMe 的延迟已经很低了,队列太深反而会增加 CPU 开销和内存占用。我个人习惯给 NVMe 设 128-256,给 HDD 设 512-1024。
3. I/O 调度器:选择与参数
说到队列,就不得不提 I/O 调度器。Linux 内核提供了几种调度算法:
| 调度器 | 适用场景 | 特点 |
|---|---|---|
| mq-deadline | 通用场景,HDD 和 SSD 都可用 | 保证每个请求的 deadline,避免饿死 |
| bfq | 桌面、交互式应用 | 按进程分配 I/O 带宽,公平性好 |
| kyber | 低延迟场景,NVMe SSD | 根据延迟动态调整队列深度,延迟可控 |
| none | 高性能 NVMe SSD | 不做调度,直接下发,CPU 开销最低 |
查看和修改当前调度器:
# 查看
cat /sys/block/sda/queue/scheduler
# 修改(临时)
echo mq-deadline > /sys/block/sda/queue/scheduler
# 永久修改(grub 或 udev 规则)
# 在 /etc/default/grub 中添加 elevator=mq-deadline
我曾经踩过一个坑:一台全闪存存储节点,用了默认的 mq-deadline,结果 4K 随机读延迟一直在 200μs 左右下不去。换成 none 调度器后,延迟直接降到 80μs。说白了,NVMe SSD 本身已经足够智能,不需要内核再帮它做调度,反而多了一层开销。
4. 其他关键参数速览
除了上面几个,还有几个参数我经常调:
vm.dirty_expire_centisecs(默认 3000,即 30 秒):脏页最多能存活多久。超过这个时间,内核会强制刷盘。对于数据安全要求高的场景,可以调小到 500-1000。vm.dirty_writeback_centisecs(默认 500,即 5 秒):内核后台刷盘线程的唤醒间隔。调小可以更及时地刷脏页,但会增加 CPU 开销。block/queue/read_ahead_kb:预读大小。顺序读场景可以调大(比如 4096 KB),随机读场景建议调小甚至关闭。block/queue/max_sectors_kb:单次 I/O 的最大大小。对于大块传输场景,可以调大到 1024 或 2048 KB。
快速查看所有 I/O 相关参数:
# 查看块设备队列参数
ls /sys/block/sda/queue/
# 查看内存脏页参数
sysctl -a | grep dirty
5. 知识体系:内核 I/O 参数调优全景
下面这张图,是我自己总结的内核 I/O 参数调优的核心逻辑。你可以看到,从应用层写数据到最终落盘,中间经过了三个关键关卡:Page Cache、块设备队列、磁盘控制器。每个关卡都有对应的参数可以调。

6. 实战:一套完整的调优流程
说了这么多理论,咱们来点实际的。假设你有一台 64GB 内存、NVMe SSD 的数据库服务器,该怎么调?
# 1. 针对低延迟场景,调小脏页阈值
sysctl -w vm.dirty_background_ratio=2
sysctl -w vm.dirty_ratio=5
sysctl -w vm.dirty_expire_centisecs=500
sysctl -w vm.dirty_writeback_centisecs=100
# 2. NVMe SSD 用 none 调度器,减少开销
echo none > /sys/block/nvme0n1/queue/scheduler
# 3. 队列深度适中,避免 CPU 浪费
echo 128 > /sys/block/nvme0n1/queue/nr_requests
# 4. 关闭预读(数据库随机读为主)
echo 0 > /sys/block/nvme0n1/queue/read_ahead_kb
# 5. 单次 I/O 大小设为 1MB(适合数据库日志写入)
echo 1024 > /sys/block/nvme0n1/queue/max_sectors_kb
重要提醒:以上修改都是临时的,重启后失效。要永久生效,请写入 /etc/sysctl.conf 或使用 sysctl.d 配置文件。块设备参数可以通过 udev 规则或 systemd 服务来设置。
我曾经帮一个电商平台做双十一前的压测,他们的 MySQL 服务器在高峰时写延迟从 2ms 飙到 200ms。我按上面的思路调了一遍,延迟稳定在 1ms 以内。说白了,内核参数调优不是什么玄学,就是搞清楚数据流经的每个环节,然后对症下药。
嗯,关于内核参数调优,今天就聊到这儿。记住一个原则:没有银弹,每个参数都要结合你的硬件、负载和业务场景来权衡。多压测、多观察、多调整,慢慢你就能找到那个「甜点」。
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐


所有评论(0)