聊到 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_ratio10当脏页占内存比例达到此值时内核后台线程开始异步刷盘vm.dirty_ratio20当脏页占内存比例达到此值时写操作会被同步阻塞强制刷盘说白了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 SSDnr_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 中添加 elevatormq-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 dirty5. 知识体系内核 I/O 参数调优全景下面这张图是我自己总结的内核 I/O 参数调优的核心逻辑。你可以看到从应用层写数据到最终落盘中间经过了三个关键关卡Page Cache、块设备队列、磁盘控制器。每个关卡都有对应的参数可以调。6. 实战一套完整的调优流程说了这么多理论咱们来点实际的。假设你有一台 64GB 内存、NVMe SSD 的数据库服务器该怎么调# 1. 针对低延迟场景调小脏页阈值 sysctl -w vm.dirty_background_ratio2 sysctl -w vm.dirty_ratio5 sysctl -w vm.dirty_expire_centisecs500 sysctl -w vm.dirty_writeback_centisecs100 # 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 以内。说白了内核参数调优不是什么玄学就是搞清楚数据流经的每个环节然后对症下药。嗯关于内核参数调优今天就聊到这儿。记住一个原则没有银弹每个参数都要结合你的硬件、负载和业务场景来权衡。多压测、多观察、多调整慢慢你就能找到那个「甜点」。