為什麼要複製資料
不只是備份——把同一份資料放到很多台機器上,換來更快、更穩、更扛得住流量的系統
開場先聽一句忠告
第 5 章一開頭,作者沒有先講技術,而是引用了《銀河便車指南》作者 Douglas Adams 的一句話當題詞。這句話說的其實是「複製」這整章的潛台詞:你以為某個東西「不可能出錯」,恰恰是最危險的假設。
「『可能出錯的東西』和『不可能出錯的東西』,最大的差別在於:當那個『不可能出錯的東西』真的出錯時,你通常根本修不了它、碰都碰不到。」
——道格拉斯・亞當斯,出自《大致無害》(Mostly Harmless,1992)
「複製(Replication)」的意思是:把同一份資料的副本,放在透過網路相連的多台機器上。
如果要複製的資料永遠不會變,複製其實很簡單——存一次就搞定。
複製真正的難處,全部都出在「資料會變」這件事上——這也是整章要講的主題。
「單一台機器」聽起來很簡單、很不容易出錯——直到它真的壞掉,你才發現整個服務也跟著停了。複製就是提早承認「機器會壞」,先把備援準備好。
複製,不只是「多印幾份」
你可能聽過「備份」,資料複製聽起來很像對吧?但備份通常是「存起來、平常用不到、出事才拿出來」;資料複製講的是另一件事——這些副本平常就同時在線上,隨時準備服務使用者。
每一台存著這份資料拷貝的機器,叫做一個副本(Replica)。想像成「影分身之術」——本體的資料被複製成好幾個一模一樣的分身,散布在不同機器上,各自都能獨當一面。
放在抽屜裡、平常沒在跑的隨身硬碟備份,出事才拿出來救急——那是「備份」。而「複製」的副本,是隨時開著、隨時能接客人的分店,跟總店做一樣的事。這正是這一章要講的東西。
複製資料的三大好處
書裡明講:要複製資料,通常是為了下面這三個理由——不是因為「怕資料不見」這麼單純,而是要換來三種實實在在的好處。
把資料放在離使用者「地理上更近」的機器上,使用者存取時不用繞大半個地球,等待時間自然變短。
就算某一台機器掛了、故障、斷線,只要還有其他副本在,系統照樣能繼續運作,不會因為單點故障就整個停擺。
把能處理讀取請求的機器數量「橫向擴充」,多台一起分攤讀取流量,整體能撐住的讀取量就變大了。
想像一間超紅的珍珠奶茶店:只開一間總店,你得跨縣市排隊(延遲高);總店突然公休你就沒奶茶喝(可用性低);排隊排到天荒地老(吞吐量低)。開好幾間分店,三個問題一次解決——這就是複製資料在做的事。
這一章先假設整份資料小到每台機器都放得下完整一份;之後遇到資料大到單機裝不下的情況,會用另一招「分區(partitioning)」來處理,那是後面章節的事,這裡先不展開。
好處是怎麼做到的?先看一眼骨架
要享受這三大好處,資料必須先能從一台機器,正確地傳到其他機器上。下面用一個最基本的骨架,帶你看一次「寫入」與「讀取」怎麼在多個副本之間跑動——這正是下一模組要細講的機制。
資料不是「一次複製完就沒事」——它是活的,一直在變。每次變更都要想辦法讓所有副本跟上。這件事「怎麼分工」、「誰能寫、誰只能讀」,正是下一模組要拆開來講的重點。
複製的兩難:資料一直在變,怎麼辦?
如果資料存進去之後永遠不會變,複製其實超簡單——每台機器存一次,就永久保持一致了。麻煩的是,真實世界的資料是活的:使用者一直在新增、修改、刪除——訂單、貼文、庫存數量,隨時都在動。
這就衍生出整章要處理的核心問題:資料一直在變,要怎麼讓多份副本保持一致性,並且快速同步到最新狀態?
資料一直在變,要怎麼讓多份副本同步?如果做得不好,使用者可能在不同副本上看到不一樣的答案——例如商品庫存有的機器顯示 100 件、有的已經是 99 件,導致超賣。這正是本章接下來要拆解的難題。
書裡接著介紹三種主流做法來解這個難題:單領導者複製、多領導者複製,以及無領導者複製——幾乎所有分散式資料庫都是用這三種方式的其中一種。
小試身手
複製的動機搞懂了嗎?來兩題檢查一下:
複製的好處講完了,第一個、也是最常見的做法登場——指定一台機器當「老大」。
主從複製:最常見的複製方式
一個節點說了算、其他節點乖乖跟——從寫入、複製到「老大」掛掉之後怎麼辦
資料庫也需要一個「說了算」的人
如果一份資料同時存在好幾台機器上,馬上會冒出一個問題:每次寫入,到底要先寫哪一台?如果大家各寫各的,資料很快就會兜不起來。
最常見、也最簡單的解法,就是主從複製——選一個節點當「老大」,其他節點乖乖跟著複製,誰都不准自己作主。
把資料庫想成一間咖啡店。首席咖啡師負責接下所有新訂單、做出咖啡,還把配方一步步記在筆記本上;學徒咖啡師不接新訂單,只負責照著筆記複製出一模一樣的咖啡,順便應付來問「我的咖啡好了嗎」的客人。
這裡有兩件事一定要記住:寫入,永遠只找老大(Leader);讀取,找老大或學徒都行(Follower)。這條規則簡單到有點反直覺——但正是它讓整套系統保持一致。
直接讀原文,旁邊就是白話
主從複製到底怎麼運作?原書用很精簡的三步驟講完,我們把關鍵幾句拉出來,原文在左、白話在右:
其中一個副本會被指定為主節點。客戶端想寫入資料庫時,一定要把請求送給主節點,它會先把新資料寫進自己的本地儲存。
主節點每次把新資料寫進自己的儲存後,也會把這筆變更透過「複製日誌」或變更串流,發送給它所有的從節點。
每個從節點都會拿到主節點傳來的日誌,並依照主節點處理的「同樣順序」,一筆一筆套用,藉此更新自己那份資料副本。
客戶端想讀取資料時,可以查詢主節點,也可以查詢任何一個從節點。
但寫入這件事,只有主節點能接受。
「同樣順序」四個字才是關鍵——所有從節點都照著主節點決定的「變更順序」去套用,這正是讓一堆副本最終長成同一份資料的秘密,而不是靠誰算得快、誰運氣好。
拆開來看:老大跟學徒各自的工作
把上一節的原文拆成三件具體的事,你會發現主從複製其實沒有想像中複雜:
不管是新增使用者、發文還是改密碼,所有寫入請求都得先送到 Leader。它是資料的權威來源,也是唯一的「守門員」。
Leader 每寫一筆,就把這筆變化記進 複製日誌,像廚房裡的食譜筆記——後面的人照著做,就能重現一模一樣的結果。
Follower 依序套用複製日誌,資料跟著同步,同時分擔掉大部分的讀取流量,讓 Leader 專心處理寫入。
多數應用「讀多寫少」——首頁瀏覽遠比發文次數多。讓一群 Follower 分攤讀取,就是常說的讀取擴展(Read Scaling);把 Follower 擺在使用者附近,還能順便降低延遲。PostgreSQL、MySQL、MongoDB,甚至 Kafka 這類訊息佇列,都內建了這一套。
跑一遍完整的主從複製生命週期
從一筆寫入進來,到 Leader 突然故障、系統選出新老大——按「下一步」,看這齣戲怎麼演。
如果採用非同步複製,舊 Leader 故障前那些「還沒來得及複製給 Follower」的寫入,就可能永遠消失;萬一舊 Leader 後來又活過來、卻誤以為自己還是老大,就會撞出兩個 Leader 同時存在的腦裂(Split Brain)——這是自動故障轉移最怕遇到的地雷。
動手配配看:Leader 該等多久才回報成功?
Leader 寫完本地資料後,要不要等 Follower 確認收到,才對客戶端說「搞定了」?這是一個永恆的取捨題。把兩種模式拖到它對應的取捨後果:
同步複製就像總咖啡師堅持等每個學徒都點頭確認記下食譜,才敢跟顧客說「咖啡好了」;複製延遲則是學徒還沒學會新配方前,顧客點到舊口味的那段尷尬時間差。多數系統會折衷用「半同步複製」——只逼一個關鍵學徒點頭,其他人慢慢跟上。
小試身手
主從複製的骨架抓到了嗎?來兩題檢查一下:
複製日誌傳輸需要時間,這段「時間差」會讓使用者看到什麼奇怪現象?往下捲,我們去會會「複製延遲」這個麻煩鬼。
複製的陰影:最終一致性
領導者說「寫好了」的那一刻,追隨者可能還沒跟上——這段時間差,會讓使用者看到三種說不出哪裡怪的怪現象
資料為什麼會「遲到」?
上一模組講過,資料庫不只一份,而是有好幾份副本分散各地,這些副本叫 追隨者。 你寫入資料時(例如發一篇貼文),通常先送到 領導者, 由它接收、存好。
關鍵在於:領導者把變更「傳給追隨者」這件事,通常是 非同步 進行的。領導者不會傻傻等所有追隨者都更新完才跟你說「搞定」——它自己存好就立刻回你,然後在背景把變更慢慢傳出去。
這就像你點餐後,廚房(領導者)做好菜就先說「餐點準備中」,而不是等外送員(追隨者)把餐點送到你手上才通知你。
領導者是廚房,追隨者是外送員。廚房出餐的瞬間跟你說「好了」,但外送員送到你手上還需要時間——這段等待,就是接下來要談的「複製延遲」。
複製延遲,與「最終」會一致
從領導者完成寫入,到追隨者實際套用完更新,中間這段時間差,叫 複製延遲。 在延遲期間,你若剛好讀到那個還沒跟上的追隨者,看到的就是舊資料。
但這個「不一致」不是永久的。只要你停止寫入、給點時間,所有追隨者最終都會追上領導者,資料變得完全一致——這就是 最終一致性。 複製延遲可能只有幾毫秒,也可能在系統負載重、網路不順時拉長到幾分鐘甚至更久。
「最終一致性」的意思是:資料總有一天會全部同步,但在那之前,你可能會看到舊的資料。「最終」這個詞,故意講得很模糊——沒人保證延遲的上限。
親眼看一次:剛發的留言,怎麼「消失」了
下面演給你看一個最尷尬的情境——你剛寫入一筆留言,馬上重新整理,卻打到一個還沒跟上的追隨者。按「下一步」跟著資料走一遍。
對使用者來說,這就是「我的留言不見了、App 是不是壞了」。他們感受到的是怪現象,不是背後的技術原因——接下來就來認識三種最常見的怪現象。
原文現場:一段「通靈」對話
書裡用一段對話說明第三種怪現象——因果順序被打亂時,答案居然搶在問題之前出現。先看原文,再看白話。
波恩斯先生問:「凱克太太,你能看到多遠的未來?」
凱克太太答:「大概十秒吧,波恩斯先生。」
這兩句話之間有因果關係:凱克太太是先聽到問題,才回答的。
但旁觀者聽起來,卻像是凱克太太在問題被問出來之前就已經回答了。
這種「通靈能力」很厲害,但也超級讓人困惑。
原因很簡單:凱克太太的話走了延遲很短的追隨者,波恩斯先生的話卻走了延遲較長的那個。旁觀者先聽到「果」、後聽到「因」,邏輯整個亂套。
複製延遲的三種怪現象,與對應解方
怪現象各不相同,但都源自同一件事:不同的讀取,打到了進度不一的副本。每種怪現象都有一種「讀取保證」對症下藥:
你剛發了貼文,馬上重新整理卻看不到——像是寫入「憑空消失」。解法:讓使用者讀「自己剛可能改過的資料」時,強制從領導者讀;其他人看,仍從追隨者讀,不犧牲效能。
第一次刷新看到新留言,第二次刷新它卻「不見了」——像時間倒流。解法:用 使用者黏性, 把同一位使用者「黏」在同一個追隨者上,讀到的資料只會越來越新,不會忽新忽舊。
像凱克太太的「通靈」——答案先於問題出現,因果關係被打亂。解法:把有 因果關係 的寫入(例如一問一答),盡量路由到同一個領導者或同一個資料庫分區,順序在複製過程中就不會被打散。
這三種讀取保證都比「最終一致性」更強一點,但都還沒到「強一致性」那麼貴、那麼犧牲效能。它們是針對性地「補洞」——只補使用者真正會感到困惑的那幾個洞,其他讀取仍分散到追隨者上跑,兼顧體驗與效能。
動手配配看:怪現象該配哪種保證?
把左邊的讀取保證,拖到它該解決的情境上。
小試身手
複製延遲的三種怪現象、三種解方,你抓到了嗎?來兩題檢查一下。
主從複製只有一個「老大」可以寫,如果允許好幾個地方同時寫,甚至完全沒有老大呢?下一站,我們往下捲,看看多領導者與無領導者複製,會打開什麼樣的新問題。
多主與無主:更自由的複製模式
拿掉唯一的班長之後,寫入變快、變耐操,但也開始有人「打架」
班長不夠用了,怎麼辦?
上一站我們認識了單主複製:所有寫入都要先交給唯一的 領導者, 簡單、好懂,但也有代價——如果領導者在地球另一端,每次寫入都要跨海一趟;如果整個機房斷線,寫入就整個停擺。
這一站,我們把「唯一班長」這個限制拿掉,看看資料庫世界怎麼玩出更自由(但也更麻煩)的花樣: 多主複製 讓好幾個人同時當班長; 無主複製 則乾脆連班長都不要了。
把資料庫想成一家跨國新聞社。單主複製像是所有新聞都要先傳回總社審核發稿;多主複製像是每個分社自己就能發稿,之後再互相交換新聞;無主複製更徹底——沒有總社,每個記者手上都有一份完整的新聞檔案,大家直接互相核對、更新。
直接讀原文:衝突是怎麼冒出來的
我們挑書裡描述「寫入衝突」與「無主複製」登場的兩小段原文,一句對一句看白話。
多主複製最大的麻煩,就是寫入衝突可能發生——這代表你一定得處理「衝突要怎麼解決」。
單主資料庫裡,第二個寫入的人會被卡住等第一個寫完,或乾脆直接失敗、強迫使用者重試。但多主的情況是:兩邊的寫入都「成功」了,衝突要等到事後非同步同步時才會被發現。
有些資料儲存系統走另一條路:乾脆放棄「領導者」這個角色,讓任何一個副本都能直接接受客戶端的寫入。
單主複製會「當場」擋下衝突(卡住或直接拒絕);多主複製卻讓兩邊都「先成功再說」,事後才發現打架——這正是多主複製好用又危險的根源。
多個班長,換來三個好處
把「唯一領導者」換成「每個資料中心都有自己的領導者」,馬上換來三個實打實的好處:
客戶端把寫入送給「離自己最近」的領導者,不必千里迢迢跨海寫入,回應速度大幅提升。
某個資料中心整個掛掉,其他資料中心的領導者依然能繼續接受寫入,不會整個系統停擺。
多主複製通常靠非同步複製, 跨資料中心的連線暫時斷掉,本地寫入照樣正常,等網路恢復再補同步。
每家分店都能自己接單、自己烤披薩(都是領導者)。台北顧客不用把訂單送到美國總部再繞回來;舊金山分店停電,台北、倫敦照樣營業;台北跟倫敦網路不穩,兩邊也還是能各自服務本地客人,事後再互相同步訂單。
三種拓樸,點點看:寫入怎麼走、誰會跟誰打架
把「班長制度」攤開來看,其實有三種完全不同的安排。點下面每個元件,看看在這種拓樸下,寫入路徑長什麼樣、又是誰可能跟誰起衝突。
不管是多主還是無主,只要「不只一個地方能寫」,就一定要面對寫入衝突——差別只在於用什麼機制去發現它、解決它。
動手配配看:衝突發生後,該用哪招收拾?
多主複製最大的麻煩就是寫入衝突。書裡整理了幾種常見解法,把它們拖到最貼切的情境描述上。
最後寫入者獲勝(LWW) 聽起來公平,其實暗藏地雷:如果兩台裝置的時鐘沒對齊,「真正比較晚做的修改」反而可能因為時間戳比較舊而被覆蓋掉——使用者根本不會知道自己的修改憑空消失了。
沒有班長,怎麼知道大家有沒有更新到位?
無主複製乾脆不設領導者,任何副本都能直接接受寫入。但這樣要怎麼確保讀到的是最新資料?答案是靠一組數字說話:
這筆資料總共存了幾份。
要幾個副本回報「我寫好了」,才算寫入成功。
要問幾個副本,才拿去比對版本、回傳最新的那份。
當w + r > n時,有個很神奇的保證:任何一次成功的寫入,跟任何一次讀取,它們各自涉及的副本集合「一定會有重疊」——所以你讀到的裡面,至少有一份是包含最新寫入的。
社區裡每個人手上都有一本食譜書(副本)。你新增一道菜時,不必等全部人都確認,只要 w 個朋友回報「我抄進去了」就算發佈成功;查食譜時你問 r 個朋友,比較誰的版本比較新。只要「發佈的那群人」和「查詢的那群人」加起來一定會有交集(w + r > n),你就總能問到擁有最新食譜的人。要是問到拿舊版本的朋友,你還會順手更新給他——這就是 讀取修復。
副本暫時不在家,寫入就失敗嗎?
如果某個副本剛好斷線或當機,無主系統不會因此拒絕寫入。它靠兩個機制撐住可用性:
原本該收資料的副本連不上,就先找「附近可用」的鄰居代收,只要湊滿 w 個確認,一樣算寫入成功。
等原本離線的副本恢復上線,剛剛代收資料的鄰居會把資料連同「這是要給誰的」提示一起交還回去。
物流大哥要把包裹送到某個圖書角,結果它鐵門深鎖。物流大哥就先交給隔壁有開門的鄰居代收,並悄悄留言「這是給隔壁的,它開門了幫我轉交」。等原本那間重新開門,鄰居就把包裹還過去——寫入不會因為一個節點暫時不在家就失敗。
小試身手
這一站的內容不少,來兩題檢查一下有沒有抓對重點:
多主、無主都可能讓兩筆寫入「同時」發生,要怎麼判斷誰先誰後、甚至兩者根本互不相干?往下捲,我們要正式拆解「並發」這件事。
版本向量:偵測與解決衝突
沒有班長盯場的時候,系統怎麼知道兩筆寫入「互不知情」,又怎麼把它們都留住?
誰先誰後,不能只看時間戳
在無領導者複製的世界裡,同一筆資料可能同時被好幾個客戶端修改。如果兩筆修改各自獨立、互不干涉,那沒問題;但如果兩邊剛好動到同一個地方,麻煩就來了——這就是 平行寫入。
你可能直覺會想:「比對時間戳,誰的時間晚就聽誰的」。這正是 最後寫入為準(LWW) 的邏輯,但這招在分散式系統裡很危險——不同機器的時鐘很難完全同步,「誰的時間比較新」常常靠不住。更糟的是,就算時間戳準確,LWW 也只是粗暴覆蓋,完全不管那筆被蓋掉的寫入裡,可能藏著別人還沒看到、也還沒被合併進來的重要修改。
兩位廚師同時喊出不同的烤箱溫度,系統只認「最後聽到的那個」——另一個溫度就這樣不留痕跡地被丟掉了。你不會看到任何錯誤訊息,資料就是安靜地不見了,這才是 LWW 最可怕的地方。
判斷順序的關鍵不是時間,是「誰依賴了誰」
那該怎麼判斷兩筆寫入到底是「有先後」還是「平行」?書裡給出的定義非常精準,我們直接讀原文,旁邊配白話。
如果操作 B 知道操作 A、依賴 A,或是建立在 A 的結果上,我們就說「A 發生在 B 之前」。
簡單說:如果兩個操作互相都不知道對方,也就是誰都沒「發生在」誰之前,那它們就是平行的。
判斷平行與否,重點根本不是物理時間——只要兩邊彼此不知情,不管它們實際上隔了多久,都算平行。
如果一個操作發生在另一個之前,後面那個直接覆蓋前面的就好;但如果兩者是平行的,就出現了需要解決的衝突。
你先發了一篇貼文,朋友看到後留言——留言「知道」貼文的存在,所以貼文發生在前。但如果你和朋友在完全不相關的社群平台上,同時各自發了一篇貼文,彼此都不知道對方在做什麼,那這兩篇貼文就是平行的——沒有誰先誰後,只有「互不知情」。
拆解一次真實的平行寫入:兩支手機、同一台購物車
光說「平行」有點抽象,我們直接跑一次書裡最經典的例子——你和朋友同時打開同一個購物車 App,其中一支手機還離線中。按「下一步」看資料庫怎麼一步步發現這兩筆寫入互不知情,最後又怎麼把它們合併起來,而不是隨便丟掉一筆。
版本向量不是一個數字,而是「每個副本各自改到第幾版」的一張清單,例如 {手機A: 1, 手機B: 1}。寫入時把你讀到的版本號一起帶回去,資料庫就能比對:你的修改「知道」多少歷史。如果知道的比資料庫現在少,那就是平行寫入,不能直接覆蓋。
平行寫入之後:同級版本、合併、墓碑
偵測到平行寫入只是第一步,資料庫接下來要做的不是「選一個」,而是「都留著,交給應用程式處理」。這中間有三個關鍵動作:
帽子和鞋子彼此不知情,資料庫不會二選一,而是把兩筆都存下來,變成同級版本——兩份「都有效」的草稿。
資料庫把同級版本一起回傳,剩下的合併工作交給應用程式:像購物車這種只會累加的情境,最簡單的合併方式就是取聯集——帽子、鞋子都留著。
但如果購物車也能刪除商品,單純取聯集會讓已刪除的商品「復活」。所以刪除不能真的抹掉,而要留一個墓碑標記——合併時看到墓碑,就知道這項目該被視為已刪除,即使另一個版本裡還留著它。
想像你和朋友一起在數位白板上編同一份食譜。你加了「麵粉」、朋友同時加了「雞蛋」,兩人互不知道對方動了手——這就是平行寫入,白板會把兩份修改都留著,等你們一起討論出最終食材清單。如果你決定「這道菜不加鹽」,白板不會直接擦掉,而是留一張「鹽(已刪除)」的小紙條,免得合併時鹽又莫名其妙地跑回來。
實戰協定:讀取要拿版本,寫入要還版本
要讓上面這套機制運作,應用程式跟資料庫之間有一個簡單但關鍵的約定——讀的時候順手拿走版本資訊,寫的時候一定要帶回去。
應用程式讀資料,資料庫連同目前的版本號(或版本向量)一起回傳,就像拿到一份「修改歷史紀錄」。
修改完要存回去時,必須把「剛才讀到的版本」一起送回,等於告訴資料庫:「我這次修改是基於這個舊狀態做的」。
帶回的版本如果被資料庫現有版本完全涵蓋,就安全覆蓋;如果彼此互不涵蓋,就是平行寫入,產生新的同級版本。
在應用程式碼裡手動合併同級版本又累又容易出錯,所以有一類特殊資料結構叫 CRDTs(無衝突可複製資料類型), 專門把集合、計數器這類資料的合併自動化,連刪除的墓碑語意都內建處理好,很適合購物車、協作編輯這種天生會有大量平行修改的場景。
小試身手
版本向量、同級版本、墓碑——這三個概念環環相扣。來兩題檢查一下:
單主、多主、無主,外加各種一致性保證,到底該怎麼選?整章收束一次講清楚。
大局:複製模式選擇地圖
單主、多主、無主——三條路都通往「多台機器同一份資料」,差別在你願意犧牲什麼
先倒帶:這一章到底在解決什麼問題?
這一章從頭到尾都在回答同一個問題:把同一份資料放到複製(replication)到好幾台機器上,聽起來很單純,一旦資料會「變動」,就立刻變成一道難題——變動要怎麼有秩序地傳播出去?
你已經跟著我們走過三種做法:只有一個節點能寫的單主複製、允許好幾個節點都能寫的多主複製、以及完全沒有固定領導者的無主複製。這一站不再逐節細講,而是把它們攤開放在同一張地圖上,讓你看清楚——選哪一種,其實是在回答「我最不能忍受哪種犧牲」。
還記得烹飪俱樂部的比喻嗎?單主是「總主廚一人說了算」;多主是「各地分區主廚各自開發特色菜,定期交換心得」;無主是「一群美食部落客互相打聽,問夠多人就能拼出最新版本」。同一本食譜,三種完全不同的治理方式。
直接讀原文:作者怎麼定義這整章
這段話出現在本章一開頭,等於是作者親口寫下的「本章路線圖」——先講複製是什麼,再列出接下來要教的三條路。左邊原文、右邊白話,一句對一句。
複製,意思是把同一份資料,透過網路,保存在好幾台機器上。
如果你要複製的資料永遠不會變,那複製很簡單——每台機器複製一次就完工了。
複製真正的難處,全都出在「資料會變動」之後怎麼處理,而這正是這一章要講的。
我們會講三種主流做法:單主複製、多主複製、無主複製。
幾乎所有分散式資料庫,用的都是這三種方式之一。
「複製的難處全在資料會變動之後」——資料不變,複製是體力活;資料一直變,複製才變成需要設計的問題。三種模式,就是三種不同的「誰有權決定變動順序」的答案。
三條路,三種脾氣
快速複習一輪,每種模式一張卡:它怎麼運作、換來什麼、犧牲什麼。
一個領導者收所有寫入,其餘追隨者照順序複製、負責讀取。架構最簡單、資料最一致,但領導者是單點瓶頸,故障轉移期間可能有停頓或短暫資料遺失。
好幾個節點都能收寫入,彼此再非同步同步。換來「就近寫入」的低延遲、和「一地斷線不影響其他地」的高可用,但代價是要正面處理寫入衝突。
沒有領導者,寫入直接送給多個節點、讀取也問多個節點比對版本,靠法定人數(quorum)機制撐住新鮮度。韌性最強、服務最難被打斷,但只保證最終一致性,讀到的可能不是最新版。
三種模式的關鍵差異,其實是同一道拉鋸反覆出現:想要更強的一致性,就要犧牲一點可用性或延遲;想要更高的可用性,就要接受資料短暫不同步。選型的第一步,是先誠實回答「我最怕哪種代價」。
點點看:每種模式的「一致性防線」擺在哪?
三種模式其實是把「誰先看到最新資料」這件事,安排在流程的不同位置。點下面每個角色,看它在各模式中扮演的防線。
動手選型:把應用情境拖到對的複製模式
這是本章壓軸的整合練習。下面四種常見的應用情境,各自最適合哪種複製模式(或它的變形)?拖拖看,拖完按「對答案」。
先問「寫入能不能容忍多個入口」——不能,就走單主;能,且是為了跨地低延遲或離線可用,就走多主或無主。再問「一致性要多硬」——金融交易這種絕不能丟資料的,寧可犧牲一點延遲也要同步確認;單純讀多寫少的網站,非同步單主的簡單架構就夠了。
整章總複習
來兩題貫穿全章的題目,檢查你是不是真的把三種模式串起來了。
這一章解決的是「一份資料,複製到多台機器」——為了容錯、就近服務、分攤讀取。但如果資料量大到一台機器根本裝不下呢?下一站要處理的,就是把資料「切開」分裝到不同機器上的難題。