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

為「故障是常態」而設計

當你把十萬台便宜機器接在一起,此刻必然有東西壞了——而這沒關係,本來就是這樣設計的。這篇指南要說明,倉儲規模電腦如何透過冗餘、複製,以及一套預設零件會死掉的軟體,把無情的故障變成一件不值一提的小事。

在這個規模下,「壞掉」就是穩態

從本階的第一篇指南起,你已經把整個 倉儲規模電腦 當成單一一台機器看待;而從上兩篇你知道工作被噴灑到成千上萬台伺服器、以及把它們縫在一起的網路上。現在要面對那個讓資料中心設計顯得異星的後果:在這個規模下,總有東西是壞的。一顆硬碟死了、一條記憶體翻了位元、一個電源供應器罷工、一台交換器重開機、整個機架斷電——不是偶爾,而是持續不斷。正確的心智模型不是「零件有時會故障」,而是「此刻,某個角落,每一天的每一小時,都正有零件在故障」。

這道算術殘酷卻簡單。假設一台 商規 伺服器的 平均故障間隔時間 是 3 年——對單一一個盒子來說相當可靠了。把 5 萬台疊起來,這支機隊的整體故障率就高了 5 萬倍,於是平均下來大約每 30 分鐘就有一台機器掉線,整天如此、永遠如此。你沒辦法靠買更好的硬體解決這件事:把每台伺服器的可靠度翻倍,也只是把故障率減半,而你有 5 萬台。靠把每個零件做到完美而來的可靠度,根本無法規模化;它必須來自別的地方。

故障、錯誤、失效:把詞用對

要為「故障是常態」而設計,你首先得有精確的詞,來定義「麻煩」究竟是什麼意思。可信賴度 文獻裡的標準鏈有三環。故障(fault)是一個缺陷——一個卡住的位元、一處裂開的焊點、一次宇宙射線撞擊。故障可能潛伏好幾個小時。當它真的弄壞了某個狀態、或產生了一個錯誤的值,那就是錯誤(error)。而當這個錯誤對外界變得可見——使用者看到錯誤答案、一個請求逾時了——那就是失效(failure)。整套可靠系統的工藝,就在於趕在故障變成失效之前攔住這條鏈。

  fault            error               failure
  (defect)  --->   (wrong state)  ---> (visible to user)

  cosmic ray   ->  flipped bit    ->   crash / wrong reply
     |               |                    |
  ECC catches it  replica masks it   ...nothing reaches the user
     V               V                    V
  chain BROKEN     chain BROKEN        no failure observed
故障—錯誤—失效這條鏈,以及冗餘切斷它的兩處:ECC 阻止故障變成錯誤;複製阻止錯誤變成使用者可見的失效。

為什麼要為這套詞彙較真?因為每一環是由不同的機制切斷的,把它們搞混就會去修錯地方。你在硬體層面阻止故障變成錯誤——例如 錯誤更正碼 記憶體,會在任何程式讀到之前,偵測並修復一個翻掉的位元。你在系統層面阻止錯誤變成失效——例如在另一台機器上保留資料的第二份副本,這樣當一台伺服器吐出垃圾、或乾脆什麼都不回時,這個請求就直接由副本來服務。同一個目標,兩個截然不同的層次。

冗餘:花錢買備品,好讓故障無聊到不值一提

最核心的技法是 冗餘:每樣東西都比嚴格需要的多備一些,這樣任何一塊的損失都會被吸收掉,沒人會察覺。你在儲存那一階以 RAID 的形式見過這個想法的小型版本:資料連同同位元被攤在多顆硬碟上,所以死掉一顆硬碟並不會失去任何東西。倉儲下了同樣的賭注,並把它套用到每個地方——硬碟、伺服器、供電線路、網路路徑,乃至不同城市裡的整座資料中心。多餘的產能不是浪費;它是你付出的保險費,好讓那場無休止的故障細雨,永遠不會變成暴風。

具體來說,資料的標準模式是複製(replication):每一塊資料都保留三份副本,放在三台不同的機器上,最好還在三個不同的機架裡,這樣單一機架斷電就不會把三份全帶走。讀取可以由任何一份副本服務;寫入則必須抵達全部三份(或安全的多數)。當一台機器死掉,系統會注意到它的副本不見了,便悄悄地把它們重新複製到健康的機器上,好把份數補回三——這是一個永遠在背景跑的自我修復程序。從外界看,一顆硬碟死掉,不過就是一個數字漂回到三。

優雅降級:彎曲,而不折斷

冗餘藏起了小型的故障。但有時一次壞太多——整個機架掉了、網路某個區段斷裂分割、流量暴衝超出產能。這時的設計目標是 優雅降級:服務只是稍微變差,與損害成比例,而不是整個垮掉。一個搜尋引擎若失去十分之一的索引伺服器,應該回傳略微不那麼完整、慢上一絲絲的結果,而不是顯示一個錯誤頁面。系統會彎曲;它不會啪一聲折斷。

這直接接回上一篇的尾延遲故事。一個展開到一千台伺服器的請求,是被其中最慢的那一台挾持的,所以一台正在故障或過載的機器,不只是丟掉自己那份工作——它還能把整個回應拖進長尾裡。優雅降級說:與其永遠等一個生病的副本,不如設一個期限,期限一過,就用手上已有的東西回答。在 100 毫秒內回傳一個 95% 完整的結果,勝過在 10 秒內回傳一個使用者早就放棄等待的完美結果。

  1. 一個請求展開到許多副本,每個副本各握有答案的一部分。
  2. 每個分片都有一個期限;健康的副本能在期限內從容回答。
  3. 若某個副本死了或太慢,協調者不會空等——它要嘛跳過那個分片,要嘛轉問一份備用副本。
  4. 使用者準時拿到一個略微不那麼完整的答案,而且通常根本不知道出過任何差錯。

誠實的侷限:冗餘救不了你的那些事

人們很容易相信:副本夠多,服務就會長生不死。並不會,而冗餘漏掉的那些故障模式,正是釀成那些著名的、長達數小時當機的元兇。冗餘假設故障是獨立的——兩台機器上的兩份副本,不會基於同樣的理由在同一時刻一起死去。相關性故障(correlated failures)則狠狠地戳破這個假設:一次糟糕的軟體推送一口氣上線到每一台伺服器、一個毒害整支機隊的設定變更、一次帶走整座資料中心的供電事件、一個讓每台機器在同一秒崩潰的閏秒臭蟲。同一份有臭蟲的執行檔複製三份,只會故障三次。

所以誠實的圖像是分層的,而非絕對的。像 ECC 這類針對單一元件的把戲,擦掉了位元翻轉那場不停的細雨。複製優雅降級 吸收了獨立的機器與機架死亡那場穩定的雨。但相關性故障需要的是完全不同的防禦:把變更先緩慢地推給少數幾台機器(金絲雀部署)、把副本散佈到互相獨立的故障域、並演練災難以確認復原路徑真的有效。再多的冗餘,都替代不了「別一次把所有東西弄壞」。

退一步看,這套哲學就清楚了,而它正是貫穿整個本階的那條線。你不去追求一台永不故障的 倉儲規模電腦——在十萬個零件的規模下,那是幻想。你打造的是一項服務,它在底下的零件持續不斷故障的同時,照樣運轉。故障不再是緊急事件,而成了你早已穿好衣服迎接的尋常天氣。有了這份心態打底,最後一篇指南終於可以問出每個維運者都在意的那個問題:既然你非得買下這一切冗餘與電力,它到底花了多少錢?