资讯动态

Linux IO性能调优实战:调度器、块层队列、零拷贝与压测方法

发布时间:2026/10/21 15:10:06 来源:尧图企业网站定制
上次聊IO的时候我花了挺多篇幅讲page cache、脏页回写和iostat怎么看。评论区问得最多的不是“我理解某某概念”而是“那我到底该调什么”。这篇就接着往下聊IO调度器、块层队列深度、文件系统挂载参数、零拷贝以及一套能说清楚问题的压测方法。适合觉得系统响应慢、但又不想盲目换硬件的后端开发和运维同学。Linux的IO优化是个典型的洋葱模型。你要是不知道一次写请求在内核里过了哪些关卡调参就只能是玄学。先把这条路走一遍再顺着瓶颈一层层拆比照着网上的“一键优化脚本”靠谱得多。下面分享的内容不保证参数万能但每个参数背后我都会说清楚适用场景和取舍依据。1. 先看看一次IO到底卡在哪一层1.1 一次写请求在内核里要过几道门一次写请求从应用程序发出来大体要经过系统调用、VFS层、具体文件系统、page cache、块层、IO调度器、设备驱动最后才轮到磁盘。数据库每次把日志刷盘看起来只是在写一个文件实际上这条链路上的每一步都可能成为瓶颈。很多人习惯性怀疑是磁盘坏了但不少延迟恰恰是在中间排队时排出来的。块层会按偏移合并相邻请求调度器会重新排列请求顺序文件系统则决定这些块什么时候真正落到设备上。你只盯着最后的us没有意义因为时间可能花在更早的地方。普通write()返回时数据其实还躺在page cache里内核会在后台根据脏页比例、回写周期把数据刷到磁盘。正因为有这个异步机制缓冲IO的吞吐表现往往很好看但一旦触发writeback延迟就会集中爆出来。所以你会发现一个典型现象平时写入挺快过一会儿突然卡一下。那不是磁盘抽风是内核在集中还债。1.2 快速定位瓶颈的四个顺手命令第一个是iostat -x 1主要看r/s、w/s、avgqu-sz、await和%util第二个是pidstat -d 1按进程维度看IO读写速度第三个是vmstat 1观察bi/bo、si/so如果swap读写频繁那系统根本不是IO慢而是内存不够在换页第四个是iotop直接看哪些进程把磁盘带宽吃掉了。这四个命令配合使用能在五分钟内把问题范围缩小一大圈。如果iostat显示磁盘几乎不忙而业务还是慢瓶颈多半不在设备而在文件系统或内存回写如果avgqu-sz长期大于设备队列深度说明请求已经积压调度器或驱动在排队如果IO等待集中在少数几个进程上顺着进程去查系统调用和日志往往比调系统参数更直接。遇到问题先做这一步后面所有调优才有方向。2. 调度器和块层参数别急着换盘2.1 现在还在服役的IO调度器老教程里提到的CFQ在新内核里已经退场现在还能看到的通常是mq-deadline、bfq、kyber和none。它们都是为多队列块设备设计的。none并不是关闭排序而是把顺序和优先级的选择权交给上层和设备对NVMe这类已经内置并发调度能力的设备none往往能降低排队延迟。mq-deadline会按期望完成时间排序避免某些请求被饿死兼顾了吞吐和公平性。bfq对流控和带宽公平比较执着适合桌面或交互负载。kyber则根据延迟动态调节并发适合延迟敏感的服务。选择的时候不要过度神话调度器的效果。调度器调优能带来的收益通常不是让吞吐翻倍而是减少尾延迟。机械盘场景下deadline通过重新排序能让寻道明显减少这对顺序和随机混合负载很关键而NVMe上如果业务全是随机IOdeadline的排序意义就不大多一层排队反而碍事。2.2 不同负载怎么选调度器业务场景典型设备建议调度器原因数据库写入和读多写少NVMe/SSDnone 或 mq-deadline数据库本身已经做了并发和顺序控制不需要调度器过度干预高并发日志、消息写入SSDmq-deadline有期限控制避免某些请求长时间饥饿桌面、交互、多媒体机械盘bfq对流控更公平能减少应用卡顿感大批顺序读写的备份任务机械盘mq-deadline排序和合并能减少寻道吞吐比bfq更稳这个表只是起点。真实判断方法很简单在你的实际负载下分别切换调度器配合fio或业务压测跑半小时对比平均延迟和P99延迟。只看平均负载或吞吐很容易错过偶发尖峰。2.3 块层队列深度怎么调/sys/block/sda/queue/nr_requests控制块层还能容纳多少个请求/sys/block/sda/device/queue_depth是设备侧允许排队的深度。这两个值不是越大越好。队列深了吞吐可能上去但每个请求在队列里等待的时间也会拉长P99延迟容易变得难看深度小了设备可能经常空闲吞吐又上不来。调整时建议一次加64或128每次改完都跑一轮fio对比QPS和P99延迟。如果P99开始明显上涨但QPS没怎么变说明队列已经过深回退一级。需要特别提醒的是sysfs里的修改是非持久的重启就失效真要上线使用就把改好的值写进开机执行的脚本里顺便留一份修改前的基线记录。3. 文件系统层调优挂载参数不是玄学3.1 commit、noatime和delalloc文件系统层面的参数很多是通过影响“什么时候落盘”和“落盘多少”来改变性能的。挂载时加noatime会减少atime更新但现代很多发行版默认就是relatime收益不像十年前那么明显所以别指望一个noatime就能救系统。判断方法很简单如果监控里看到大量分布均匀的小写IO而且不是业务产生的那才有可能和访问时间更新有关这时再考虑加noatime。ext4的commitN参数控制日志提交周期默认一般是5秒。调大它能降低日志提交频率把多个事务攒一起落盘对小写频繁的场景有帮助代价是掉电或崩溃时最多可能丢失最近N秒内的数据。做数据库和消息队列的同学要格外慎重应用层已经做了一套预写日志再把commit拉得特别大等于把可靠性赌在内核日志周期上风险并不划算。老教程喜欢教人关barrier提升性能这个我建议别碰。现代文件系统默认行为已经考虑到元数据一致性关掉它只是拿掉一道安全防线换来的性能提升通常很有限。delalloc则是默认开启的数据延迟分配它先把小写入攒在内存里再找连续块一次性落盘能减少碎片和写放大。除非你在做底层文件系统开发否则别关。3.2 ext4日志模式的三种选择日志模式行为特点适用场景ordered数据先落盘再写元数据日志一致性和性能均衡大多数生产环境的默认选择writeback数据和元数据落盘顺序不保证写入性能最好但崩溃后风险高可重建的缓存、临时目录journal数据和元数据都写日志安全等级最高但写入放大明显对一致性要求极高的关键元数据挂载时用mount -o datawriteback /dev/sdb /data这类方式切换先临时挂载验证再写进fstab。datawriteback并不是直接损坏文件系统而是崩溃后可能出现文件数据和大小不一致所以不要把重要的数据库数据放在这种模式下。ordered模式兼顾性能和一致性除非你有非常明确的理由否则就留在默认值。3.3 小文件多怎么办inode和预分配小文件多最容易踩的坑不是IO慢而是inode不够用。df -i看inode使用率如果使用率已经到80%以上哪怕磁盘还有大量剩余空间新文件也可能创建失败。格式化阶段可以用mke2fs -i 4096调整让每个4KB空间分配一个inode给小文件场景预留更多索引节点。这会牺牲一部分空间但能换来文件创建能力的增长。已经跑起来的系统如果inode不足只能迁移数据后重新格式化成本很高所以早期规划很重要。对大量会不断增长的文件可以用fallocate提前占住一段连续区间避免写入时反复搜索和扩展extent。这样写入延迟会稳定很多代价是提前占用了容量。比较适合索引文件、预分配缓存这类场景。做之前先估算文件最终大小别随手分配一个天大的空间否则磁盘利用率会变得很难看。4. 从sendfile到io_uringIO快的另一条路4.1 传统readwrite到底多干了什么活传统的文件传输是read加writeread把磁盘数据从内核page cache拷到用户缓冲区write再把用户缓冲区的数据拷回内核socket缓冲区。一次发送至少要经历两次用户态和内核态的复制对高并发和超大文件来说特别浪费。实际上操作系统本来可以用DMA把磁盘数据直接搬到内存再用DMA从内存交给网卡只有中间这段用户态的搬运是完全可以省掉的。零拷贝的最终目标就是减少或去掉这段无意义的搬运。这也是为什么高并发Web服务在处理大文件时会刻意选择文件发送专门接口而不是自己读文件再写socket。性能差异在文件小的时候看不出来等并发上来以后CPU时间大量消耗在数据复制上系统吞吐会明显下降。4.2 sendfile、mmap和splice的边界sendfile让内核直接在文件页缓存和socket缓冲区之间搬运数据彻底绕过了用户态。它在静态文件、图片服务、CDN加速场景非常有用很多Web服务都在用这套机制优化文件发送。要留意的是sendfile并不是所有情况下都更快文件太小的时候系统调用的开销占比可能抵消掉省下来的复制成本。文件越大、并发越高收益越明显。mmap适合你本身就要在内存里反复修改文件内容的场景比如需要随机改写大文件的索引结构用它避免每次read都拷贝一遍。但mmap也有page fault成本脏页回写还会带来额外延迟别只看性能测试里的好看数据。splice则是在两个文件描述符之间搬数据适合管道和流式转发场景但它不是万能组合比如很难直接把socket接到socket上做转发。选哪个取决于你的数据流向和访问模式。4.3 io_uring值不值得追io_uring的价值不只是省拷贝它把提交和收割请求这两个系统调用换成了共享内存里的环形队列大幅减少并发IO带来的上下文切换开销。存储引擎、代理缓存、数据库这类追求高吞吐高并发的中间件确实值得接io_uring。代价是使用门槛高需要配合异步框架普通的业务代码想享受它的红利前期改造量不小。如果系统调用和上下文切换还远不是瓶颈那不如先把并发模型和文件系统参数调好。以我的经验大多数应用连sendfile和带深度的异步IO都还没用明白直接上io_uring只会增加排查难度。做存储基础组件或中间件的团队可以考虑普通后端业务先等等把基础优化做扎实收益来得更稳妥。5. 压测先跑明白fio、iostat和blktrace配合用5.1 fio参数选对了结论才靠谱fio最容易被低估的两个参数是iodepth和direct。direct1会绕过page cache直接向块设备发IO测的是设备真实能力而不是内存命中率。iodepth表示每个进程同时有多少个IO挂在队列里模拟的是应用层并发IO能力。举个例子测随机读可以这样跑fio --namerandread --ioenginelibaio --rwrandread \ --bs4k --size1G --numjobs4 --iodepth32 \ --direct1 --time_based --runtime60 --group_reportingruntime建议给足60秒以上太短的话缓存和调度器都还没进入稳态测出来的数据会偏乐观。numjobs和iodepth的乘积决定总并发数不能拍脑袋要贴近业务的连接数、线程数和队列模型。--group_reporting会把多个进程的结果聚合成一份报告否则4个进程各自输出一遍看起来非常痛苦。5.2 常见业务场景怎么压大文件顺序读写的核心指标是稳定吞吐适合用大块IO测fio --nameseqwrite --ioenginelibaio --rwwrite \ --bs1m --size1G --numjobs4 --iodepth32 \ --direct1 --time_based --runtime60 --group_reporting数据库场景更关心小随机读写和延迟分布适合混合读写fio --namedb_sim --ioenginelibaio --rwrandrw --rwmixread70 \ --bs8k --size1G --numjobs8 --iodepth32 \ --direct1 --time_based --runtime60 --group_reporting日志和消息类写入主要是顺序追加重点看并发写入下的延迟稳定性fio --nameappend_log --ioenginelibaio --rwwrite \ --bs4k --size1G --numjobs8 --iodepth16 \ --direct1 --time_based --runtime60 --group_reporting没有一套放之四海皆准的fio参数。真正有效的做法是先想清楚业务的IO类型读多还是写多大块还是小块随机还是顺序然后反过来设计压测参数。5.3 压测时怎么配合iostat、blktrace和ioping跑fio的同时开一个iostat -x 1会让结果更有说服力。如果fio平均延迟低而磁盘%util已经到100%说明设备并发能力有限继续提高iodepth只能让延迟更难堪。如果avgqu-sz很大、await很高而%util并不高多半是排队时间变长需要回头查调度器和队列深度。svctm在新版iostat里意义有限不用太当真。blktrace可以抓取块层请求重放在哪个环节出了问题。blktrace -d /dev/sdb -o trace -w 5抓5秒然后用blkparse -i trace播放分析能看到IO是在调度器排队还是在驱动层卡住。ioping则适合测延迟分布比如ioping -c 100 -s 4k /data会给出平均延迟和最大延迟对定位偶发尖峰很有帮助。这两个工具都需要root权限压测时注意选一个实际负载不会太重的磁盘否则结果会互相干扰。6. 常见问题与排查技巧实录6.1 iowait高但磁盘不忙问题出在哪遇到这种表现先用vmstat区分是CPU等待还是内存回收。如果si/so比较高说明内存在换页系统看起来像IO慢本质是内存不够。再看脏页情况脏页过多会触发后台writeback写盘任务变得集中这时现象是阶段性的高延迟。可以通过调整dirty_background_ratio让回写更平滑也可以调低dirty_ratio降低同步写阻塞的概率。调整后观察一两天重点看P99延迟有没有下降而不是盯着平均负载。还有一种容易被忽略的情况是文件系统层面的锁等待。大量小文件创建时目录索引结构可能被频繁更新并发线程虽然没在等磁盘但都在等锁这时候iowait不高业务却明显卡住。用strace或者perf看进程在哪个内核函数上卡住比盲目调磁盘参数有效得多。6.2 util不高但延迟抖动明显是谁在拖后腿磁盘util在iostat里的定义是设备有请求处理的时间占比不是设备负载百分比。SSD并发处理能力强util很高也可能延迟正常机械盘随机IO时util不高也可能已经在排队。单看util判断要不要扩容是很危险的应该结合avgqu-sz、await和fio的P99数据一起看。现实中我见过一台机器报警说磁盘忙深入一查是同一时间有大量小文件创建inode目录锁争抢根本不是磁盘问题。如果延迟尖峰呈周期性出现和日志提交周期对得上大概率是ext4的commit参数和业务fsync频率互相冲突。可以调大commit让日志批次更宽也可以优化业务端把多次fsync合并成一次。关键是先找到尖峰的时间和频率规律再决定动哪一层。6.3 常见排查速查表现象优先排查调整方向iowait高但设备不忙内存换页、脏页回写、文件系统锁调整内存回收参数和脏页阈值写入延迟周期性尖峰ext4日志提交、fsync频率调整commit参数合并业务fsyncutil高但延迟正常设备并发能力、队列深度结合吞吐和P99判断不一定扩容随机读性能差调度器、文件系统块大小用fio验证设备真实iops后逐层排查小文件创建慢inode使用率、目录索引预留inode优化目录结构最后分享一个我自己踩过的坑。有一次把一台机械盘的调度器从mq-deadline换成了none想着少一层排队会更快。结果备份任务的吞吐不升反降因为机械盘本身没有重排序能力deadline对顺序IO的合并和优先级控制恰恰是帮大忙的。后来换回mq-deadline时间才恢复正常。IO调优的麻烦就在这同一个参数在不同设备上的表现能完全相反。所以动手之前一定要留好基线数据改一个参数观察一段时间尽量不要一次性把所有优化都堆上去。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑