资讯动态

Linux块层IO调度器选型与调优实战

发布时间:2026/10/27 6:00:09 来源:尧图企业网站定制
1. 先把问题摆到桌面上调度器到底在管什么很多人做 Linux IO 优化上来就翻iostat看到%util打到 100、await飙到几十毫秒第一反应是盘不行了换 NVMe。结果盘换了业务延迟还是抖。我在好几个线上环境里都撞过这堵墙后来才发现真正被忽略的一环是块层的 IO 调度器——那个躺在/sys/block/dev/queue/scheduler里、一个方括号就能切换的小东西。它解决的问题很朴素应用提交到内核的 IO 请求是杂乱无章的可能上一秒在读 inode 元数据块下一秒在写日志尾部再下一秒又跑去读一个完全不相邻的扇区。机械盘的头只有一个闪存盘的通道和 die 也是有限的如果完全按到达顺序硬怼下去寻道时间、通道争用、写放大全都会失控。调度器干的事情就是在请求进入设备队列之前做三件事合并相邻扇区的请求、排序以减少物理跳转、控制分发节奏避免某一类 IO 把其他类型饿死。这套逻辑对谁有用我把它分成三类人。第一类是运维和 SRE手里有几十上百台机器需要批量定策略关心的是默认值要不要改、改了会不会翻车。第二类是后端开发自己的服务跑在共享盘上偶尔被同机邻居拖慢想知道能不能从内核层给自己争一点公平。第三类是刚接触性能调优的同学看了一堆mq-deadline、kyber、bfq的名词搞不清它们的分界线在哪里。这三类人的诉求不一样但底层判断依据是同一套。下面我按为什么这么设计 → 怎么选 → 怎么调 → 怎么验证 → 哪里会踩坑的顺序讲中间会给出可以直接抄的参数和脚本。需要提前说一句调度器不是银弹它只能优化请求排队与分发这一段如果你的瓶颈在文件系统元数据锁、在 page cache 回写策略、在应用自己的同步写模型上切调度器基本无效。判断瓶颈在哪一层比选哪个调度器重要十倍。2. 调度器的设计逻辑为什么是电梯而不是先来先服务2.1 从一次 iowait 异常说起早些年我在一台跑数据库的机器上遇到过典型场景业务侧的慢查询告警每隔十几分钟来一波top里%wa冲到 30 以上但磁盘的吞吐量看起来并不高。抓iostat -x 1看r/s和w/s都不大aqu-sz平均队列长度却时不时冒到几十await在峰值能到 80ms。这个组合很说明问题吞吐不高但队列长、延迟高意味着请求在队列里排队的时间远大于设备实际服务的时间。换句话说设备没被喂饱但请求被堵住了。继续往下查发现同一块盘上既有大量 4K 随机读业务查询又有周期性的顺序写日志刷盘两者混在一个队列里互相踩。这时候调度器的排序和饥饿保护机制就成了关键——如果没有它一个长时间的批量顺序写完全可能把前面的随机读全部压住因为写请求是连续下发的队列一直处于填满状态。加上deadline当时还是单队列版本之后await峰值从 80ms 降到 20ms 出头慢查询告警频率明显下降。这个案例让我形成了一个习惯看到高延迟 低吞吐 长队列的组合先别怀疑硬件先看调度器配置。2.2 合并、排序、饥饿避免三件事缺一不可把调度器拆开看核心就三个动作理解了这三个动作所有调度器变体的差异都好解释了。合并merge分两种。一种是前向合并新请求正好接在已有请求的尾部扇区后面另一种是后向合并新请求正好补在头部前面。合并成功意味着两个请求变成一个设备只需服务一次。对机械盘来说这是纯赚对闪存盘来说也能减少命令开销。合并效率取决于请求的扇区连续性顺序读写的合并率天然高随机 IO 基本合不上——所以别指望调度器能救随机 IO 的命。排序sort是电梯算法的本体。把所有待处理请求按起始扇区号排成一列磁头从当前位置出发一路扫过去扫到头再反向扫回来。这样做的好处是寻道距离最短代价是——如果新来的请求扇区号在磁头已经扫过的区域它得等下一轮。这就是所谓的饥饿。饥饿避免aging / expire就是给每个请求设一个最长等待时间超时了不管顺序如何都强行插队处理。mq-deadline里的read_expire和write_expire就是干这个的读默认 500ms写默认 5000ms这个 10 倍差距是有讲究的读一般是同步的应用在等结果延迟直接转化成用户感知写多半是异步回写稍微慢一点用户察觉不到所以给写更长的容忍窗口换取更好的顺序性。提示读过期时间远小于写过期时间是绝大多数场景下的正确设定。除非你的业务是写密集且写操作同步等确认比如某些日志系统的 fsync 模式否则不要轻易把这两个值调成一样或反过来。2.3 调度器在内核 IO 栈里的位置搞清楚位置才知道它能改什么、不能改什么。一次写操作的大致路径是应用调用write()→ 进入 page cache标记脏页 → 回写线程或直接写场景把脏页转成 bio → bio 提交到块层 →块层合并成 request 并交给调度器排队→ 调度器按策略把 request 分发给设备队列 → 驱动把 request 转成硬件命令 → 设备执行。关键点在于调度器看到的是 request不是 bio也不是应用层的 IO。它不关心这个请求来自哪个进程、属于哪个文件它只看扇区号、大小、读写方向和到达时间。所以如果你想让某个进程的 IO 优先那属于 cgroup 的 io 控制器和ionice的职责范围跟调度器是两个层面的东西得配合着用。另一层要理解的是多队列blk-mq架构。老内核里整块设备只有一个请求队列、一把锁多核高并发下锁竞争严重。新架构给每个 CPU 或者每组 CPU 分配一个软件队列再映射到设备的硬件队列上锁的粒度小了扩展性好了。但这也带来一个副作用调度器是作用在每个软件队列上的全局的排序效果被削弱了。这解释了为什么在 NVMe 这种深队列设备上复杂的排序算法收益很小甚至为负——每个队列各自为战跨队列的合并和排序根本做不到反而多了一层计算开销。3. 主流调度器的选型逻辑与适用边界3.1 none名字骗人它并不是什么都不做none老内核里叫noop是最容易被误解的一个。它不做排序但合并还是做的——同一个队列里扇区相邻的请求依然会被合并成一个。它只是省掉了排序和维护红黑树的开销。什么时候选它当设备本身足够聪明或者足够深的时候。NVMe 盘内部通常有几十上百个队列每个队列能挂 64 甚至更多命令设备固件自己有成熟的命令调度逻辑能处理乱序到达。内核层再排一遍序收益接近于零而每个 IO 多走的那些计算路径却是实打实的成本。在超高 IOPS 场景百万级 4K 随机读下none相比mq-deadline能有百分之几到十几的 IOPS 提升这个数字在高并发下不算小。反过来说如果你的设备是 SATA SSD 或者机械盘队列深度只有 1 或者 32设备内部没有复杂的调度能力那none会让请求完全按到达顺序硬怼写请求堆积时读延迟会很难看。我见过有人在机械盘上把调度器设成none追求低开销结果备份任务一跑前端接口全卡这就是典型的误用。3.2 mq-deadline延迟敏感场景的稳妥答案mq-deadline是当前多数发行版在多队列设备上的默认值这个默认值得很合理。它保留了截止时间机制读请求 500ms、写请求 5000ms 的过期保护在混合读写场景下表现稳定不会出现某一类请求被无限期饿死的情况。它的内部结构比none复杂但复杂度可控读写各维护一个按扇区排序的红黑树加上一个按过期时间排序的 FIFO 链表分发时优先看 FIFO 里有没有过期的没有就按批次从红黑树里取。批次数由fifo_batch控制默认 16意思是连续从排序树里取 16 个请求后回头检查一次 FIFO防止低优先级的请求等太久。writes_starved默认 2表示读批次执行 2 轮后才考虑放一批写过去这是读优先策略的体现。我个人的经验是只要你不确定该选哪个就选mq-deadline。它在数据库、消息队列、容器宿主机这些混合负载场景下都没有明显短板。它的性能不是最高的但抖动是最小的而生产环境里稳定通常比峰值高 5%值钱得多。3.3 kyber为低延迟闪存设计的轻量选手kyber是后来加入的设计目标很明确在多队列设备上用极低的 CPU 开销实现延迟控制。它不用红黑树排序而是维护两个 FIFO 队列读和写靠令牌桶机制控制分发节奏——每个队列有一定数量的令牌取一个请求消耗一个令牌令牌耗尽的队列会被暂时挂起直到有请求完成归还令牌。它的可调参数很少主要就是read_lat_nsec和write_lat_nsec默认读 2ms、写 10ms代表你期望的延迟目标。内核会根据实测延迟动态调整内部状态。参数少是优点也是缺点优点是几乎不用调缺点是遇到特殊负载时你能动的手脚不多。适用场景我总结为中等队列深度的 NVMe负载以随机小 IO 为主且 CPU 资源紧张。如果你的机器上 IO 线程本身就吃满了 CPUkyber的低开销特性会有帮助。但如果你的负载是重度的顺序大块写kyber的 FIFO 结构缺乏排序能力表现会不如mq-deadline。3.4 bfq公平性优先桌面和混布场景的选择bfqBudget Fair Queueing走的是另一条路。它不是按请求排序而是按进程/队列分配带宽预算保证每个参与者都能拿到相对公平的份额。它的目标是让交互式任务的延迟不被后台批量任务拖垮——这也是它长期作为桌面发行版默认调度器的原因一边在后台解压大文件一边拖窗口界面依然跟手。代价是开销。bfq需要跟踪每个队列的历史行为、计算预算、做扇区数启发式估算CPU 占用明显高于其他几个。在高 IOPS 场景下这个开销会直接吃掉吞吐量所以它不适合做数据库或者高并发服务的盘。但它在一种服务器场景下很有价值多租户混布。比如一台机器上跑着十几套测试环境或者容器平台上一块盘被多个业务共享你没法给每个业务精确限速但又不能让某个跑批任务的业务把盘占满。这时候bfq的公平性调度能起到软隔离的作用。当然更精确的做法是用 cgroup v2 的io.max和io.weightbfq更适合作为兜底。3.5 一张表把选型说清楚调度器排序能力延迟保护CPU 开销适合设备典型场景none无仅合并无最低深队列 NVMe百万级 IOPS 随机读、纯闪存阵列mq-deadline强红黑树强过期机制中SATA SSD、NVMe、HDD数据库、消息队列、通用服务器kyber弱FIFO中令牌桶低中等队列 NVMe随机小 IO、CPU 紧张环境bfq按队列公平强预算制高SATA SSD、HDD桌面、多租户混布、共享盘看这张表的时候注意一点排序能力强不等于性能好。排序的价值在于减少物理寻道机械盘上收益巨大NVMe 上可能完全是负收益。选型的本质是判断你的设备需不需要内核帮你排队而不是比谁的算法更复杂。4. 队列深度与硬件特性参数不是拍脑袋定的4.1 从单队列到多队列架构变了策略也得变前面提过 blk-mq 的影响这里展开说。老的单队列架构下所有 CPU 抢一把queue_lock4 核以上就开始明显退化。多队列架构下每个软件队列有自己的锁硬件队列数量由设备驱动上报NVMe 通常是 CPU 核数或者更多SATA 控制器通常只有 1 到 2 个。这个变化直接影响了调度参数的含义。以nr_requests为例在单队列时代它是整个设备的队列深度默认 128在多队列时代它是每个软件队列的深度。如果你有 16 个软件队列每个 128理论上能挂 2048 个在途请求但设备硬件队列可能只有 64 个位置多出来的只能在软件层等着。所以调大nr_requests并不总是好事队列太长会推高延迟因为排在后面的请求等待时间被拉长了。我在一台 32 核机器上做过对比nr_requests从默认值调到 1024顺序写的吞吐涨了大概 8%但 4K 随机读的 P99 延迟涨了近 40%。结论是对延迟敏感的业务宁可让队列短一点也不要为了纸面吞吐把队列堆长。4.2 不同介质的合理队列深度这个值没有统一答案但有个估算思路队列深度的作用是填满设备流水线让设备在处理当前请求的同时还有后续请求可以拿。所以理想值约等于设备单次服务时间内能完成的请求数 × 期望的并发层数。机械盘单次寻道加旋转延迟平均 8ms 左右一次只能处理一个请求所以队列深度 1 到 4 就够了再深只是排队。SATA SSD 内部并发通道一般是 4 到 8 个队列深度 32 左右合理。NVMe 盘内部通道多、延迟低几十微秒队列深度 64 到 256 都能被有效利用具体看设备的手册和实测。这里给一个实测方法固定其他变量只改nr_requests从 32 到 512 扫一遍记录 IOPS 和 P99 延迟两个指标。找到 IOPS 曲线开始走平的那个点再往回调一档就是你的甜点值。曲线走平说明设备已经吃满继续加深队列只会增加排队时间。4.3 请求大小与调度策略的联动还有一层容易被忽略IO 大小会改变调度器的工作模式。4K 小请求在队列里密密麻麻排序和合并都有意义但如果是 1M、4M 的大块顺序写一个请求本身就占用了大量扇区合并的空间很小排序的收益也被摊薄了。对于大块顺序 IO我的做法是干脆让它走none或者把nr_requests调小让请求快速穿过块层直接下去减少在队列里的停留时间。有些场景下用direct1绕过 page cache、配合较大的 IO 尺寸比在调度器上折腾有效得多。另外提一个和调度器配合的参数/sys/block/dev/queue/nr_zones带区设备相关和read_ahead_kb。预读值调大对顺序读有明显帮助对随机读则是纯粹的浪费预读的数据根本用不上还占带宽。机械盘上read_ahead_kb设 128 到 256 合理NVMe 上可以设到 512 甚至更高但前提是你的负载确实有顺序读。5. 实操切换调度器与参数调节的完整流程5.1 先看清现状别急着动手第一步永远是看。查看某块盘当前可用的调度器和当前生效值cat /sys/block/sda/queue/scheduler # 输出形如[mq-deadline] kyber bfq none # 方括号里的就是当前生效的如果是 NVMe 设备路径是/sys/block/nvme0n1/queue/scheduler。多块盘就写个循环一次看完for d in /sys/block/sd* /sys/block/nvme*n1; do [ -f $d/queue/scheduler ] echo $d: $(cat $d/queue/scheduler) done同时要确认设备类型和队列深度这两个信息决定了后面怎么选# 看是不是旋转设备1 表示机械盘0 表示闪存 cat /sys/block/sda/queue/rotational # 看调度队列数量 ls /sys/block/nvme0n1/mq/ | wc -l # 看当前队列深度 cat /sys/block/sda/queue/nr_requests注意容器里可能看不到这些路径需要在宿主机上操作有些云厂商的虚拟盘会把scheduler文件隐藏或者只暴露一个选项遇到这种情况说明底层存储不归你管调不了别浪费时间。5.2 临时切换与永久生效临时切换最简单的做法就是往文件里写echo mq-deadline /sys/block/sda/queue/scheduler这条命令立即生效但重启就丢。要在压测中快速对比多个调度器用这种方式最方便。写完之后再看一眼文件确认方括号位置变了。永久生效有三条路我按推荐度排序。第一条是 udev 规则只作用于指定类型的设备粒度最细cat /etc/udev/rules.d/60-iosched.rules EOF # 机械盘用 mq-deadline ACTIONadd|change, KERNELsd[a-z], ATTR{queue/rotational}1, ATTR{queue/scheduler}mq-deadline # SSD 用 mq-deadline ACTIONadd|change, KERNELsd[a-z], ATTR{queue/rotational}0, ATTR{queue/scheduler}mq-deadline # NVMe 用 none ACTIONadd|change, KERNELnvme[0-9]n[0-9], ATTR{queue/scheduler}none EOF写完执行udevadm control --reload-rules udevadm trigger让它立刻生效不用重启。这种方式的优点是规则清晰、可版本化管理放进配置仓库里跟着机器一起下发。第二条是内核启动参数elevatormq-deadline作用于所有设备作为全局默认。缺点是粒度粗没法区分机械盘和闪存盘。如果机器上盘的种类单一这条最省事。第三条是改 initramfs 或者 systemd 的 tmpfiles不推荐维护成本高还容易在多路径设备上出问题。5.3 关键参数怎么调mq-deadline的参数在/sys/block/dev/queue/iosched/下ls /sys/block/sda/queue/iosched/ # fifo_batch front_merges read_expire write_expire writes_starved几个值的含义和调整思路read_expire默认 500ms。如果业务对读延迟极度敏感比如在线交易的元数据查询可以降到 100 到 200ms让读请求更快插队。降太多的副作用是写请求被频繁打断顺序性变差写吞吐下降。write_expire默认 5000ms。日志类业务可以适当加大到 10000ms让写请求攒得更整齐提升顺序性但如果你的写是同步 fsync 模式加大这个值反而会让 fsync 的等待时间变长得往小调。fifo_batch默认 16。调大意味着一次连续处理的请求更多、排序效果更好、吞吐更高但过期检查的间隔也变长延迟抖动增加。高吞吐场景可以试 32 到 64低延迟场景保持 16 甚至降到 8。writes_starved默认 2。调大是进一步向读倾斜调小则更平衡。混合负载下保持默认通常最稳。front_merges默认 1开启。如果发现前向合并命中率很低随机 IO 场景关掉能省一点开销但收益微乎其微一般不用动。kyber的参数更简单cat /sys/block/nvme0n1/queue/iosched/read_lat_nsec cat /sys/block/nvme0n1/queue/iosched/write_lat_nsec这两个值代表你期望的目标延迟设成你业务能接受的 P99 值即可比如读设 10000001ms。bfq参数稍多实际用得上的就两个low_latency默认 1开启低延迟模式交互场景保持开启和slice_idle空闲时的等待时间桌面场景设 0 可以提升吞吐但牺牲一点交互性。5.4 和 cgroup 配合做真正的细粒度控制调度器管的是全局队列如果你想按进程或者按容器分配 IO 带宽得上 cgroup v2 的 io 控制器。这两者是互补的调度器决定队列里的请求怎么排cgroup 决定谁有资格往队列里放请求。# 查看某块盘的设备号 lsblk -o NAME,MAJ:MIN # 给某个 cgroup 设置写带宽上限格式 major:minor rbps... wbps... echo 8:0 rbps104857600 wbps52428800 /sys/fs/cgroup/mygroup/io.max # 按权重分配默认 100 echo 8:0 200 /sys/fs/cgroup/mygroup/io.weightio.max是硬上限超过就被限流io.weight是相对权重空闲时可以用满竞争时按比例分。我一般对跑批类的业务用io.max卡死上限对在线业务用io.weight给更高的权重这样既保证跑批能跑完又不影响在线业务的延迟。再配合ionice在进程级别微调# 把备份进程设为 idle 级别只在磁盘空闲时才处理 ionice -c 3 -p $(pgrep -f backup_script) # 给关键进程最高优先级 ionice -c 1 -n 0 -p $(pgrep -f critical_service)ionice的-c 1是实时级别用的时候要谨慎搞不好会把其他进程彻底饿死我一般只给极少数确认无害的进程用。6. 用压测把结论坐实fio 方案与指标判读6.1 一套可以直接跑的对照测试光调参数不看数据等于瞎调。我常用的对照方法是同一个 fio 脚本跑三遍每遍换一个调度器其他条件全部固定。#!/bin/bash # 用法./bench.sh device scheduler DEV$1 SCHED$2 echo $SCHED /sys/block/$DEV/queue/scheduler echo current scheduler: $(cat /sys/block/$DEV/queue/scheduler) fio --namerandread \ --filename/dev/$DEV \ --ioenginelibaio \ --direct1 \ --rwrandread \ --bs4k \ --iodepth32 \ --numjobs4 \ --runtime60 \ --ramp_time10 \ --time_based \ --group_reporting fio --namerandwrite \ --filename/dev/$DEV \ --ioenginelibaio \ --direct1 \ --rwrandwrite \ --bs4k \ --iodepth32 \ --numjobs4 \ --runtime60 \ --ramp_time10 \ --time_based \ --group_reporting fio --nameseqwrite \ --filename/dev/$DEV \ --ioenginelibaio \ --direct1 \ --rwwrite \ --bs1m \ --iodepth8 \ --numjobs1 \ --runtime60 \ --ramp_time10 \ --time_based \ --group_reporting几个参数的选择理由得说清楚不然抄了也不知道为什么。--direct1是必须的绕过 page cache 才能测到真实的块层和设备性能否则你测的是内存。--ramp_time10让前面的预热数据不进入统计避免缓存效应干扰。--iodepth32配合--numjobs4等效并发 128能压出设备的真实能力如果你的实际负载并发很低这个值应该跟着降不然测出来的结论和线上对不上。注意--filename/dev/sdX会直接写裸设备必须先确认这块盘上没有数据和文件系统。生产机上误操作一次代价是整盘数据。测试一定要在隔离环境或者用独立的测试分区做。6.2 指标怎么读哪些数字才算数fio 输出里我主要看四个数iops、bw、clat的avg和p99。前两个看吞吐能力后两个看延迟分布。很多人只看iops忽略了p99结果选了个峰值高但抖动大的配置上线后被长尾拖死。判读的原则是看趋势不看单点。比如mq-deadline相比none在 4K 随机读上 IOPS 低 8%但p99低 15%这个交换通常是值得的。反过来如果 IOPS 低 20% 而p99没改善那就没必要换。另外一定要同时看系统侧指标fio 的数字是应用视角的iostat -x 1是设备视角的两者对不上说明中间有问题iostat -x 1 30重点看r_await和w_await的差值。如果w_await远高于r_await比如 3 倍以上说明写请求被大量排队这时候要么调小write_expire要么检查是不是回写策略的问题vm.dirty_ratio太大会导致集中回写。如果aqu-sz长期大于设备队列深度的一半说明队列一直处于饱和状态加深队列没用得从上游减少请求或者换更快的盘。还有一个容易被忽略的指标是%util。在闪存设备上%util达到 100% 并不意味着设备饱和因为多队列设备可以并行处理这个指标在 NVMe 上的参考价值很低。判断饱和应该看aqu-sz和await的趋势而不是%util。7. 踩过的坑与排查清单7.1 问题速查表现象可能原因排查动作处理方式调度器切换后无变化设备被上层虚拟化接管文件只读或未生效cat文件确认方括号位置检查是否在容器内到宿主机操作确认云盘是否支持切换后性能反而下降设备类型与调度器不匹配查rotational和队列深度机械盘回mq-deadlineNVMe 试none延迟抖动大但平均延迟正常fifo_batch过大或队列过深看iostat的aqu-sz波动降低fifo_batch减小nr_requests写延迟远高于读延迟写饥饿或回写集中触发对比r_await与w_await查dirty_ratio调小write_expire调整回写参数重启后配置丢失只做了临时切换检查 udev 规则是否加载用udevadm trigger重载并验证多块盘策略不一致udev 规则匹配范围过窄逐盘cat确认用通配符覆盖或写循环脚本统一设置容器内 IO 优先级不生效cgroup 层级或控制器未启用检查io.max文件是否存在确认 cgroup v2 已挂载且 io 控制器已开启7.2 几个文档里不写的实操心得第一改之前先记基线。我有个习惯动任何参数前先跑一遍iostat -x 1 60存下来改完再跑一遍对比。没有基线你根本不知道改的是好了还是坏了只能凭感觉。有几次我以为自己优化了结果对比基线发现 P99 反而涨了及时回滚。第二一次只改一个变量。有人喜欢一次把调度器、队列深度、预读值全调了然后测出来效果好就上线。这样做的风险是你永远不知道是哪个改动起了作用出问题时也没法精确定位。我的做法是串行验证每个改动单独跑一轮 60 秒的压测记录数据最后再组合验证一次。第三别迷信最新和最强。bfq算法最复杂理论上最公平但在高 IOPS 服务器盘上就是不如mq-deadline甚至none。选型要看场景不是看算法的先进程度。我见过有人在数据库服务器上把调度器设成bfq想优化公平性结果 QPS 掉了两成多。第四虚拟机和云盘的调度器基本调不动。在虚拟化环境下你看到的块设备是宿主机给你的一个抽象层调度器要么不存在要么参数被屏蔽。这种情况下优化方向应该转向应用层减少 IO 次数、合并小写、用 direct IO 减少缓存拷贝或者存储层的规格选择而不是在块层折腾。第五参数文件写入失败不一定报错。有些内核版本对某些参数是静默忽略的echo返回成功但实际没生效。所以每次改完都要cat回来确认这是最基本的验证动作别省。7.3 一个完整的排查路径示例最后给一套我在遇到IO 延迟异常时的固定排查顺序照着走能覆盖八成情况。先看整体top里的%wa和iostat -x 1的await、aqu-sz判断是设备饱和还是排队问题。再分层定位用pidstat -d 1看是哪个进程在大量读写用iotop确认读写方向和块大小。然后看配置确认当前调度器、队列深度、是否有多路径导致请求分散。再看系统参数vm.dirty_ratio、vm.dirty_background_ratio是否过大导致集中回写。最后才考虑换调度器和调参数。这个顺序的逻辑是从宏观到微观、从配置到参数因为绝大多数 IO 问题不是调度器引起的但很多人一上来就调调度器调完发现没用反而把原来的问题掩盖了。我自己的经验是真正需要动调度器的场景大概只占三成剩下的七成在应用层的 IO 模型、文件系统选择或者回写策略上。顺带说一句题外话如果你打算在容器密集的机器上做 IO 隔离先把 cgroup v2 的 io 控制器跑通这比调调度器的收益大得多而且能精确到单个容器。调度器是全局的旋钮cgroup 是每个租户的阀门两者配合才能既保证整体吞吐又保证个体公平。

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

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

免费获取报价 →
↑