【Linux从入门到精通】第43篇:I/O调度算法与磁盘性能优化

目录

一、引言:当多个进程同时读写磁盘

[二、Linux I/O栈:请求如何到达磁盘](#二、Linux I/O栈:请求如何到达磁盘)

三、三种经典调度器

[3.1 CFQ:人人有份的公平模式](#3.1 CFQ:人人有份的公平模式)

[3.2 Deadline:拒绝长队等待](#3.2 Deadline:拒绝长队等待)

[3.3 NOOP:简单到极致的"无序"调度](#3.3 NOOP:简单到极致的“无序”调度)

[3.4 三种调度器对比](#3.4 三种调度器对比)

[3.5 查看和修改调度器](#3.5 查看和修改调度器)

[3.6 一个具体的场景分析](#3.6 一个具体的场景分析)

四、ionice:精细控制I/O优先级

[4.1 I/O优先级体系](#4.1 I/O优先级体系)

[4.2 基本用法](#4.2 基本用法)

[4.3 实战:不打扰用户的备份脚本](#4.3 实战:不打扰用户的备份脚本)

[4.4 ionice的选择策略](#4.4 ionice的选择策略)

五、综合实战:磁盘性能优化决策流程

六、本篇小结

动手练习

七、下篇预告


一、引言:当多个进程同时读写磁盘

想象一个场景:你正在用浏览器加载网页(需要读取缓存文件),同时后台的备份脚本在疯狂拷贝大量文件到备份目录。这两个操作的I/O请求会同时到达磁盘。

如果没有调度,它们会按到达顺序排队。但浏览器的操作是交互式 的------你在等页面加载,每多等100毫秒都会觉得卡;而备份脚本是批处理任务,晚几秒完成无伤大雅。

I/O调度器就是要在这种资源竞争中找到平衡:让交互式操作快速响应,让批处理任务高效完成,换谁也不至于干等。

二、Linux I/O栈:请求如何到达磁盘

先从整体看清楚一个读/写请求的完整路径:

text

复制代码
应用程序(write/read系统调用)
        ↓
VFS(虚拟文件系统层):统一接口,屏蔽不同文件系统的差异
        ↓
Page Cache(页缓存层):先写入内存缓存,由内核异步刷盘
        ↓
文件系统(ext4/xfs等):负责将数据组织为文件系统格式
        ↓
Block Layer(通用块层):组装成块I/O请求(bio),准备下发给磁盘
        ↓
┌──────────────────────────────────┐
│  I/O调度器(决定请求的排队顺序)   │  ← 本文主角
└──────────────────────────────────┘
        ↓
设备驱动(与物理磁盘通信)
        ↓
磁盘硬件(HDD/SSD)

队列在这个流程中的位置决定了它的角色:通用块层将上层请求打包好后,不是直接扔给驱动,而是先交给I/O调度器排队。调度器根据算法决定"谁先谁后",然后将排好序的请求派发给磁盘驱动。

为什么VFS和文件系统不做这个调度? 因为它们面对的是文件和目录,而I/O调度器面对的是物理扇区地址------只有到了这个层次,才知道两个请求是否访问相邻位置、是否可以合并、是否在同一个磁盘上。

三、三种经典调度器

3.1 CFQ:人人有份的公平模式

CFQ(Completely Fair Queuing,完全公平排队)曾是Linux默认的I/O调度器,它的设计思想是:为每个进程分配独立的I/O队列,按时间片轮流向每个队列派发请求

text

复制代码
进程A队列:[A1 A2 A3] ─┐
进程B队列:[B1 B2]    ─┤ → 调度器按时间片轮流分发 → 磁盘
进程C队列:[C1 C2 C3] ─┘

这种设计的意义在于确保没有单个进程可以独占磁盘。即使有一个进程在疯狂读写大文件,其他进程的I/O请求也能得到公平的响应机会,不会被"饿死"。

CFQ还内置了I/O优先级支持 ------优先级高的进程分得更大的时间片,它的请求在队列中等得更短。后面要讲的ionice就是基于这个机制。

适用场景:HDD,尤其是多用户、多任务的桌面或服务器环境。

3.2 Deadline:拒绝长队等待

CFQ太"公平"了------每个进程一个队列轮流服务------但也因此带来了问题:如果一个请求在队列尾部等待太久,它可能要等很多轮才能被派发。

Deadline调度器用一个简单规则解决了这个问题:每个请求都有"截止时间"。读请求默认500ms超时,写请求默认5000ms超时。当某个请求等待时间接近截止时间,调度器会跳过常规的排序逻辑,优先处理这个请求。

这意味着Deadline调度的核心约束是延迟上限可控------一个请求最多等多久,是可以预期而不会无限排队。

适用场景:数据库服务器(对读延迟敏感)或任何要求I/O响应时间可预测的场景。

3.3 NOOP:简单到极致的"无序"调度

NOOP(No Operation)调度器只维护一个FIFO(先进先出)队列,几乎没有调度逻辑。它做的工作就是请求合并:如果两个相邻请求访问连续的磁盘扇区,就合并成一个请求批量处理。

NOOP不对请求做任何重新排序。原因并不是它"懒",而是在某些硬件上,硬件自身已经做了排序,内核再做一遍排序反而浪费CPU。这些硬件包括:

  • SSD和NVMe:无机械寻道,访问任何位置速度相同,软件排序无意义

  • 高端存储阵列(SAN/NAS):控制器内部有自己的I/O调度

  • 虚拟化环境:宿主机Hypervisor层已经对I/O做了合并和排序

在这些场景下,NOOP作为内核层最简单的通道,把调度工作完全交给硬件,反而效率最高。

3.4 三种调度器对比

调度器 核心策略 队列设计 适用硬件 典型场景
CFQ 公平分配I/O带宽 每进程独立队列 HDD 多任务桌面、多用户服务器
Deadline 保证延迟上限 读/写两队+截止时间 HDD 数据库、延迟敏感应用
NOOP 仅合并相邻请求 单一FIFO队列 SSD/NVMe 所有SSD类场景、虚拟化

3.5 查看和修改调度器

bash

复制代码
# 查看某块磁盘当前的调度器和可用调度器
cat /sys/block/sda/queue/scheduler
# 输出:noop [deadline] cfq
# [] 括起来的是当前生效的调度器

# 临时修改调度器
echo noop | sudo tee /sys/block/sda/queue/scheduler

# 查看系统中所有磁盘的调度器
for disk in /sys/block/sd*; do
    echo "$(basename $disk): $(cat $disk/queue/scheduler)"
done

# 永久修改(添加到GRUB启动参数,以CentOS为例)
# vim /etc/default/grub
# 在 GRUB_CMDLINE_LINUX 中添加 elevator=noop
# sudo grub2-mkconfig -o /boot/grub/grub.cfg

现代Linux的变迁 :内核5.0+引入了全新的多队列(multi-queue)块层设计,对应的调度器也升级为mq-deadlinebfq(Budget Fair Queuing,CFQ的理念继承者)和kyber(面向低延迟的简单调度器)。但无论调度器怎么升级,核心设计思想------让不同的硬件发挥各自的优势------从未改变。

3.6 一个具体的场景分析

场景 :你有一台HDD服务器,上面既跑着Nginx(需要快速读取静态文件响应Web访问),又有凌晨的rsync备份脚本(连续大量读盘)。用户反馈网站时不时卡一下,iostat显示await很高。

原因:rsync的大批量读请求抢占了CFQ调度器的大量时间片,导致Nginx的I/O请求在队列中排队等待,时延变大。

解决方向

  • 选项A:让rsync从Nginx不在用的时候读取(cron调时间,最简单)

  • 选项B:降低rsync的I/O优先级,让它把调度器时间片让给Nginx(此时CFQ的进程级队列起作用)

  • 选项C:换成Deadline调度器,利用读优先和截止时间机制,确保交互式读请求不被无限排队

选项B正是接下来要讲的ionice

四、ionice:精细控制I/O优先级

4.1 I/O优先级体系

ionice控制I/O调度器分配给进程的优先级,它依赖CFQ调度器才能生效(NOOP和Deadline没有每进程队列的概念)。

三个调度类

调度类 数字 含义 使用场景
realtime 1 实时优先级,其他进程必须等待它完成 通常不推荐用
best-effort 2 尽力而为(默认),可在0-7范围内调节 最常用
idle 3 空闲类,仅在磁盘完全空闲时才执行I/O 后台批处理任务

best-effort类的优先级值(0=最高优先级,7=最低优先级):

text

复制代码
优先级 0 ───────────── 7(默认 4)
  高优先级              低优先级
(抢得快)            (让着别人)

4.2 基本用法

bash

复制代码
# 查看进程的I/O优先级
ionice -p PID

# 以最低优先级执行备份脚本(不干扰核心服务)
ionice -c 2 -n 7 rsync -av /data/ /backup/

# 以idle类执行日志归档(只有磁盘完全空闲时才干活)
ionice -c 3 tar -czf /archive/logs_$(date +%Y%m%d).tar.gz /var/log/

# 调整已有进程的优先级
ionice -c 2 -n 7 -p $(pgrep -f "rsync")

4.3 实战:不打扰用户的备份脚本

bash

复制代码
#!/bin/bash
# backup.sh - 以最低I/O优先级执行备份,不干扰在线服务

LOG="/var/log/backup_$(date +%Y%m%d).log"

echo "[$(date)] 备份开始(I/O优先级:最低)" >> "$LOG"

# nice降低CPU优先级(第12篇讲过),+19是最低
# ionice降低I/O优先级,-n 7是最低
nice -n 19 ionice -c 2 -n 7 rsync -av --delete /data/ /backup/data/ >> "$LOG" 2>&1

echo "[$(date)] 备份完成" >> "$LOG"

这个脚本同时使用了nice(降低CPU优先级)和ionice(降低I/O优先级),确保备份操作不会影响白天用户的正常访问。

4.4 ionice的选择策略

任务类型 调度类 优先级
交互式应用(Nginx、MySQL) best-effort 0-3
普通脚本 best-effort 4(默认)
定时备份 best-effort 6-7
日志归档 idle -

五、综合实战:磁盘性能优化决策流程

当遇到磁盘I/O性能问题时,按以下步骤分析:

第一步:确认瓶颈在I/O

bash

复制代码
vmstat 2
# wa列 > 10% = I/O瓶颈
# b列 > 0 = 进程在等待I/O

第二步:按硬件类型选择调度器

bash

复制代码
# 查看当前磁盘类型
cat /sys/block/sda/queue/rotational
# 0 = SSD, 1 = HDD

# HDD → deadline 或 bfq
# SSD → noop 或 none

# 查看当前调度器
cat /sys/block/sda/queue/scheduler

第三步:降低非关键进程的I/O优先级

bash

复制代码
# 识别高I/O进程
iotop -o   # 按I/O使用率排序

# 对非关键进程降低优先级
ionice -c 2 -n 7 -p PID

第四步:在应用层面优化

bash

复制代码
# 检查是否有大文件被反复重写
lsof +D /data/ | grep -E "^.*W"

# 适当调大Page Cache容量(让更多数据在内存中完成操作)
# 使用 tmpfs 存放临时文件
sudo mount -t tmpfs -o size=2G tmpfs /tmp

关于lsof +D+D表示递归搜索目录,输出访问该目录的所有进程和打开的文件描述符。加上grep W过滤出以"写"模式打开的文件。这个命令可以帮助你找到"谁在不停地往磁盘写数据",但+D递归搜索本身也会消耗I/O,慎在高峰期使用。

六、本篇小结

I/O栈路径 :应用 → VFS → 文件系统 → 通用块层 → I/O调度器 → 驱动 → 磁盘

三种调度器

调度器 核心思想 一句话
CFQ/bfq 公平分配 "每个人都得轮到"
Deadline/mq-deadline 截止时间 "读请求不能再等了"
NOOP/none 无调度 "硬件自己看着办"

ionice核心命令

场景 命令
最低优先级备份 ionice -c 2 -n 7 rsync ...
空闲才干活 ionice -c 3 tar ...
查看已有进程优先级 ionice -p PID

动手练习

bash

复制代码
# 1. 查看所有磁盘的调度器
for disk in /sys/block/sd* /sys/block/nvme*; do
    [ -d "$disk" ] && echo "$(basename $disk): $(cat $disk/queue/scheduler)"
done

# 2. 判断你的磁盘是HDD还是SSD
cat /sys/block/sda/queue/rotational
# 0 = SSD, 1 = HDD

# 3. 创建两个后台测试任务——一个高优先级I/O,一个低优先级
dd if=/dev/zero of=/tmp/high_io bs=1M count=500 &
ionice -c 2 -n 7 dd if=/dev/zero of=/tmp/low_io bs=1M count=500 &
# 用iostat观察磁盘活动和吞吐量
iostat -x 1 5

# 4. 安装iotop查看各进程的I/O使用情况
sudo apt install iotop -y
sudo iotop -o   # 只显示有I/O活动的进程

# 5. 清理测试文件
rm /tmp/high_io /tmp/low_io

七、下篇预告

I/O调度器解决了磁盘读写请求的排队问题,但数据最终要通过网络传输给用户。当并发连接数飙升时,你可能遇到过"TIME_WAIT状态满屏"的困扰。

下一篇我们将深入Linux网络协议栈与TCP参数调优 ,学习TCP三次握手/四次挥手的内核实现,理解TIME_WAIT的由来,以及如何通过调整net.ipv4.tcp_tw_reuse等内核参数来优化高并发场景下的网络性能。


延伸思考 :SSD上没有机械寻道,为什么很多发行版在SSD上仍然默认使用mq-deadline而非none?因为多队列框架下,mq-deadline在实现请求合并写回优化方面仍然比完全的"无调度"有优势------将相邻扇区的写请求合并成批量操作、通过控制写积压来避免延迟飙升,即使在随机访问同样块的所有位置的SSD上,这些优化依然有意义。

相关推荐
计算机安禾7 小时前
【Linux从入门到精通】第44篇:Linux网络协议栈与TCP参数调优
linux·网络协议·tcp/ip
rleS IONS7 小时前
Linux系统离线部署MySQL详细教程(带每步骤图文教程)
linux·mysql·adb
学不会pwn不改名7 小时前
【ArchLinux】如何制服国产免驱网卡
linux·运维·网络
这张生成的图像能检测吗7 小时前
(论文速读)FreDN:基于可学习频率分解的时间序列预测的频谱解纠缠
人工智能·深度学习·算法·机器学习·时序模型
AI木马人7 小时前
10.人工智能实战:大模型系统如何做全链路性能优化?从请求进入到 GPU 推理的端到端瓶颈分析与落地方案
人工智能·性能优化
可视化运维管理爱好者7 小时前
rg完整中文操作指南
linux·运维·服务器·ai
CN-Dust7 小时前
【C++】for循环嵌套例题专题
java·c++·算法
计算机安禾7 小时前
【Linux从入门到精通】第40篇:LAMP/LNMP环境一键部署脚本实战
android·linux·adb
承渊政道7 小时前
【动态规划算法】(子数组系列问题建模与解题思路精讲)
数据结构·c++·学习·算法·leetcode·动态规划·哈希算法