2883 字
14 分钟
持久数据的可靠性
RAID (Redundant Array of Inexpensive Disks)
- 廉价磁盘冗余阵列: 它将多块不可靠的小磁盘组合起来,变成一个又大又快又可靠的大磁盘。
| RAID级别 | 别名 | 数据组织方式 | 校验机制 | 最小磁盘数 | 可用容量 | 容错能力 | 主要优缺点 |
|---|---|---|---|---|---|---|---|
| RAID 0 | 条带化 | 数据分块交替存储 | 无 | 2 | n | 无 | 优:读写性能最高,存储空间利用率100%*;缺:无冗余,可靠性最低 |
| RAID 1 | 镜像 | 数据完全镜像 | 无(通过副本冗余) | 2 | n/2 | 允许损坏1块盘 | 优:读性能佳,数据安全性最高;缺:存储成本高,写性能较低,空间利用率50% |
| RAID 2 | 汉明码校验 | 数据按位条带化 | 专用汉明码(ECC)校验盘 | 可变(如4+3) | 小于n | 允许损坏1块盘(可纠错) | 优:可实时纠错;缺:设计复杂,校验盘比例高,成本高 |
| RAID 3 | 位级奇偶校验 | 数据按位/字节条带化 | 专用奇偶校验盘 | 3 | n-1 | 允许损坏1块盘(非校验盘) | 优:大文件连续读写性能好;缺:校验盘是单一瓶颈,随机写入性能差 |
| RAID 4 | 块级奇偶校验 | 数据按块条带化 | 专用奇偶校验盘 | 3 | n-1 | 允许损坏1块盘(非校验盘) | 优:适合多读的小文件场景;缺:校验盘是单一瓶颈,随机写入性能差 |
| RAID 5 | 分布式奇偶校验 | 数据与奇偶校验信息分布式存储 | 奇偶校验信息分布在各盘 | 3 | n-1 | 允许损坏1块盘 | 优:均衡性能、容量、安全性;缺:小量的写惩罚,坏盘重建压力大 |
| RAID 6 | 双重奇偶校验 | 数据与双重奇偶校验信息分布式存储 | 两个独立的奇偶校验信息 | 4 | n-2 | 允许损坏2块盘 | 优:容错能力极强;缺:设计更复杂,双倍的小量写惩罚,坏盘重建压力更大 |
理解RAID需要掌握4个核心概念:
- 镜像(Mirroring):将相同的数据复制到多个磁盘上。这直接提高了可靠性,并且多个副本可以并行读取,提升了读性能。但代价是存储利用率低,例如RAID 1的利用率只有50%。
- 数据条带(Data Stripping):将数据分割成块(位、字节或更大的块),然后轮流存储在多个磁盘上。当进行读写时,可以同时对多块磁盘操作,从而显著提升数据传输速率。RAID 0是纯粹的条带化,但它没有冗余,一旦某块磁盘故障,所有数据都会丢失。
- 数据校验(Data Parity):通过算法(如异或运算)计算出一段数据的校验值。当某块磁盘上的数据丢失时,可以利用剩余磁盘上的数据和校验信息来重建丢失的数据。这是一种用计算空间换取可靠性的方法(如奇偶校验)。
- 写惩罚(Write Penalty): 带有奇偶校验的RAID每次写入新数据,都需要重新读取旧数据和旧校验值,计算新校验值后再写入,这导致一次逻辑写入可能引发多次物理读写操作,降低了写效率。
在选择和实施RAID时,还需注意以下几点:
- 热备盘(Hot Spare):配置一块或多块空闲磁盘作为全局或局部热备盘。当阵列中某块数据盘故障时,系统能自动用热备盘替换故障盘,并立即开始重建数据,大大缩短系统运行在降级状态的时间。
- 重建时间(Rebuild Time):一块磁盘故障后,将数据恢复到新磁盘的过程可能非常耗时,尤其是对于大容量硬盘组成的RAID 5或RAID 6阵列。在此期间,阵列性能会下降,且如果再有磁盘故障,数据将面临风险。
- 硬件RAID vs. 软件RAID:RAID可以通过专门的硬件卡(硬件RAID)或操作系统驱动程序(软件RAID)实现。硬件RAID性能更好,不消耗主机CPU资源;软件RAID成本低,配置灵活,但会占用系统资源。
崩溃一致性与崩溃恢复
- 文件系统的崩溃一致性是个很关键问题,它确保了即使在系统遭遇突发状况——如断电、Kernel Panic 或硬件故障等问题,磁盘上的文件系统结构依然能保持逻辑上的完整和自洽,不会因为一次意外而导致整个分区的数据永久损坏或丢失。
问题的根源:非原子性的多步更新
想象一个简单操作:向一个已有文件append追加一个数据块。在底层,这通常需要三次独立的磁盘写入(bwrite):
- 更新数据位图:在数据位图(Data Bitmap)中找到一个空闲位并标记为已使用。
- 更新inode:将一个新的指针指向刚刚分配的数据块,并更新文件的大小。
- 写入新数据块本身。
- 底层的
bwrite(块写入) 操作既不保证原子性,也不保证执行顺序。操作系统将写请求提交给磁盘驱动,但磁盘自身的调度算法和内部缓存可能会为了效率而重新排序这些写入。
如果在这三次写入之间的任意时刻系统崩溃,文件系统就会陷入不一致的状态。例如:
- 仅写入数据块:数据在盘上,但没有人能访问它(元数据未指向它),相当于写入一个空间泄露的碎片。
- 更新了位图和inode,但数据块未写入:元数据指向了垃圾数据,对垃圾数据的读取甚至执行,可能出现一切BUG!
- 更新了inode和数据块,但位图未更新:文件可正常访问,但位图显示该数据块未分配,会被后面的数据覆盖。
append-only 日志 (Journaling)
- 日志落盘后,才更新数据结构。若崩溃,重放日志并清除(redo / undo)
- bread bwrite bflush(barrier): (all-or-nothing)
- bread lseek -> bwrite begin -> bwrite log -> bflush 落盘 -> bwrite end -> bflush ( -> 更新数据)
- ext4 checksum
- metadata写放大?数据丢失?
解决方案的演进
| 技术方案 | 核心思想 | 优点 | 缺点 | 应用示例 |
|---|---|---|---|---|
| 文件系统检查 (FSCK) | 事后补救:崩溃后扫描并修复整个文件系统 | 实现相对简单 | 恢复时间极长,与磁盘容量成正比 | 早期 Ext2 文件系统 |
| 日志 (Journaling) | 预写日志:先将操作意图记入日志,再实际修改文件系统 | 恢复速度极快(秒级),可靠性高 | 有额外的写操作开销 | Ext3/4, XFS, NTFS |
| 写时复制 (Copy-on-Write) | 不覆盖:任何更新都写入新位置,然后更新指针 | 数据一致性极佳,支持高效快照 | 加剧写放大,可能产生碎片 | Btrfs, ZFS, APFS |
| 软更新 (Soft Updates) | 有序写入:精心控制元数据写入顺序以避免不一致 | 避免写日志的开销 | 实现极其复杂 | BSD 的 UFS/FFS 相关变种 |
| 日志结构文件系统 (LFS) | 万物皆日志:将所有数据和元数据更新顺序写入日志 | 写性能高,尤其适合随机写 | 垃圾回收机制复杂,读性能可能受影响 | 研究性 |
日志机制详解
日志 (Journaling) 是目前最主流、最成熟的崩溃一致性解决方案。它的核心思想源自数据库领域的“预写日志” (WAL, Write-Ahead Logging)。
- 在数据结构上,日志不是一个无限增长的普通文件,而是一个预先分配好大小的“循环缓冲区(Circular Buffer)”。Head 跟 Tail 指针之间的日志就是写入但是还没写好的“活跃”数据,是系统崩溃后需要检查的区域。
日志技术通过“预写日志”来解决一致性问题,其核心流程如下:
- 日志写入:将本次操作需要修改的所有元数据(和数据)作为一个事务(包括开始标记TxB、内容、结束标记TxE)先写入日志区域。
- 日志提交:当日志事务完整写入磁盘后,才被认为已提交。
- 加检查点:将日志中的修改内容真正应用到文件系统的主数据结构上。
- 释放:主数据更新完成后,在日志中标记此事务空间可复用。
- 日志落盘后,才更新数据结构。若崩溃,重放日志并清除(redo / undo)
- bread, bwrite, bflush(barrier): (实现 all-or-nothing)
- bread lseek -> bwrite begin -> bwrite log -> bflush 落盘 -> bwrite end -> bflush ( -> Checkpoint 更新数据) -> 修改Head指针释放日志空间
# 查看为每个设备运行的日志内核线程 (journaling block device)ps -el | grep jbd崩溃后的极速恢复
当系统崩溃后重启时,文件系统驱动程序无需扫描整个磁盘,只需检查小小的日志区域:
- 情况1:如果在日志中发现一个完整且已提交的事务(即找到了
TxB和对应的TxE),但可能还没来得及Checkpoint。恢复过程就是简单地重放(Redo) 日志:将日志中的修改内容再写一遍到最终位置。 - 情况2:如果在日志中发现一个不完整的事务(只有
TxB没有TxE),说明在日志提交完成前就崩溃了。恢复过程就是忽略(Undo) 这个事务,因为它从未被“承诺”过,主数据区也从未被它“污染”。
日志的三种模式:安全与性能的权衡
日志主要保护的是元数据 (Metadata) 的一致性,但如何处理数据 (Data) 本身,ext3/ext4 提供了三种模式:
journal模式 (最安全,最慢)- 行为:数据和元数据都先完整地写入日志。
- 优点:提供了最高级别的一致性保证。无论何时崩溃,文件内容和其元数据总是同步的。
- 缺点:所有数据都要写两遍(一遍到日志,一遍到最终位置),造成了严重的写放大(Write Amplification),性能开销最大。
ordered模式 (默认模式,最佳平衡)- 行为:只将元数据写入日志,但强制规定一个写入顺序:必须先将数据块成功写入最终位置,然后才能将引用该数据块的元数据事务提交到日志中。
- 优点:避免了最坏的“元数据指向垃圾数据”的情况。因为它保证了只要元数据被恢复,其指向的数据块内容至少是正确的(虽然可能是旧的,但不是垃圾)。性能远高于
journal模式。 - 缺点:如果系统在数据写入后、元数据提交前崩溃,可能会导致文件末尾出现一些未被元数据引用的“新数据”。
writeback模式 (最快,最不安全)- 行为:只将元数据写入日志,并且对数据和元数据的写入顺序没有任何保证。
- 优点:性能最高,因为它解除了数据和元数据写入的顺序依赖。
- 缺点:一致性保证最弱。崩溃恢复后,你可能会发现文件大小增加了,但对应的物理块里还是旧的、无关的数据。这在很多应用场景下是不可接受的。