13.3 存储冗余、校验与备份
档案馆已经学会在断电后恢复一致结构,你却在库房门口看见另一种灾难:一块设备彻底失效,另一块仍能读却悄悄翻了几个 bit,还有一份目录被操作员亲手删掉。日志无法解决这些问题,因为它记录的是写入顺序,不是另一个故障域里的副本。
RAID、checksum、snapshot 和 backup 分别处理其中一部分,没有任何一个可以替代其余全部。先记住这条边界:
冗余帮助当前服务继续运行;备份提供另一个时间点或故障域中的恢复副本。
1. 先按故障模型讨论 RAID
RAID 把多块设备组合成一个逻辑存储。下面假设各盘容量相同,用 N 表示磁盘数、S 表示单盘容量:
| 布局 | 可用容量 | 能承受的整盘失效 | 主要特点 |
|---|---|---|---|
| RAID 0 | N × S | 0 | 条带化,无冗余 |
| 两盘 RAID 1 | S | 任意 1 盘 | 镜像,布局直观 |
| RAID 5 | (N - 1) × S | 任意 1 盘 | 分布式单校验 |
| RAID 6 | (N - 2) × S | 任意 2 盘 | 双重独立校验 |
| RAID 10 | N / 2 × S | 每个镜像组至多 1 盘 | 镜像组再条带化 |
表中的容错只描述“整盘失效”这一种模型。控制器故障、固件缺陷、机柜断电、误操作和文件系统损坏可能同时影响多个成员盘。
RAID 10 也不能简单说成“任意坏 N/2 块”。若同一镜像组的全部成员失效,数组就丢失对应条带;恰好分散在不同镜像组时,才可能承受多块盘同时失效。
2. 条带、镜像与校验
RAID 0:只追求分布
数据被分散到多块设备:
stripe 0: [A0 on disk 0] [A1 on disk 1]
stripe 1: [A2 on disk 0] [A3 on disk 1]多个请求有机会并行,但任一成员盘失效都会使部分条带不可读,逻辑卷整体通常不可用。它适合可重新生成的数据或上层已经提供冗余的场景,不能被称为容错方案。
RAID 1:保存相同内容
镜像把同一逻辑数据写到多个成员。读取可以从可用副本选择,写入则要满足阵列的完成策略。镜像能从整盘失效中继续服务,也可以给校验和修复提供另一份数据。
但镜像会忠实复制逻辑层发出的错误写入。误删、勒索软件加密和应用覆盖会很快出现在所有镜像成员上。
RAID 5/6:用校验恢复缺失成员
RAID 5 在每条 stripe 中保存数据块与一个分布式校验块。某一成员缺失时,可以用其余块重建。RAID 6 使用两组独立校验关系,允许同时缺失两块成员。
小范围覆盖可能需要:
- 读取旧数据和旧校验;
- 计算新校验;
- 写新数据和新校验。
若中途断电,新数据与校验可能不匹配,这类风险常被称为 write hole。控制器缓存、日志、全条带写与文件系统集成可以缓解它,但“有奇偶校验”本身没有说明掉电一致性协议。
RAID 10:镜像组上的条带
RAID 10 把每个数据块写入一个镜像组,再把不同块分布到多个组。它避免了 RAID 5/6 的校验更新路径,常用于重写频繁、恢复时间敏感的工作负载。代价是通常只有一半裸容量可用。
“更适合数据库”仍不是无条件结论。数据库自身复制方式、云块存储承诺、容量预算、故障域和恢复目标可能改变选择。
3. 用代码核对容量假设
下面的函数故意只接受等容量磁盘,并把布局约束写进代码:
def usable_capacity(level, disk_sizes):
if not disk_sizes or any(size <= 0 for size in disk_sizes):
raise ValueError("disk sizes must be positive")
if len(set(disk_sizes)) != 1:
raise ValueError("this model requires equal-sized disks")
disks = len(disk_sizes)
size = disk_sizes[0]
normalized = level.upper()
if normalized == "RAID0":
if disks < 2:
raise ValueError("RAID0 needs at least two disks")
return disks * size
if normalized == "RAID1":
if disks != 2:
raise ValueError("this model defines a two-disk mirror")
return size
if normalized == "RAID5":
if disks < 3:
raise ValueError("RAID5 needs at least three disks")
return (disks - 1) * size
if normalized == "RAID6":
if disks < 4:
raise ValueError("RAID6 needs at least four disks")
return (disks - 2) * size
if normalized == "RAID10":
if disks < 4 or disks % 2 != 0:
raise ValueError("RAID10 needs an even number of disks")
return disks // 2 * size
raise ValueError(f"unknown level: {level}")
sizes = [8] * 6 # six 8-TB disks; units remain TB in this model
assert usable_capacity("RAID0", sizes) == 48
assert usable_capacity("RAID5", sizes) == 40
assert usable_capacity("RAID6", sizes) == 32
assert usable_capacity("RAID10", sizes) == 24
print("capacity checks passed")真实阵列混用不同容量成员时,许多实现只能按最小成员或分区计算可用空间,额外部分可能浪费。厂商还可能提供不同于传统 RAID 等级的布局,容量公式应以具体产品文档和配置为准。
4. 少一排档案还能营业,重建时却最脆弱
数组进入 degraded state 后,冗余余量已经减少;重建又会长时间读取其余成员并写入替换设备。档案馆看似仍开门,风险窗口却比平时更大。
成员盘失效后,阵列会进入 degraded 状态。此时读请求可能需要从镜像副本或校验关系重建缺失数据。替换设备后,系统还要扫描大量存量数据并写入新盘。
重建风险来自多方面:
- 剩余设备承受持续读取,更容易暴露潜伏错误;
- 用户业务与重建争夺 I/O;
- 阵列在冗余减少的状态下停留更久;
- 第二块盘或同一故障域可能继续失效;
- 错误更换了健康成员,会人为扩大故障;
- 热备盘若从未被实际验证,也可能在接管时失败。
不要根据容量给出固定的“重建要几小时”。介质速度、当前负载、控制器策略、错误重试和重建限速都会改变结果。监控应关注降级时长、介质错误、重建进度和备用设备健康,而不只是阵列最终是否显示 optimal。
5. 校验和与冗余要配对
传统块设备可能返回内容错误但没有报 I/O 失败。没有端到端校验时,上层无法区分“读取成功”与“读到了错误字节”。
完整的数据完整性路径需要:
写入时计算校验
↓
校验信息与数据由合适的更新协议保护
↓
读取或 scrub 时重新计算
↓
不匹配 ──> 从独立冗余取得候选副本
↓
验证候选并修复损坏副本只做镜像但不校验,遇到两个副本内容不同时可能不知道谁正确;只做校验但没有冗余,则只能检测,不能修复。校验算法还要匹配威胁模型:防随机介质错误与防恶意篡改并不是同一个目标。
6. 快照为什么仍不是备份
COW 文件系统或存储阵列可以快速创建快照。快照很适合:
- 回滚一次错误发布;
- 保存短期一致视图;
- 为备份任务提供稳定读取点;
- 快速克隆测试环境。
但若快照与主数据位于同一设备、阵列或管理域,它们可能一起受到:
- 整套存储损坏;
- 管理员误删;
- 凭据泄露;
- 勒索软件删除快照;
- 机房或账号级故障。
快照是版本保留机制。只有当副本被复制到独立故障域,并有明确保留、访问控制和恢复流程时,它才成为备份体系的一部分。
7. 备份要从恢复目标反推
先定义两个目标:
- RPO(Recovery Point Objective):最多能接受丢失多长时间的数据;
- RTO(Recovery Time Objective):故障后多快必须恢复服务。
RPO 决定备份或复制频率,RTO 决定恢复介质、自动化与预热程度。只写“每天备份”没有说明一次恢复需要三小时还是三天,也没有说明最后一次成功备份是否可读。
一套更完整的备份设计会考虑:
- 版本化,而不是只保留一个会被覆盖的副本;
- 独立故障域与独立凭据;
- 至少一份难以被在线攻击者修改的副本;
- 传输和静态加密,以及密钥的独立恢复;
- 数据库等有状态系统的一致性备份协议;
- 校验、清单与定期恢复演练;
- 明确的保留期、删除流程和合规要求。
恢复演练必须真正启动服务或验证业务数据,不能止于“备份任务返回成功”。无法在 RTO 内还原的备份,对事故现场的帮助有限。
8. 选型时问什么
不要从“RAID 5 还是 RAID 10”开始,先回答:
- 需要防的是整盘失效、静默损坏、节点失效还是误操作?
- 数据是否已在应用层跨节点复制?
- 阵列、主机、电源和机架分别属于哪些故障域?
- 随机写、顺序吞吐、尾延迟和容量谁更重要?
- 降级期间是否还能满足性能目标?
- 重建和 scrub 的窗口有多长,如何监控?
- 快照由谁删除,备份凭据是否与生产隔离?
- 最近一次完整恢复演练是什么时候,结果是否满足 RPO/RTO?
云环境里的“磁盘”可能已经由服务商在底层复制。再在虚拟盘之上套传统 RAID,既可能增加复杂度,也可能把看似不同的成员放在同一底层故障域。应以云产品当前的持久性、故障域和快照语义为准。
9. 小结
RAID、校验、快照和备份对应不同问题:
- RAID 0 没有容错,RAID 1/10 依靠镜像,RAID 5/6 依靠校验;
- 可用容量公式不能表达控制器、机架和人为故障;
- 降级与重建期间,冗余下降且潜伏错误集中暴露;
- 校验和负责发现错误,冗余副本负责提供修复来源;
- 快照保留同一系统中的历史视图,不天然跨越故障域;
- 备份是否可靠,要由版本隔离、恢复演练和 RPO/RTO 证明。
到这里,文件从命名、写入、崩溃恢复到设备失效形成了完整链条。下一章进入虚拟化与容器,看操作系统怎样把 CPU、内存和 I/O 的真实边界重新包装给不同工作负载。