🐧 第八章 外部存储器
📄 一句话概括

1000 个随机请求,先来先服务要跑 6762072 磁道,排个序(电梯算法)只要 19951 磁道——降到 0.3%,省了 339 倍。代价是响应时间不再公平:最后一个请求可能要多等很久。

📝 本节和前面几节的关系

8.7 说块层能把相邻请求合并,但随机请求没法合并。这一节讲块层的另一半工作:把【没法合并的请求】排个队。

一、先排队再干活

磁盘只有一个磁头,同一时刻只能服务一个请求。那么多请求挤在一起,谁先谁后,结果完全不同。 先来先服务(FIFO):按到达顺序做。公平,但磁头会来回乱跑。 电梯算法(SCAN):磁头朝一个方向走,沿路的请求全做掉,到边了再折回来。像电梯一样,所以叫这个名字。 Demo 85 在 20000 条磁道上撒 1000 个随机请求。FIFO 要走 6762072 磁道,SCAN 只要 19951 磁道。SCAN 差不多就是把整片区域扫了一遍,这是理论下界。 所以排序的收益是【把乱走变成扫一遍】。只要请求够多、够分散,这个收益就接近【请求数】这个量级。

二、代价是不公平

排序把【总时间】压到最低,但把【单个请求的等待】拉长了。 一个刚到磁头附近就被服务的请求,可能几十微秒就完成了。一个刚好在磁头背后的请求,可能要多等一整趟往返。 极端情况还有【饿死】:如果一直有新请求落在磁头前方,后面那个永远轮不上。 所以真实调度器都要加限制:给每个请求设一个截止时间,快到期了就插队。这就是 mq-deadline 里的 deadline。 这是一条通用规律:任何【为了总吞吐而重排】的策略,都会牺牲【最坏情况的延迟】。吞吐量与延迟的矛盾,在第 4 章(4.10)讲过一遍。

三、四种调度器

none(也叫 noop):不排序,只做合并。适合 SSD 与虚拟化环境——因为 SSD 没有磁头,排序不省时间。 mq-deadline:排序,但给每个请求设截止时间。通用选择,读优先于写(因为读是同步等待的,写可以延迟)。 bfq:按进程分权重,保证公平。适合桌面与交互场景——让你正在打开的那个窗口能抢到 IO。 kyber:按延迟目标控制队列深度。适合高速 SSD,靠【少排队】而不是【排好序】来控延迟。 怎么选:机械盘用 mq-deadline,SSD 用 none,桌面用 bfq。现代发行版基本都自动判断了。

四、为什么现在叫【多队列】

老的内核只有一个请求队列,所有 CPU 的 IO 都往一个队列里塞。在几十万 IOPS 的 SSD 上,这个队列本身成了瓶颈——不是因为排得慢,是因为【抢锁】。 现在改成【每个 CPU 一个队列】:各自排序、各自合并,最后再汇到设备。这就是 mq 的来历(multi-queue)。 这个改动的动机很值得注意:它不是【为了更快地排队】,而是【为了不排队】。当设备快到一定程度,软件里的共享结构就成了瓶颈。第 4 章讲调度器时也是同一件事:锁的竞争会吃掉多核的全部收益。

五、动手 Demo:I/O 调度器——先排队再干活能省多少

在 20000 条磁道上随机撒 1000 个请求,分别按【到达顺序】和【排序后】服务一次,算磁头一共要走多少磁道。

import random

random.seed(3)
N, TRACKS = 1000, 20000
reqs = [random.randrange(TRACKS) for _ in range(N)]

def dist(seq):
    cur, tot = 0, 0
    for x in seq:
        tot += abs(x - cur)
        cur = x
    return tot

fifo = dist(reqs)
scan = dist(sorted(reqs))
print("FIFO 总寻道 %8d 磁道  平均 %.0f 磁道" % (fifo, fifo / N))
print("SCAN 总寻道 %8d 磁道  平均 %.0f 磁道" % (scan, scan / N))
print("降到 %.1f%%  省了 %.0f 倍" % (scan * 100.0 / fifo, fifo * 1.0 / scan))

实跑输出:

--------------------------------------------------------------------
I/O 调度器——先排队再干活能省多少
--------------------------------------------------------------------
① 1000 个随机请求,两种服务顺序

  **总寻道距离 = 磁头走过的磁道数之和。**
  **它直接决定总耗时**(一次寻道 8.5 毫秒覆盖 20000 磁道的话,每个磁道约 0.0004 毫秒)。

  策略                   总寻道距离       平均每次      降到
  --------------------------------------------------------------
  先来先服务(FIFO)     6762072 磁道     6762 磁道     100%
  电梯算法(SCAN)       19951 磁道       20 磁道       0.3%

  **排一下序,总寻道距离降到 0.3%——省了 339 倍。**
  **代价是【响应时间不再公平】**:**最后那个请求可能要多等很久,因为磁头要先把沿路的都服务完。**

② 于是有了几种调度器

  调度器          做法                             适合
  ----------------------------------------------------------------
  none / noop     不排序,只做合并                 SSD、虚拟化
  mq-deadline     排序,但给每个请求设截止时间     通用
  bfq             按进程分权重,保证公平           桌面、交互
  kyber           按延迟目标控制队列深度           高速 SSD

  **为什么 SSD 上要用 none**:
  **SSD 没有磁头,【排序】不省任何时间**,**反而让请求多等一会儿。**
  **所以【原来为了机械盘设计的优化,在 SSD 上变成了负担】。**

③ 这一层为什么现在叫【多队列】

  **老的内核只有一个请求队列,所有 CPU 的 IO 都往一个队列里塞**,
  **在几十万 IOPS 的 SSD 上,这个队列本身成了瓶颈。**
  **现在改成【每个 CPU 一个队列】**,**各自排序、各自合并,最后再汇到设备。**
  **这也是 `mq-deadline` 里那个 `mq` 的来历**(multi-queue)。

  📋 **Linux 上看当前调度器**:`cat /sys/block/sda/queue/scheduler`,
  **方括号里的那个是当前生效的**;
  **改**:`echo mq-deadline | sudo tee /sys/block/sda/queue/scheduler`。

这个 Demo 是纯模型:只算磁头移动距离,没有真的发 IO。但结论是可信的——寻道时间与移动距离基本成正比(8.1)。 SCAN 的 19951 磁道 ≈ 扫一遍整片区域,这是理论下界:1000 个请求分散在 20000 磁道上,至少要把整片走一遍。 FIFO 的 676 万磁道意味着平均每次跳 6762 磁道,相当于每次都横穿三分之一片盘。 排序几乎不花成本(就是一次 sorted),收益是 339 倍——这是性价比极高的一次优化。它唯一的代价是【公平性】。

📋 Linux 上看当前调度器:cat /sys/block/sda/queue/scheduler,方括号里的那个是当前生效的; 📋 改:echo mq-deadline | sudo tee /sys/block/sda/queue/scheduler。

30 秒实验

  1. 把 TRACKS 改成 2000,看 SCAN 的收益会不会变小。
  2. 把 N 改成 100,看倍数怎么变。
  3. cat /sys/block/sda/queue/nr_requests,这是队列深度。

常见误区

  1. 【排序总是好的】。排序优化总吞吐,牺牲最坏延迟,还会让远处的请求饿死。所以真实调度器都要加截止时间。
  2. 【SSD 上也该排序,反正不亏】。SSD 上排序不省任何时间,还让请求多等,所以默认用 none。
  3. 【有了调度器,随机写就不慢了】。排序只减少寻道距离,不能把随机变成顺序,也改变不了【每个请求单独发】这个事实。
自测三问(点开对答案) 1. 为什么排序能把总寻道距离降两个数量级? —— **因为排序把【来回乱跳】变成了【扫一遍】**。**1000 个请求散在 20000 磁道上,乱跳要跳 676 万磁道,扫一遍只要 2 万磁道**——**这就是理论下界。** 2. 排序的代价是什么? —— **响应时间不再公平**:**刚好在磁头背后的请求要多等一整趟,极端情况下会被后来的请求饿死。****所以真实调度器给每个请求设截止时间。** 3. 为什么 SSD 上要用 `none` 调度器? —— **因为 SSD 没有磁头,排序不省任何时间**,**却让每个请求在队列里多等一会儿。****上一代为了机械盘做的优化,在新介质上成了纯开销。**

相关笔记