磁盘调度算法:从机械硬盘原理到Linux I/O性能优化实战
2026/7/13 2:07:09 网站建设 项目流程

那天下午,团队里一位刚接触后端开发不久的同事跑过来,指着监控面板上一段持续波高的磁盘 I/O 等待时间曲线问我:“这服务器是不是硬盘坏了?怎么查询一会儿快一会儿慢的,完全没规律。”我让他把同一时段数据库的慢查询日志拉出来对比,发现一个有趣的现象:很多查询本身并不复杂,但它们要读取的数据块在物理磁盘上离得特别远。这就像一本厚厚的书,你要查的资料偏偏分散在第 1 页、第 300 页和第 50 页,光来回翻书的时间就远超过阅读时间本身。

这个问题背后,其实就是操作系统里一个经典且至关重要的机制——磁盘调度算法。它不像内存管理或者进程调度那样常被提起,却直接决定了数据存取的效率,尤其是在高并发 I/O 的场景下。今天,我们就从一次真实的性能问题出发,把磁盘调度算法到底在解决什么问题、为什么需要它、以及不同算法各自的适用场景彻底讲清楚。

1. 先理解机械硬盘的“物理局限”为什么是性能瓶颈的关键

要弄明白磁盘调度算法为什么存在,首先得回到机械硬盘(HDD)的工作方式上。虽然现在 SSD 越来越普及,但在大量温冷数据存储、企业级归档等场景,机械盘凭借其成本优势依然占据重要地位。而机械硬盘的物理结构,决定了它有一种“与生俱来”的延迟。

1.1 机械硬盘的三大耗时操作

当应用程序发起一个读取磁盘数据的请求时,硬盘需要完成三个机械动作:

  1. 寻道时间(Seek Time):磁头移动到数据所在的磁道(柱面)上。这是最耗时的部分,磁头是机械臂,需要横向移动。
  2. 旋转延迟(Rotational Latency):盘片旋转,将目标扇区转到磁头下方。这取决于硬盘转速(比如 7200 RPM 的硬盘,旋转一圈大约 8.3ms,平均延迟约 4.15ms)。
  3. 传输时间(Transfer Time):实际读取或写入数据的时间,通常最快,与数据量大小有关。

如果把一次磁盘 I/O 请求的总时间公式简化一下,大概是:总时间 = 寻道时间 + 旋转延迟 + 传输时间

其中,寻道时间是最大的变量,也是磁盘调度算法主要优化的对象。顺序读写时,磁头几乎不需要大范围移动;而随机读写时,磁头就像唱机的指针,在不同磁道间来回跳跃,大量时间浪费在了路上。

1.2 “先来先服务”的朴素策略为什么在磁盘 I/O 上行不通

最直观的调度策略就是 FIFO(First-In-First-Out),也就是先来的 I/O 请求先被处理。这很公平,但效率可能非常低。

举个例子,假设当前磁头在 50 号磁道,接下来收到的请求序列是:100, 10, 80, 30, 120

如果按 FIFO 顺序服务: 磁头移动路径是:50 → 100 → 10 → 80 → 30 → 120 总寻道距离 = |50-100| + |100-10| + |10-80| + |80-30| + |30-120| = 50 + 90 + 70 + 50 + 90 = 350(单位:磁道)

可以想象,磁头就像没头苍蝇一样在盘片上来回“折返跑”。这种服务策略只考虑了公平性,完全没有考虑磁头的当前位置和移动效率。在高负载的磁盘 I/O 场景下,这会导致平均响应时间急剧上升,吞吐量下降。因此,操作系统需要更智能的算法来重新排列这些 I/O 请求的服務顺序,以减少不必要的磁头移动。

2. 四大经典磁盘调度算法的工作原理与适用场景

为了解决 FIFO 的效率问题,人们设计了几种经典的磁盘调度算法。它们核心思想相似:对等待队列中的 I/O 请求进行重排序,让磁头以更“经济”的路径移动。但各自的策略和侧重不同。

2.1 SSTF(最短寻道时间优先):看似高效,实则可能“饿死”远端请求

SSTF 算法非常直观:总是选择离当前磁头位置最近的请求先服务。

还是上面的例子,磁头在 50,请求序列:100, 10, 80, 30, 120

SSTF 的调度过程会是:

  1. 当前在 50,最近的是 80(距离 30)吗?不,是 30(距离 20)吗?不,最近的是 10(距离 40)吗?不对,仔细算:离 50 最近的是 80(距离 30)?还是 30(距离 20)?我们重新排序:计算所有请求与 50 的距离:100(50), 10(40), 80(30), 30(20), 120(70)。最近的是 30(距离 20)。
  2. 服务 30(移动 20)。
  3. 当前在 30,剩余请求 [100, 10, 80, 120] 与 30 的距离:100(70), 10(20), 80(50), 120(90)。最近的是 10(距离 20)。
  4. 服务 10(移动 20)。
  5. 当前在 10,剩余 [100, 80, 120] 与 10 的距离:100(90), 80(70), 120(110)。最近的是 80(距离 70)。
  6. 服务 80(移动 70)。
  7. 当前在 80,剩余 [100, 120] 与 80 的距离:100(20), 120(40)。最近的是 100(距离 20)。
  8. 服务 100(移动 20)。
  9. 最后服务 120(移动 20)。

总寻道距离 = 20 + 20 + 70 + 20 + 20 = 150。相比 FIFO 的 350,效率提升巨大!

SSTF 的优缺点非常鲜明:

  • 优点:平均寻道时间短,吞吐量相比 FIFO 有显著提升。
  • 缺点可能导致“饥饿”(Starvation)。如果不断有新的请求到达磁头当前位置附近,那么那些位于磁盘两端的请求可能永远得不到服务。在高负载下,这是一个严重问题。

2.2 SCAN(电梯算法):公平性与效率的折中

SCAN 算法模仿电梯的工作方式:磁头在一个方向上(如从低磁道号向高磁道号)移动,服务所有沿途的请求,直到到达磁盘的一端(最内圈或最外圈),然后掉头,向反方向移动,继续服务沿途请求。

假设磁头初始在 50,向磁道号增大方向移动。请求序列:100, 10, 80, 30, 120

SCAN 调度过程:

  1. 从 50 出发,向高磁道号移动。沿途服务大于等于 50 的请求:先服务 80,再服务 100,最后服务 120(到达磁盘末端)。
  2. 掉头,向低磁道号移动。沿途服务小于 120 的请求:服务 30,再服务 10(到达磁盘另一端)。

移动路径:50 → 80 → 100 → 120 → 30 → 10 总寻道距离 = 30 + 20 + 20 + 90 + 20 = 180。

SCAN 算法的特点:

  • 优点:解决了 SSTF 的饥饿问题,对所有位置的请求都相对公平,因为每个请求最多等待磁头一个完整的来回扫描。
  • 缺点对最近扫描过的区域不公平。假设磁头刚从低磁道号扫到高磁道号,一个新请求到达低磁道号,它必须等待磁头走到最高端再掉头回来,等待时间可能较长。

2.3 CISCAN(循环扫描算法):进一步优化公平性

CISCAN 是 SCAN 的改进版,旨在提供更一致的等待时间。它与 SCAN 的区别在于:当磁头到达磁盘一端时,立即返回到另一端的开头,而不是中途就开始服务请求。可以理解为磁头只在单向移动时服务请求,返程是“空跑”,快速回到起点。

同样例子,磁头在 50,向磁道号增大方向移动。请求序列:100, 10, 80, 30, 120

CISCAN 调度过程:

  1. 从 50 出发,向高磁道号移动。沿途服务大于等于 50 的请求:80, 100, 120(到达末端)。
  2. 立即跳回到磁盘开始端(比如磁道 0),但返程不服务任何请求。
  3. 从 0 开始,再次向高磁道号移动。沿途服务请求:10, 30。

移动路径:50 → 80 → 100 → 120 → 0 → 10 → 30 总寻道距离 = 30 + 20 + 20 + 120 + 10 + 20 = 220。虽然寻道距离可能变长,但公平性更好。

CISCAN 的特点:

  • 优点:等待时间分布比 SCAN 更均匀,消除了 SCAN 中对两端请求的响应时间差异过大的问题。
  • 缺点:返程空跑,可能略微增加不必要的磁头移动。

2.4 LOOK 与 C-LOOK:更实用的“智能电梯”算法

LOOK 和 C-LOOK 是 SCAN 和 CISCAN 的优化版本。它们不会机械地走到磁盘的物理端点,而是在移动方向上如果没有更远的请求,就提前掉头

  • LOOK:磁头在一个方向上移动,服务沿途请求。当该方向没有等待的请求时,立即掉头。
  • C-LOOK:磁头在一个方向上移动,服务沿途请求。当该方向没有等待的请求时,立即跳转到另一端有请求的最小磁道号开始继续单向扫描。

在我们的例子中(磁头在 50,请求:100, 10, 80, 30, 120):

  • LOOK(向增大方向):从50出发,服务80, 100, 120。此时增大方向无请求,掉头。向减小方向移动,服务30, 10。路径:50->80->100->120->30->10,距离同SCAN为180,但避免了走到物理端点。
  • C-LOOK(向增大方向):从50出发,服务80, 100, 120。增大方向无请求,直接跳到最小请求磁道10的位置,然后向增大方向服务10, 30。路径:50->80->100->120->10->30,距离=30+20+20+110+20=200。公平性类似于CISCAN,但减少了空跑距离。

LOOK/C-LOOK 是实际系统中常用的算法,因为它们避免了不必要的磁盘两端移动,在效率和公平性之间取得了很好的平衡。

3. 算法对比与选型:如何根据场景做判断

了解了原理,我们该如何选择?这取决于你的应用对延迟和公平性的要求。

算法优点缺点适用场景
FIFO简单,绝对公平寻道性能差,吞吐量低几乎不用于现代磁盘I/O调度
SSTF平均响应时间短,吞吐量高可能饥饿,响应时间变化大(方差大)负载较轻,且I/O请求分布均匀的系统
SCAN无饥饿,吞吐量较好两端请求的响应时间差异大需要兼顾吞吐量和一定公平性的通用系统
C-SCAN响应时间更均匀,公平性好返程空跑,吞吐量略低于SCAN对请求响应时间一致性要求高的场景(如多媒体流)
LOOK/C-LOOK综合性能最好,兼顾效率与公平实现稍复杂现代操作系统的默认或常用选择(如Linux的CFQ、Deadline调度器借鉴其思想)

注意:上述讨论基于机械硬盘(HDD)。对于固态硬盘(SSD),由于其没有机械部件,数据访问时间几乎与存储位置无关,因此这些基于寻道优化的磁盘调度算法对SSD性能提升微乎其微。SSD的I/O调度更关注于并发请求处理、垃圾回收、磨损均衡等。

4. 从理论到实践:在Linux中查看和设置I/O调度器

理论最终要服务于实践。在 Linux 系统中,磁盘调度算法是以 I/O Scheduler 的形式存在的。你可以方便地查看和修改它。

4.1 查看当前系统的I/O调度器

对于某个磁盘(例如 sda),可以这样查看:

# 查看 /sys/block/sda/queue/scheduler cat /sys/block/sda/queue/scheduler

输出可能类似:[mq-deadline] kyber bfq none方括号[ ]括起来的即为当前正在使用的调度器。

  • mq-deadline: 为每个请求设置截止时间,防止饥饿,适合多种负载。
  • bfq(Budget Fair Queueing): 试图为进程提供公平的带宽分配,适合桌面交互式应用。
  • kyber: 针对快速设备(如 SSD)的轻量级调度器,主要优化延迟。
  • none: 不使用任何调度算法,直接下发给设备(常用于SSD)。

4.2 如何临时更改I/O调度器

# 将 sda 的调度器改为 bfq echo 'bfq' | sudo tee /sys/block/sda/queue/scheduler

这种更改在重启后失效。

4.3 如何永久更改I/O调度器

可以通过内核命令行参数或 udev 规则来设置。例如,使用 udev 规则,创建文件/etc/udev/rules.d/60-ioscheduler.rules,加入以下内容:

# 对 SSD(以 nvme 开头和 sdX 且旋转速率=0 的磁盘)设置 none 或 kyber 调度器 ACTION=="add|change", KERNEL=="sd[a-z]*", ATTR{queue/rotational}=="0", ATTR{queue/scheduler}="kyber" # 对 HDD(旋转速率=1)设置 mq-deadline 或 bfq 调度器 ACTION=="add|change", KERNEL=="sd[a-z]*", ATTR{queue/rotational}=="1", ATTR{queue/scheduler}="mq-deadline"

然后重新加载 udev 规则:sudo udevadm control --reload-rules && sudo udevadm trigger

5. 超越单机:分布式系统中的“磁盘调度”思想

磁盘调度算法的思想——对无序请求进行重排序以优化整体效率——并不仅限于单机操作系统。在分布式系统、数据库、网络传输等领域,你都能看到类似的思想。

  • 数据库的写操作优化:许多数据库的 WAL(Write-Ahead Logging)会合并多次写操作,顺序写入日志文件,这类似于 SCAN 算法对随机写进行顺序化。
  • 分布式存储系统的数据布局:系统会有意地将可能被同时访问的数据(如一个文件的多个块)放置在相近的物理位置(同一机架、同一存储节点),这本质上是在数据存储阶段就避免了“长距离寻道”。
  • CDN 的内容分发:将热门内容缓存到离用户最近的边缘节点,减少数据传输的“寻道时间”(网络延迟)。

理解了磁盘调度算法的核心逻辑,就能以一种更抽象的视角去看待很多系统的性能优化问题:如何通过合理的排序和布局,将随机的、低效的访问模式,转变为顺序的、高效的访问模式。

回到开头那个服务器 I/O 等待高的问题。最终的解决方案并不是去调整操作系统的磁盘调度算法(它可能已经是较优的 LOOK/C-LOOK 变种),而是通过优化数据库的索引设计,避免产生大量跨磁道的随机读请求,并从应用层面对查询进行合并,让磁盘的“磁头”能够更连续地工作。这正说明了,真正的高手,不仅要理解工具(调度算法)本身,更要理解问题(I/O 模式)的根源,从而在更早的环节做出更优的设计。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询