1本章要解决的问题

如何构建一个大、快、可靠的存储系统?关键技术是什么?不同做法之间有哪些权衡?

How can we make a large, fast, and reliable storage system? What are the key techniques? What are trade-offs between different approaches?

上一章我们看到,一块磁盘又慢(随机 I/O 只有不到 1MB/s)、又有限(容量固定),还会坏(坏了数据就没了)。我们总是希望磁盘更快、更大、更可靠。1980 年代后期,伯克利的 Patterson、Katz 和当时的学生 Gibson 提出了 RAID(Redundant Array of Inexpensive Disks,廉价冗余磁盘阵列):把多块便宜的磁盘组合起来,对外假装成"一块"更好的磁盘。

本章是持久化部分承上启下的一环:它建立在第 37 章的磁盘性能模型之上(顺序带宽 S、随机带宽 R),又把"透明地在下层提供可靠性"这一思想带进后面的文件系统章节——第 42 章的日志、第 45 章的数据完整性都会再次遇到"一致更新"和"故障模型"的问题。

2核心概念速览

透明性(transparency)

对外 RAID 就是一块普通磁盘:一个线性块数组。操作系统和应用一行代码都不用改。

映射问题(mapping)

给定逻辑块号,RAID 要算出去哪块盘、哪个偏移,并发出一个或多个物理 I/O。

fail-stop 故障模型

磁盘要么完全正常,要么整块坏掉,而且坏了能立刻被发现。先不考虑静默损坏。

RAID-0 条带化

把块轮流分到各盘,性能和容量的上限,但没有任何冗余:坏一块盘就丢数据。

RAID-1 镜像

每个块存两份。可靠、随机读快,但容量减半,每次写都要写两份。

RAID-4 奇偶校验

每个条带加一个 XOR 校验块,只花一块盘的容量就能容忍一块盘故障;但小写要"读-改-写"。

小写问题(small-write problem)

RAID-4 每次小写都要读写校验盘,校验盘成为瓶颈,随机写只有 R/2。

RAID-5 旋转校验

把校验块轮流放在各盘上,消除单一校验盘瓶颈,随机写提升到 N·R/4。

3接口、内部结构与故障模型

在文件系统看来,RAID 就是一块又大又快(但愿如此)又可靠(但愿如此)的磁盘:一个线性的块数组,每块可读可写。文件系统发出逻辑 I/O 后,RAID 在内部计算该访问哪些盘,再发出一个或多个物理 I/O。例如镜像 RAID 每收到一次逻辑写,就要发出两次物理写。

主机 / 文件系统 以为自己在用一块盘 逻辑 I/O SCSI/SATA RAID(其实是一台专用计算机) 微控制器运行 RAID 固件 DRAM缓冲读写 NVRAM安全缓冲写 校验逻辑XOR 硬件 物理 I/O(一个或多个) 磁盘 0 磁盘 1 磁盘 2 磁盘 3 对外:块 0 … n−1 的线性数组
外面看是一块盘,里面是处理器 + 内存 + 多块磁盘。透明性让 RAID 可以"即插即用"

给系统加新功能时,要想想能不能透明地加上,不要求系统其他部分做任何改变。RAID 就是完美的例子:管理员把 SCSI 磁盘换成 SCSI 接口的 RAID,主机、操作系统都一点不用改。解决了部署问题,RAID 从第一天起就更容易成功。

故障模型(fault model):要讨论"可靠",先要说清楚会发生什么故障。本章采用最简单的 fail-stop 模型:磁盘只有两种状态——正常(所有块都能读写)或故障(永久丢失)。并且故障很容易被检测到:RAID 控制器能立刻发现某块盘坏了。暂时不考虑更复杂的"静默"故障,比如磁盘内容损坏、某个扇区读不出来(潜在扇区错误,latent sector error)——这些留到第 45 章。

4怎样评估一个 RAID

三个维度:

负载分两类:顺序(大块连续访问,如读 1MB,从块 x 到 x+1MB)和随机(每次一个小请求,如 4KB,地址随机,如数据库事务)。第 37 章告诉我们,一块盘在两种负载下差别巨大,所以设一块盘的顺序带宽为 S MB/s、随机带宽为 R MB/s,通常 S ≫ R。原书的例子:

参数值顺序:每次 10MB随机:每次 10KB
平均寻道7 ms7 ms7 ms
平均旋转延迟3 ms3 ms3 ms
传输速率50 MB/s10MB / 50MB/s = 200 ms10KB / 50MB/s = 0.195 ms
一次请求总时间210 ms10.195 ms
带宽S = 10MB / 210ms = 47.62 MB/sR = 10KB / 10.195ms = 0.981 MB/s

顺序访问时寻道和旋转被大块传输"摊薄",S 接近磁盘峰值;随机访问几乎全在寻道和旋转,R 不到 1MB/s,S/R 接近 50。(原书把 KB/ms 近似记作 MB/s。)后面分析各级 RAID 时,都用 N、S、R 来表示吞吐量。

5RAID-0:条带化

RAID-0 严格来说不算 RAID——它没有冗余。但作为性能和容量的上限,它值得先弄懂。最简单的条带化(striping)把块轮流(round-robin)分到各块盘上。同一行的块叫一个条带(stripe):块 0、1、2、3 就在同一个条带里。这样做是为了在访问连续的大块数据时,尽量让所有盘并行工作。

每块盘上连续放几个块再换下一块盘,这个量叫 chunk 大小。原书 Figure 38.1 是 chunk = 1 块(4KB),Figure 38.2 是 chunk = 2 块(8KB),此时一个条带是 4 × 8KB = 32KB。拖动下面的参数看布局怎么变,输入逻辑块号看它被映射到哪里:

RAID-0 条带化布局。颜色下划线区分不同 chunk 所在的"行",黄框是查询的块

给定逻辑块 A,RAID 怎么知道去哪块盘、哪个偏移?chunk = 1 时只要两个整数运算:

Disk   = A % number_of_disks
Offset = A / number_of_disks     // 整数除法,4 / 3 = 1

原书的例子:4 块盘,请求块 14 → 盘 14 % 4 = 2,偏移 14 / 4 = 3,即第三块盘(从 0 数是盘 2)的第四个块(偏移 3)。原书留了个练习:"改一改公式支持别的 chunk 大小"。答案是先算 A 在第几个 chunk,再算 chunk 内的位置:

chunk  = A / C                 // 第几个 chunk
Disk   = chunk % N
Offset = (chunk / N) * C + A % C   // 前面完整的行数 × C + chunk 内偏移

例如 N=4、C=2 时块 14:chunk = 7,盘 7 % 4 = 3,偏移 (7/4)×2 + 0 = 2——对照上面 Figure 38.2 验证一下。

chunk 大小的权衡

"最好"的 chunk 大小取决于负载,很难确定。实际阵列常用较大的 chunk(如 64KB),但本章分析里 chunk 大小不影响结论,所以下面都假设 chunk = 1 块。

RAID-0 分析

6RAID-1:镜像

镜像(mirroring):每个块存不止一份,每份放在不同的盘上,于是能容忍磁盘故障。典型做法是每个逻辑块两份物理副本(原书 Figure 38.3):盘 0 和盘 1 内容完全相同,盘 2 和盘 3 相同,数据在这两对镜像之间再条带化。

RAID-10(1+0,镜像的条带) RAID-01(0+1,条带的镜像) RAID-0 条带:块 0,1,2,3… 镜像对 A 镜像对 B 盘00 2 4 盘10 2 4 盘21 3 5 盘31 3 5 RAID-1 镜像 条带阵列 X 条带阵列 Y(副本) 盘00 2 4 盘11 3 5 盘20 2 4 盘31 3 5
原书 Figure 38.3 的布局是 RAID-10:先组成镜像对,再在镜像对上条带化;RAID-01 则是两个大条带阵列互为镜像

读时 RAID 有选择:可以读任意一份副本。例如读逻辑块 5,可以去盘 2 也可以去盘 3。写时没有选择:两份都必须更新才能保持可靠性——不过这两次写可以并行,比如写块 5 可以同时发给盘 2 和盘 3。

任何一次逻辑操作要改多块盘的 RAID 都会遇到一致更新问题(consistent-update problem)。以镜像为例:RAID 决定把某块写到盘 0 和盘 1,先给盘 0 发了写请求,还没来得及给盘 1 发,断电了。结果盘 0 是新版本、盘 1 是旧版本——两份副本不一致。我们希望两块盘的状态原子地改变:要么都是新的,要么都是旧的。

通用解法是某种预写日志(write-ahead log):先记下"我要用这些数据更新这两块盘",再去做。崩溃后运行恢复程序重放所有未完成的事务,就能保证镜像不会不同步。每次写都往磁盘上记日志代价太高,所以大多数 RAID 硬件带一小块非易失内存(NVRAM,如电池供电的 RAM)来记日志,这样既一致又不贵。第 42 章的文件系统日志会把这个思想发扬光大。

RAID-1 分析

顺序读块 0–7:0→盘0,1→盘2,2→盘1,3→盘3,4→盘0,5→盘2,6→盘1,7→盘3 盘 0 的磁道: 0 读 2 转过 4 读 6 转过 盘 1 的磁道: 0 转过 2 读 4 转过 6 读 盘 0 和盘 1 内容相同(0 2 4 6)。 每块盘只读"每隔一块",但磁头 仍要转过被跳过的块——转过的时间 不产生有用带宽。 ⇒ 每块盘只交付一半峰值带宽,顺序读合计 (N/2)·S,而不是 N·S。 (原书脚注:更聪明的做法是向两个镜像分别发大块、不同区域的请求,可能拿到全带宽。)
镜像顺序读的"半带宽"陷阱

7RAID-4:用奇偶校验省空间

镜像太费空间。基于奇偶校验(parity)的方法用更少的容量换取冗余,代价是性能。原书 Figure 38.4 是一个 5 盘 RAID-4:每个条带加一个校验块,比如 P1 由块 4、5、6、7 算出。

用什么函数算校验?需要它能在条带丢失任意一块时把那块算回来。XOR(异或)正好可以:一组比特中 1 的个数为偶数时 XOR 为 0,为奇数时为 1。

C0C1C2C3P
0011XOR(0,0,1,1) = 0
0100XOR(0,1,0,0) = 1

好记的说法:每一行(包括校验位)里 1 的个数必须是偶数——这是 RAID 为保证校验正确必须维护的不变量。恢复也就很自然:假设 C2 那一列丢了,第一行读出其余的 0(C0)、0(C1)、1(C3)、0(P),1 的个数是奇数,所以丢失的必定是 1。重建值的算法和算校验一模一样:把剩下的所有块(含校验)XOR 起来。

块是 4KB 不是 1 比特怎么办?逐比特做 XOR,结果放进校验块对应的比特位。原书用 2 比特的"块"举例:Block0..3 = 00 10 11 10 → Parity = 11;10 01 00 01 → 10。在下面自己试:点比特翻转,选一块"丢失",看 XOR 怎样把它算回来。

XOR 校验:逐比特计算;丢失任意一块,都能用其余块 XOR 重建

RAID-4 分析

小写:加法校验 vs 减法校验

假设要覆盖写块 1。直接写的话 P0 就不对了,必须同时更新 P0。两种方法:

  1. 加法校验(additive parity):并行读入条带里其他所有数据块(块 0、2、3),和新的块 1 XOR,得到新校验;再并行写新数据和新校验。问题是读的次数随盘数增长,大阵列要读很多块。
  2. 减法校验(subtractive parity):只读旧数据 Cold 和旧校验 Pold。新旧数据相同则校验不变;不同则把旧校验对应位翻转。用 XOR 一句话写完:
P_new = (C_old ⊕ C_new) ⊕ P_old          (38.1)

以一个比特为例:原来 C2 = 1、P = 0,要写 C2new = 0:(1 ⊕ 0) ⊕ 0 = 1,校验翻转;若写 C2new = 1:(1 ⊕ 1) ⊕ 0 = 0,不变。对块来说就是 4096 × 8 个比特一起算,一般新旧块总会有不同,所以新校验块也会变。

原书的思考题:盘数多少时加法比减法 I/O 更少?拖动下面的参数自己算:

加法:读其余 N−1−k 块 + 写 k 块 + 写校验 = N 次;减法:读 k 块旧数据与旧校验 + 写回 = 2(k+1) 次

下面的分析都假设用减法:每次小写 4 次物理 I/O(2 读 2 写)。现在有大量写同时到来,RAID-4 能并行几个?看原书 Figure 38.6:同时写块 4 和块 13(标 *),它们分别在盘 0 和盘 1,数据的读写可以并行;但两者都要读写各自的校验块 P1 和 P3(标 +),而它们都在盘 4 上。

盘 0盘 1盘 2盘 3盘 4
00123P0
1*4567+P1
2891011P2
312*131415+P3

原书 Figure 38.6:写 4 和 13 时,数据盘 0、1 可并行,但两个请求都要读写盘 4

这就是基于校验的 RAID 的小写问题(small-write problem):数据盘本可并行访问,校验盘却让所有写串行化。校验盘每次逻辑写要做 2 次 I/O(读一次、写一次),所以 RAID-4 的随机小写吞吐量只有 R/2 MB/s——而且加盘也不会变好。

延迟:单次读映射到单盘,延迟同单盘;单次写要两读(并行)再两写(并行),总延迟约为单盘的两倍(读要等两个里较慢的;写回时磁头已就位,不需要寻道,可能比平均快些)。

8RAID-5:旋转校验

为了(部分地)解决小写问题,Patterson、Gibson 和 Katz 提出了 RAID-5:和 RAID-4 几乎一样,只是把校验块轮流放在各块盘上(原书 Figure 38.7),消除单一校验盘的瓶颈。

原书 Figure 38.7:5 盘 RAID-5(左对称布局)。每个条带的校验向左移一块盘,数据从校验盘右边开始排

因为 RAID-5 除了少数更好的地方外与 RAID-4 相同,它几乎完全取代了 RAID-4。唯一例外是明确知道只会做大块写、根本不会有小写的系统(如 NetApp 的 WAFL),那里用 RAID-4 更简单一些。

布局的细节:raid.py 支持两种 RAID-5 布局。左对称(left-symmetric)就是 Figure 38.7:第 r 个条带的校验在盘 N−1−(r mod N),数据从校验盘右边一块开始、绕回排列,这样顺序读时连续的块依次落在所有盘上。左非对称(left-asymmetric)的校验位置相同,但数据按盘号从小到大排、跳过校验盘(第二行是 4 5 6 P1 7)。在下面的模拟器里切换两者对比。

9模拟器:仿 raid.py 看每个逻辑请求变成哪些物理 I/O

这个模拟器重现了原书作业工具 raid.py 的核心:选 RAID 级别、盘数、chunk 大小,输入一串逻辑请求,逐步看每个请求被映射到哪块盘哪个偏移、产生哪些物理读写。每个块里存 1 字节的真实数据(十六进制小字),所以你能看到校验是怎样 XOR 出来的;还可以拔掉一块盘,看降级读如何用 XOR 重建数据,最后换盘重建。

模拟器

RAID 映射与物理 I/O

请求语法(分号或换行分隔):r 14 读块 14;w 4 写块 4;w 0 4 从块 0 开始写 4 块;fail 2 拔掉盘 2;replace 换新盘并重建。蓝框 = 物理读,红框 = 物理写,黄框 = 逻辑目标。点任意格子做"反向映射"。
原书示例:
物理读物理写逻辑目标校验块故障盘

建议的玩法:① 用"38.6 写 4、13"看 RAID-4 的校验盘(盘 4)负载条是怎样一枝独秀的,再把级别换成 RAID-5 重新载入同一序列;② 在 RAID-5 下把"小写校验"切成加法,比较同一个写请求的 I/O 数;③ 试 w 0 4(整条带)和 w 1 2(半条带)的区别;④ 运行"拔盘"预设,核对降级读算出的值是否等于原值。

10对比总结:Figure 38.8 计算器

把前面的分析汇总成一张表(原书 Figure 38.8)。这里省略了不少细节:镜像写时寻道时间是两次寻道的最大值,比单盘略高;RAID-4/5 更新校验时第一次读旧校验多半要完整寻道和旋转,但第二次写校验只需旋转。改下面的参数,表格里的数值会实时更新(绿色 = 该行最优):

计算器

RAID 容量、可靠性与性能对比

N = 磁盘数,B = 每盘块数,S / R = 单盘顺序 / 随机带宽,T = 单盘单次请求时间。S、R 也可以由下面一行的磁盘参数算出。

结论(原书原话的意思):

11其他有趣的 RAID 话题

12先预测,再验证

(作业 1)5 盘 RAID-5 左对称、chunk=1:逻辑块 9、16、23 分别在哪块盘、哪个偏移?各自条带的校验在哪块盘?先对照 Figure 38.7 的规律手算,再在模拟器里输入 r 9; r 16; r 23 验证。换成左非对称呢?
看答案

每条带 4 个数据块。块 9:条带 2(偏移 2),条带内第 1 个;校验盘 = 4 − 2 = 2;左对称数据从盘 3 开始 → 块 8 在盘 3、块 9 在盘 4。块 16:条带 4,校验盘 0,块 16 在盘 1、偏移 4。块 23:条带 5,校验盘 4 − (5 mod 5) = 4,第 3 个 → 盘 3、偏移 5。

左非对称:块 9 → 数据按盘号跳过校验盘 2 排:8 在盘 0、9 在盘 1;块 16 → 盘 1(条带 4 校验在盘 0,第 0 个数据在盘 1);块 23 → 盘 3。

(作业 2)4 盘 RAID-0,chunk 从 1 改成 2 再改成 4,逻辑块 14 的位置怎么变?先用公式 chunk = A / C、Disk = chunk % N、Offset = (chunk / N)·C + A % C 手算。
看答案

C=1:盘 2,偏移 3。C=2:chunk 7 → 盘 3,偏移 (7/4)·2 + 0 = 2。C=4:chunk 3 → 盘 3,偏移 0·4 + 2 = 2。chunk 越大,相邻的逻辑块越倾向于待在同一块盘上。

(作业 3,反向问题)勾选"反向模式",载入 5 盘 RAID-4 下的一个请求,你只看到物理 I/O:读 盘1 偏移2、读 盘4 偏移2、写 盘1 偏移2、写 盘4 偏移2。逻辑请求是什么?
看答案

两读两写、且涉及校验盘 4 → 这是一次减法小写。数据在盘 1 偏移 2 → 条带 2 的第 1 个数据块 → 逻辑块 2·4 + 1 = 9,即 w 9。

(作业 4)5 盘 RAID-5、chunk=1,请求 w 0 1、w 0 2、w 0 3、w 0 4 分别产生多少次物理 I/O(减法)?什么请求大小对 RAID-4/5 最高效?
看答案

1 块:4 次(2 读 2 写)。2 块:6 次(读 2 块旧数据 + 旧校验,写回 3 块;加法只要 5 次)。3 块:8 次(加法仍只要 5 次——"自动取少"模式会选它)。加法恒为 N 次,所以写的块越多越划算。4 块:整条带写,5 次写、0 次读。所以应该按完整条带的整数倍写(这里是 4 块 = 16KB),并且对齐条带边界。

(作业 5-7,粗略估算)4 块盘,100 个随机单块写,每次物理 I/O 约耗时 T。假设负载均匀,最忙的那块盘要做多少次 I/O(≈ 完成时间)?分别估算 RAID-0/1/4/5,再用"随机生成"(100 个请求、写比例 100%)看"最忙磁盘 I/O 数"统计是否吻合。
看答案

RAID-0:100 次 I/O 平摊到 4 盘 ≈ 25T。RAID-1:200 次物理写 / 4 ≈ 50T。RAID-4:数据盘各 ~67 次,但校验盘要做全部 200 次 ≈ 200T(R/2 的由来)。RAID-5:400 次 I/O 平摊到 4 盘 ≈ 100T(N·R/4 的由来)。随机抽样会有波动,但量级一致。读请求呢?RAID-0 与 RAID-1 都 ≈ 25T,RAID-4 ≈ 33T(校验盘闲着),RAID-5 ≈ 25T。

13动手实验

用 C 在内存里模拟 N 块盘,实现 Figure 38.7 同款的左对称 RAID-5:全条带写填满阵列 → 拔掉磁盘 2 → 逐块 XOR 重建并与原数据比对 → 对 1000 次随机小写分别用加法和减法更新校验,比较 I/O 次数、确认两种方法得到的校验块一致。

cd labs/38-raid && make && ./raid

也可以 ./raid 3 512 改盘数和块大小。示例输出(你的机器上数字会相同,因为这是确定性的计算;不同参数会不同):

== RAID-5(左对称),5 块盘,每块 4096 字节,每盘 64 块,可用 256 个逻辑块 ==
前 3 行布局(与原书 Figure 38.7 相同的规则):
      0     1     2     3   P0  
      5     6     7   P1      4 
     10    11   P2      8     9 

[1] 全条带写填满阵列:0 次读,320 次写(每行 5 写、0 读)
[2] 磁盘 2 故障 → 重建它的 64 个块(51 个数据块 + 13 个校验块):64/64 与原数据完全一致,共 256 次读
    例:第 0 行第 0 字节:0xA2 ⊕ 0x68 ⊕ 0xF5 ⊕ 0x10 = 0x2F(盘 2 上原本是 0x2F)

[3] 1000 次随机单块小写:加法 vs 减法(N 从 3 到 8)
    N | 加法 I/O/次 | 减法 I/O/次 | 两种方法的校验块是否相同 | 更省的方法
    3 |         3.0 |         4.0 | 相同 ✓               | 加法
    4 |         4.0 |         4.0 | 相同 ✓               | 一样多
    5 |         5.0 |         4.0 | 相同 ✓               | 减法
    6 |         6.0 |         4.0 | 相同 ✓               | 减法
    7 |         7.0 |         4.0 | 相同 ✓               | 减法
    8 |         8.0 |         4.0 | 相同 ✓               | 减法

该观察到:重建的 64 个块全部与原数据一致;每重建一个块要读 N−1 块盘;加法每次 N 次 I/O、减法固定 4 次,N=4 是交叉点。两种方法算出的校验完全相同——XOR 满足交换律、结合律,且 x ⊕ x = 0,所以 Cold ⊕ Cnew 恰好"标出了"哪些位需要翻转。

14自测题

1. 4 盘 RAID-0,chunk = 1 块,逻辑块 14 在哪里?
Disk = 14 % 4 = 2,Offset = 14 / 4 = 3。C 是 chunk = 2 时的答案。
2. 为什么镜像 RAID(RAID-1)的顺序读带宽只有 (N/2)·S?
读只需一份副本,但若轮流分配,每块盘拿到的是 0、4、8… 这样隔一块的请求,磁头要转过中间的块,一半时间在"空转"。
3. 4 比特的块 0011、0101、1001 的 XOR 校验块是?若丢了第二块,怎么恢复?
0011 ⊕ 0101 = 0110,再 ⊕ 1001 = 1111。重建用的是同一个运算:其余所有块(含校验)XOR。
4. 5 盘 RAID-4 用减法校验做一次单块随机写,需要几次物理 I/O?
读旧数据、读旧校验、写新数据、写新校验,共 4 次,与盘数无关。加法校验则要 N = 5 次(读 3 块其他数据 + 写 2 块)。
5. RAID-4 的随机小写吞吐量为什么只有 R/2,而且加盘也没用?
所有写都要访问唯一的校验盘,每次 2 次 I/O,于是校验盘的随机带宽 R 被一分为二,所有写被它串行化。RAID-5 把校验分散开才解决。
6. 关于 RAID-5 相对 RAID-4,下列说法正确的是:
RAID-5 只是把校验块旋转分布:容量 (N−1)·B、容忍 1 块盘、顺序 (N−1)·S 都与 RAID-4 相同;每次小写仍是 4 次 I/O,但能在各盘间并行,得到 N·R/4。
7. 镜像 RAID 写到一半断电,两份副本一新一旧。这叫什么问题?常见的硬件解决办法是?
多盘更新需要原子性。先把"要做什么"记到非易失 RAM 的日志里,崩溃后恢复程序重放未完成的更新,避免每次写都往磁盘记日志的高代价。
8. 同一个 5 盘 RAID-5,写 4 个连续且与条带对齐的块(整条带),和分 4 次各写 1 块,物理 I/O 各多少?
整条带写:4 个数据块 + 1 个新校验,全部并行写,0 次读 = 5 次。4 次小写:每次 4 次 I/O = 16 次。所以应当按条带大小对齐地写大块。
9. 在 fail-stop 模型里,下面哪种情况不在本章的考虑范围内?
fail-stop 只考虑整盘故障且易于检测。静默损坏(silent corruption)和潜在扇区错误要到"数据完整性"一章用校验和等技术处理。

15常见误区与小结

本章要点

和后续章节的联系:从下一章开始我们回到"一块盘"的视角,学习文件系统的 API(第 39 章)和实现(第 40 章)。RAID 里的一致更新问题会在第 42 章以"崩溃一致性"的形式卷土重来,日志(journaling)正是 RAID 用 NVRAM 记日志这一思想的推广;而 fail-stop 之外的静默错误,则留给第 45 章的校验和与数据完整性。