跳轉到

9.1 RAID 與資料冗餘#

概念說明#

RAID(Redundant Array of Independent Disks,獨立磁碟冗餘陣列)把多顆磁碟組成一個邏輯磁碟,用並行與冗餘同時換取效能與可靠性。主要等級:

  • RAID 0(條帶 striping):資料輪流切到各磁碟。讀寫吞吐近似 N 倍,但任一磁碟故障即全毀。無冗餘。
  • RAID 1(鏡像 mirroring):每筆資料寫兩份到兩顆磁碟。可容忍 1 顆故障,讀取可由任一副本服務。
  • RAID 5(條帶 + 單同位元):每個條帶組(N−1 顆資料 + 1 顆同位元)用 XOR 算出同位元區塊,同位元輪流分布在各磁碟。可容忍 1 顆故障;故障後以 XOR 重建:遺失資料 = 其餘所有區塊 XOR 同位元。
  • RAID 6:雙同位元(如 Reed-Solomon),可容忍 2 顆同時故障,大容量磁碟的現代標配(因重建期間再壞一顆的風險不可忽略)。
  • RAID 10:先鏡像再條帶,資料庫熱門選擇。

RAID 的本質是「空間換可靠性 + 並行換吞吐」,它是分散式系統副本與 erasure coding 的單機前身。

演算法步驟:RAID 5 的寫入#

小寫(read-modify-write): 1. 讀舊資料 D_old 與舊同位元 P_old(2 次 I/O)。 2. 計算差值 Δ = D_new XOR D_old。 3. 新同位元 P_new = P_old XOR Δ;寫入 D_new 與 P_new(2 次 I/O)。 → 共 4 次 I/O,這是 RAID 5 小寫放大的來源。

大寫(full-stripe write):整個條帶一次算同位元,只需 1 輪寫入。

Python 範例#

def xor_bytes(a, b):
    return bytes(x ^ y for x, y in zip(a, b))

class RAID5:
    def __init__(self, n_disks=4, block="0000"):
        self.n = n_disks
        self.disks = [dict() for _ in range(n_disks)]
        self.io = 0
        self.stripe = 0          # 目前條帶編號

    def _disk_of(self, lba):
        """邏輯區塊 lba → (磁碟, 條帶);同位元輪流分布"""
        stripe, idx = divmod(lba, self.n - 1)
        p = stripe % self.n                  # 同位元所在磁碟
        data_disks = [d for d in range(self.n) if d != p]
        return data_disks[idx], stripe, p

    def write(self, lba, data):
        d, s, p = self._disk_of(lba)
        old = self.disks[d].get((s, lba), b"\x00" * len(data))
        p_old = self.disks[p].get(s, b"\x00" * len(data))
        delta = xor_bytes(data, old)         # read-modify-write
        self.disks[d][(s, lba)] = data
        self.disks[p][s] = xor_bytes(p_old, delta)
        self.io += 4
        return d, p

    def simulate_fail(self, disk_id):
        """磁碟故障:以其餘磁碟的 XOR 重建該碟內容"""
        stripes = set()
        for d in range(self.n):
            if d == disk_id: continue
            for k in self.disks[d]:
                stripes.add(k[0] if isinstance(k, tuple) else k)
        for s in stripes:
            acc = b"\x00" * 4
            for d in range(self.n):
                if d == disk_id: continue
                for k, v in self.disks[d].items():
                    if (k[0] if isinstance(k, tuple) else k) == s:
                        acc = xor_bytes(acc, v)
            self.disks[disk_id][s] = acc

raid = RAID5(n_disks=4)
d, p = raid.write(0, b"AAAA")
print(f"資料寫入磁碟 {d},同位元在磁碟 {p},小寫 I/O: {raid.io}")
raid.write(3, b"BBBB")
print("磁碟 1 故障前內容:", {k: v for k, v in raid.disks[1].items()})
raid.simulate_fail(1)
print("故障重建後磁碟 1:", {k: v for k, v in raid.disks[1].items()})

複雜度與 I/O 成本分析#

  • 讀取:N 個磁碟並行,吞吐 ×N(RAID 0/5)。
  • 小寫:RAID 5 為 4 次 I/O(寫放大 4×);RAID 6 為 6 次。
  • 重建:整顆磁碟的所有條帶各一次 XOR + 寫,N TB 磁碟在眾多 I/O 壓力下可能需要數小時到數天。
  • 可靠性:MTTF 縮短效應——磁碟越多、越大,重建視窗越長,RAID 已不是備份;分散式系統因此改用 erasure coding(9.3 節)與多副本。

練習#

  1. 手算:條帶組資料 D1=1010、D2=0110、D3=1100,RAID 5 的同位元為何?D2 故障後如何重建?
  2. 統計 RAID 5 在「全部小寫」與「全部整條帶寫」兩種負載下的有效 IOPS 差異。
  3. 說明為何現代大容量磁碟陣列偏好 RAID 6 或 RAID 10 而非 RAID 5。