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

RAID:速度、冗餘,以及重建難題

一顆硬碟既慢又會壞。RAID 把好幾顆硬碟黏成一顆——靠分散、鏡像與同位——讓它更快、或更耐操、或兩者兼具。我們從零把各個層級搭起來、親手算一遍同位,然後直視幾乎人人都忘記的那個難堪事實:失效之後的「重建」本身,就是一個危險的時刻。

為什麼一顆硬碟永遠不夠

到現在你已經知道單一一顆硬碟的兩個硬事實。從本階段前面幾篇,一顆硬碟慢得令人心痛,因為每次存取都要付搜尋與旋轉,而它坐在儲存階層又慢又遙遠的底端。而從日常經驗,硬碟是個機械物,終究會壞——軸承磨損、磁頭撞毀,一顆快閃硬碟會耗盡它的抹除週期。所以單一一顆硬碟一次給你兩個分開的問題:它太慢,而且它是一個單點故障,一次死亡就把你所有的資料一起帶走。

RAID——獨立磁碟冗餘陣列(Redundant Array of Independent Disks)——是一個簡單而有力的點子:把好幾顆便宜的硬碟綁在一起,向作業系統呈現成一顆又快又耐操的硬碟。竅門在於有了多顆硬碟,你能做到兩件用一顆永遠做不到的事:把工作分攤到各顆硬碟上,讓讀與寫並行發生(更快),以及把某些資料存兩份、或存額外的檢查資訊,好讓一顆壞掉的硬碟能從倖存者身上重建回來(更安全)。幾乎每一種 RAID 設計都是某個用來換取速度、安全、或兩者的配方——而全部的學問就在它們之間的取捨。

分散與鏡像:兩招原始動作

從兩招基本動作開始;每一個有名字的 RAID 層級都由它們搭成。分散(striping,RAID 0)把你的資料切成固定大小的小塊,再把連續的小塊輪流鋪到各顆硬碟上——第 0 塊在 A 碟、第 1 塊在 B 碟、第 2 塊在 C 碟、第 3 塊回到 A 碟,依此類推。如今一次橫跨好幾塊的大讀或大寫會同時打中所有硬碟,所以有 N 顆硬碟,你每秒能搬動的資料量大約是 N 倍。這是純粹的速度與純粹的容量:每顆硬碟的每一個位元組都裝著你的資料,毫無浪費。但它的冗餘是零——恰恰與那個 R 所代表的相反。任何一顆硬碟死掉,就會在每個檔案裡戳出一個個破洞,整個陣列也就完了。把資料分散到 N 顆硬碟上,丟失資料的機率其實是一顆硬碟的 N 倍,因為 N 顆裡的任何一顆都能要它的命。

鏡像(mirroring,RAID 1)是相反的直覺:保留兩份完整副本,兩顆硬碟各一份,同步寫入。如今單一一顆硬碟失效對你毫無代價——作業系統就從倖存者那邊讀,你再從容地換掉壞掉的那顆。讀取甚至可以快一點,因為任一份副本都能服務一個請求。代價直白而無可迴避:你付兩顆硬碟的錢,卻只拿到一顆的容量。你花一半的錢買的是安全,不是空間。鏡像是最簡單、最值得信賴的冗餘,當資料珍貴、而你又沒有很多顆硬碟時,它至今仍是對的選擇。

於是這兩招原始動作圈出了兩個極端。分散全是速度與容量、毫無安全;鏡像全是安全、卻用掉一半容量作為代價。有意思的 RAID 層級活在兩者之間,而它們抵達那裡靠的是一個漂亮的把戲,讓你只花一顆額外硬碟的代價、而非把一切加倍,就得到冗餘:同位(parity)

同位:便宜的冗餘

同位是那種你親手做過一次之後就覺得理所當然、做之前卻像魔術的點子。核心運算是 XOR(互斥或),它有一個我們利用的性質:把所有東西 XOR 在一起做出一個同位區塊,之後你就能靠把其餘全部 XOR 起來,救回任何一個遺失的區塊。這跟學校裡的把戲是同一套算術——若你知道 a + b + c = 17,而有人把 b 擦掉了,你能救回 b,因為你知道 a、c 和總和。同位就是那個總和,逐位元算出來的。最貼近日常的感受方式:在資料位元加上同位位元之中,1 的個數永遠是偶數,所以若某個位元不見了,你就知道它原本必須是什麼才能讓個數保持偶數。

Three data disks + one parity disk.  P = D0 XOR D1 XOR D2

           bit pattern (one column)        count of 1s
  D0:           1                               \
  D1:           0                                |  data
  D2:           1                               /
  P :           0   (= 1 XOR 0 XOR 1)         total is even

Disk D1 dies.  Rebuild it from the survivors:
  D1 = D0 XOR D2 XOR P  =  1 XOR 1 XOR 0  =  0    <- recovered!
同位 = 各資料區塊的 XOR。丟掉任何一塊,把其餘 XOR 起來就能把它救回。一顆額外硬碟保護 N 顆資料硬碟。

這就是 RAID 5 的核心:把資料分散到 N 顆硬碟上換取速度,並在每一條條帶(stripe)上撥出一顆硬碟份量的空間放那條條帶的同位——但讓哪一顆硬碟放同位逐條帶輪替,這樣就沒有單一一顆硬碟變成寫入瓶頸。有 N 顆硬碟,你得到 N 減 1 顆的容量(不管你有幾顆,只有一顆份量「賠」給同位)、完整的分散讀取速度,並能在任何一顆硬碟死掉時存活。這是個美妙的交易:用鏡像的保護、卻只花鏡像的一小部分成本。RAID 6 老實地再往前一步——每條條帶放兩個獨立的同位區塊——所以它能在任何兩顆硬碟同時失效時存活,代價是兩顆硬碟份量的容量。請記住為什麼有人會想要在第二次失效時存活;那正是最後一節的全部重點。

取捨對照表,以及代價藏在哪裡

我們把常見的層級並排釘住,讓取捨變得具體,想像四顆 1 TB 的硬碟。RAID 0(分散):可用 4 TB,最快,零次失效都撐不住。RAID 1(鏡像,作為兩對、或一個大鏡像):可用 2 TB,每個鏡像能撐一次失效,簡單可靠。RAID 5(分散 + 一份同位):可用 3 TB,撐得住一次失效,全能的多面手。RAID 6(分散 + 兩份同位):可用 2 TB,撐得住任何兩次失效,大型陣列的選擇。RAID 10(先鏡像、再把鏡像分散):可用 2 TB,又快又耐操,當你付得起硬碟時的最愛。注意那個放諸四海皆準的型態:更多安全要嘛用容量、要嘛用寫入速度來換,從來不是免費的。

同位在寫入時也不是免費的,這是 RAID 5 與 6 老實的陷阱。要改一個區塊,陣列不能直接覆蓋它——否則整條條帶的同位就會錯。它必須讀出舊的資料區塊與舊的同位、算出新的同位,再把資料和同位都寫回去:一次小寫入大約是「讀—讀—寫—寫」。這個小寫入懲罰正是為什麼一個忙著做許多細碎散亂寫入的資料庫,常常偏好 RAID 10(就兩次單純的寫入,沒有同位運算),即便它讓出更多容量。作業系統用它對付慢硬碟一貫的方式來緩解這一擊——把寫入成批,讓就定位的代價攤提——但它無法讓同位的算術消失。

沒人提的重建難題

這是宣傳手冊跳過的部分,也是這篇之所以取這個名字的原因。「能撐住一顆硬碟失效」聽起來像終點線,但它其實是陣列一生中最危險那段時間的起點。當 RAID 5 裡一顆硬碟死掉,你現在是在沒有冗餘的狀態下運行——本來保護你的同位,正被花在掩蓋那顆消失的硬碟上。你插進一顆全新的硬碟,陣列開始重建(rebuild):它必須讀過每一顆倖存硬碟上的每一個區塊,把它們 XOR 起來,將死掉硬碟的內容重組到新硬碟上。在這完成之前,再多一次失效就會失去一切。

  1. 一顆硬碟失效。陣列進入降級(degraded)狀態:它仍然供應資料,但是靠即時重組那顆消失硬碟的區塊來供應,所以它變慢,而且再無任何安全餘裕。
  2. 你換掉死掉的硬碟。重建開始,必須讀過每一顆倖存硬碟的每一個區塊,去重算那顆遺失的——這是對整個陣列一次完整、持續不斷的掃描。
  3. 在大容量硬碟上,那次掃描要花很久。以硬碟速度複製 8 TB 份量的資料要跑上許多小時、有時超過一天——而這段期間整個陣列都毫無保護。
  4. 在那些小時裡,每一顆倖存硬碟都在重負之下被從頭讀到尾——這恰恰是最可能讓第二顆虛弱硬碟現形、或讓某個閒置時悄悄腐爛的不可讀磁區現形的壓力。

有兩件事讓這真正可怕,而且兩者都隨著硬碟變大而變糟。第一,陣列裡的硬碟通常是同一批、同一型號,一起買來、操得一樣兇——所以當一顆老死,它的兄弟們在統計上也蓄勢待發地準備跟上,而重建那沉重的讀取正好是那一推。第二,硬碟有一個微小但真實的「不可恢復讀取錯誤」機率:某個磁區就是讀不回來。在一次性的讀取裡你永遠不會注意到,但重建必須完美地讀過每一顆倖存硬碟的每一個磁區,而在一個好幾 TB 的陣列上,在那次完整掃描期間哪怕只撞上一個壞磁區的機率,已經不再是可以忽略的了。撞上一個,RAID 5 就無法完成重建——偏偏就在你最需要它的那一刻。

這正是為什麼對大容量的現代硬碟,RAID 6(兩份同位,撐得住兩次失效)和鏡像/RAID 10(重建只讀一顆倖存硬碟,而非整個陣列)超越了單純的 RAID 5。而這把我們直接繞回開頭那則警告:就連 RAID 6 講的也都是在重建期間撐住硬碟的死亡——它對刪除、毀損、火災毫無作為。對儲存,老實工程師的立場是分層的,一如大量儲存設計的通則:依你需要的可用時間、以及你能承受的重建風險來選一個 RAID 層級,並且在別處保留真正的備份,因為冗餘與備份回答的是兩個不同的問題,而誰也不能替代誰。

在固態硬碟上 RAID 還重要嗎?

值得老實地替這個階段收尾,因為 RAID 生於旋轉硬碟的年代,而固態硬碟改變了它的某些算術。冗餘的故事沒變:固態硬碟一樣會死、快閃單元一樣會磨損,所以分散、鏡像、同位保護你的方式和從前一模一樣,而重建難題也一樣真實(甚至可說更尖銳,因為一批壞掉的固態硬碟可能幾乎同時撞上它們的磨損上限)。改變的是速度的故事。單一一顆固態硬碟本就快到,為了原始吞吐量而分散的意義遠不如從前;而且因為固態硬碟沒有搜尋時間,那些讓分散式硬碟發光的、精心安排連續配置的把戲,根本不再以同樣的方式划算。

甚至有一個值得點名的小張力。同位的小寫入懲罰意味著 RAID 5 與 6 會產生額外的寫入,而在快閃記憶體上額外的寫入意味著額外的磨損——正是固態硬碟自家控制器已經在對抗的那個「寫入放大」。所以當預算允許時,現代的直覺在固態硬碟上偏向鏡像或 RAID 10:更簡單、對快閃壽命更溫柔、重建也快得多。這呼應了磁碟排程那篇的教訓——固態硬碟悄悄抽掉了好幾個旋轉硬碟年代聰明把戲的腳。冗餘的點子完整地撐過了這場轉變;偏向速度的那些則大多沒有,因為它們當初對抗的代價已經不在了。

最後,注意 RAID 本質上是什麼:一個抽象。它拿好幾顆有缺陷的實體硬碟,交給作業系統一顆虛擬硬碟——更快、或更安全、或兩者兼具——讓檔案系統可以使用它,而不必知道底下藏著幾顆真正的硬碟,正如邏輯區塊定址在一片雜亂的實體現實之上、交給軟體一個扁平的區塊陣列。那份分層正是這整個階段的深層主題。每一層都把它下面的彆扭藏起來,向上提供一個更乾淨的表面——而每一層抽象的代價,就是它底下那個老實的陷阱,而那恰恰是作業系統工程師被付錢去隨時看著的東西。