JOVANA
Explore Library Glossary Getting Started Three Levels Fields How it works Mission
Join the mission
All guides

可靠度:RAID 與錯誤更正

前幾篇導覽教你怎麼把資料搬得快;這一篇問的是更難的問題——當硬體自己會翻轉位元、會壞掉時,你怎麼讓資料保持「正確」?答案是冗餘,而 RAID 與錯誤更正碼正是兩種優美而互補的辦法:多花一點點儲存空間,把對資料的信任買回來。

「快」不等於「值得信賴」

這一階前面幾篇導覽談的都是速度:怎麼透過匯流排PCIe 餵裝置、DMA 怎麼在不燒 CPU 週期的情況下搬移整塊資料、為什麼硬碟(HDD)要付尋道與旋轉延遲、而固態硬碟(SSD)卻得先抹除再寫入。但一個「飛快地回傳錯誤位元組」的儲存系統,比沒用還糟。這最後一篇談的是可靠度(dependability):你存進去的資料,就是你拿回來的資料;而且就算其中一個零件壞了,系統仍能繼續服務。

把用詞講精準很有幫助,因為在可靠度的世界裡它們並非同義詞。故障(fault)是硬體裡的瑕疵(一個磨損的快閃記憶體單元、一條接觸不良的線)。當故障被觸發,它產生一個錯誤(error)——系統狀態裡一個不正確的位元或數值。如果這個錯誤被放任傳到外界、汙染了使用者看到的服務,它就變成一次失效(failure)。可靠設計的整場遊戲,就是要斬斷這條鏈:容忍故障,讓它們永遠不變成使用者看得見的失效。這組故障—錯誤—失效的區分,正是本篇接下來所倚靠的詞彙。

我們也用數字來衡量可靠度。可靠度(reliability)問的是系統能運行多久而不失效——常以平均故障間隔時間MTBF)來概括。可用度(availability)問的是服務「實際上線」的時間佔比,這取決於它「多罕見才失效」以及「多快能復原」兩者。單顆現代硬碟的 MTBF 以「年」為單位,聽起來令人安心——直到你把一千顆塞進一座倉儲級電腦,才驚覺從統計上看,每週都有好幾顆正在死去。在規模面前,失效不是要去防範的「意外」,而是要去規劃應對的「持續背景發生率」。

貫穿一切的唯一念頭:冗餘

你會遇到的每一種可靠度機制,都是同一個念頭換上不同戲服:冗餘(redundancy)。你刻意存下比資料嚴格所需更多的位元,這樣萬一某些位元遺失或損毀,倖存的位元仍足以把原始資料釘死。最粗暴的形式是完整複製——擺兩顆內容一模一樣的硬碟,一顆死了另一顆來回答。這行得通,卻讓你的儲存成本翻倍。冗餘的藝術,在於用「少得多」的額外位元買到同等的保護,而這正是錯誤更正碼與較聰明的 RAID 等級所達成的。

冗餘內部有個關鍵區分。有時你只需要「偵測」出資料壞了——一個校驗位元(一個同位(parity)位元,設定成讓 1 的總數永遠是偶數)能逮到任何單一位元的翻轉,卻無法告訴你「哪一個」位元翻了,所以它修不了。偵測很便宜;更正得花更多冗餘位元,因為這個碼不只要察覺錯誤,還得指出它的位置。可以這樣抓個大概:偵測一次翻轉約需一個額外位元,而定位並更正它需要好幾個——而你想走得越深(更正兩個錯、撐過兩整顆硬碟陣亡),就得付出越多冗餘。

錯誤更正碼:在傳輸途中修好位元

下到位元層級,一個錯誤更正碼ECC)靠著附上幾個精心算出的額外位元來保護一塊資料。經典範例是漢明碼(Hamming code),它加入數個同位位元,每個各自看守一組不同、且彼此重疊的資料位元子集。當你把資料讀回來時,重新計算那些同位;若全部通過,資料就是乾淨的;若沒通過,「哪些同位失敗」的那個「樣式」會組成一個二進位數字——稱為症候(syndrome)——它直指那個翻轉的單一位元,你再把它翻回去就好。這就是「單錯更正」:字組裡任何位置的一個錯誤位元,都被無聲而精確地修復。

這不是什麼奇珍異玩——它在你於記憶體那一階學過的機器內部「持續」運轉。伺服器用的 ECC 記憶體為每個 64 位元的 DRAM 字組加上 8 個校驗位元(標準的 SEC-DED 碼:單錯更正、雙錯偵測)。它存在是為了對付軟錯誤(soft error):一道宇宙射線或一顆迷途的 α 粒子打中一個 DRAM 單元,能在毫無硬體損傷下翻轉一個儲存的位元——單元好好的,只是數值錯了。ECC 在資料流出記憶體時無聲地更正這些,這正是為什麼一座繁忙的資料中心能運行數年,卻從沒有一個翻轉的位元到達軟體層。快閃 SSD 對 ECC 倚賴得更深,因為快閃單元真的會磨損、會隨著老化而變得越來越不可靠。

Parity vs error-correcting code (single 8-bit byte)

  detection only (1 parity bit):
    data 1011001  -> add p so #1s is even -> 10110011
    read back 10010011 (one bit flipped):
      #1s is now odd  -> ERROR DETECTED, but WHICH bit? unknown -> cannot fix

  correction (Hamming, several check bits):
    recompute each overlapping parity -> some pass, some fail
    pattern of failures = syndrome = binary index of the bad bit
      syndrome 0101 -> bit 5 is wrong -> flip bit 5 back -> FIXED
一個同位位元只能拉警報;一個含多個重疊同位的碼,能把「失敗的樣式」變成「壞位元的位址」,於是它能被修好。

RAID:撐過一整顆死掉的硬碟

ECC 修好的是個別位元,但一整顆「乾脆不回應」的硬碟呢?那正是 RAID 要處理的——獨立磁碟冗餘陣列(Redundant Array of Independent Disks),把好幾顆便宜的硬碟綁成一夥,讓整個陣列比任何單顆硬碟都更可靠(而且往往更快)。RAID 最好理解成一道有名字的小階梯,每一階是「成本對保護」曲線上不同的一點,而每一階都只是冗餘換的另一套戲服。

  1. RAID 0(分條,striping):把資料切散到多顆硬碟以求速度,卻不加任何冗餘——這是唯一一個讓你「更不」可靠的等級,因為任何單顆硬碟陣亡就全盤皆失。它列在這裡是當基準,而非保護。
  2. RAID 1(鏡像,mirroring):在兩顆硬碟上各留一份完整副本。簡單、復原又快,但你要付出 100% 的額外儲存——正是前面那種粗暴的完整複製冗餘。
  3. RAID 5(區塊分條加分散式同位):在 N 顆硬碟上把資料分條,每條存一個同位區塊,並把這些同位散佈到所有硬碟。任何一顆硬碟陣亡,都能靠對倖存者做 XOR 把它的內容重建出來。額外開銷只有 N 顆裡的 1 顆,而非加倍。
  4. RAID 6(雙重同位):每條兩個獨立的同位區塊,於是陣列能撐過「兩顆」硬碟同時陣亡——在大型陣列上這很要緊,因為第二顆常常正好在第一顆漫長而吃重的重建期間故障。

RAID 5 的核心美妙地簡單——它就只是用 XOR 做同位。對每一條分條,同位區塊就是該條裡所有資料區塊的「互斥或」(XOR)。XOR 有個魔法性質:只要你知道除了一個以外的所有值,就能把手上還有的全部 XOR 起來,還原出那個遺失的。所以當一顆硬碟死掉,控制器從每顆倖存的硬碟讀出對應的區塊、把它們 XOR 起來,死掉硬碟上原本的位元組就原封不動地跳出來。你花一顆硬碟份的容量,換來任何單顆硬碟都可復原——和 ECC 在位元層級做的那筆慷慨交易一模一樣,如今放大到了整顆硬碟。

誠實的瓶頸——以及這一整階如何拼在一起

退一步,看看這一整階的形狀。我們開篇就坦承:I/O 往往是「真正」的瓶頸——一顆遠在一奈秒內就發出一道指令的 CPU,接著可能為了一顆硬碟等上數毫秒,那是數百萬週期的落差,連你先前見過的記憶體牆都相形見絀。這些導覽裡所有的機制,都是為了管理那道殘酷的落差而存在:DMA 與中斷讓 CPU 不必盯著緩慢的傳輸、SSD 縮短延遲、寬厚的 PCIe 連線拓寬吞吐量。可靠度是最後一塊拼圖,而它有自己的代價:每一次同位計算、每一次 ECC 檢查、每一次重建,都消耗頻寬與時間。你是在拿一些效能去買正確性,而在關鍵路徑上,這筆交易必須是「刻意」為之的。

同樣值得保持誠實的是:可靠度最要緊的地方,不在單一裝置層級,而在規模上。一顆筆電的 SSD 在它的有用壽命裡鮮少故障,所以它的主人幾乎不去想 RAID。但一座跑在廉價商用硬體上的倉儲級電腦,把零件死亡當成家常便飯,並為優雅降級(graceful degradation)而設計——當一個零件死去,系統以略微縮減的容量繼續運行,而不是崩潰。在那裡,可靠度不是最後才栓上去的功能,而是核心的架構假設,貫穿於冗餘的硬碟、跨機器複製的資料、以及繞過死亡節點重新導流的軟體。同一個念頭——花費冗餘把故障轉換成「沒發生的事」——只是在每一個層級重複,從一個 DRAM 字組上的 8 個 ECC 位元,一路到整座被複製的資料中心。

而這便與整道計算機結構階梯最初的念頭接上了環。效能從來不是唯一目標;效能鐵律(執行時間 = 指令數 x CPI x 週期時間)告訴你怎麼讓電腦「快」,卻對「答案對不對」或「機器在不在線上」隻字未提。一位真正的計算機結構師,永遠在同時權衡三件事——速度、成本與可靠度——而最優雅的設計,像 XOR 同位與漢明碼,靠著「幾乎免費」而在三者上同時取勝。那種權衡的功夫,遠勝於任何單一花招,正是你一直在學著去「看見」的這門手藝。