一致性的幻覺:線性一致性
為什麼多副本的資料庫,能讓你「感覺」它只有一份資料——以及這個假象什麼時候會被拆穿
同時問兩個副本同一個問題,卻拿到兩個答案
在會「最終一致」的資料庫裡,如果你同時問兩個不同的副本同一個問題,可能會得到兩個不同的答案。這很令人困惑——如果資料庫能營造出「其實只有一份資料」的假象,那不是簡單多了嗎?
這正是 線性一致性(linearizability) 的核心想法。它的基本概念很簡單:讓系統表現得好像只有一份資料副本,而且對它的所有操作都是原子的。
一旦某個客戶端成功完成一次寫入,所有正在讀取的客戶端都必須能看到剛寫入的值。
你讀到的值一定是最新、最即時的值,不會來自過期的快取或落後的副本——這就是 新鮮度保證(recency guarantee)。
一旦有任何一次讀取回傳了新值,之後所有的讀取(不論在哪個客戶端)都必須也回傳新值,不能再倒退回舊值。
想像全公司只有一張公告欄。一旦有人把新公告貼上去,任何之後來看的人都只會看到新公告,不可能看到被蓋掉的舊紙條。多副本資料庫背後其實有很多張紙,但線性一致性讓它看起來就像只有這一張公告欄。
直接讀原文:世界盃比分為什麼讓球迷一頭霧水
書裡用一個真實網站的例子,帶出「不是線性一致」到底長什麼樣子。一句對一句,左邊原文、右邊白話。
如果資料庫能營造出「其實只有一份副本(也就是只有一份資料)」的假象,那不是簡單多了嗎?
在一個線性一致的系統裡,只要有一個客戶端成功完成寫入,所有正在讀取這個資料庫的客戶端,都必須能看到剛剛寫進去的值。
要維持「只有一份資料」的假象,就得保證讀到的值是最新、最即時的,不能來自過期的快取或落後的副本。
換句話說,線性一致性是一種新鮮度保證。
Bob 知道自己是在聽到 Alice 喊出比分之後才按下重新整理的,所以他預期自己查到的結果,至少要跟 Alice 看到的一樣新。
如果 Alice 和 Bob 同時重新整理,拿到不同結果反而不奇怪——他們根本不知道對方的請求何時被伺服器處理。但 Bob 的動作明明發生在 Alice 之後,卻看到更舊的世界。這正是違反線性一致性的關鍵:後發生的操作,不該看到比先發生的操作更舊的狀態。
互動重播:手機、電視、還有那個「幻覺」
下面用世界盃比分的經典場景,演給你看「沒有線性一致性」會多讓人困惑,然後看線性一致性怎麼把這個漏洞補起來。按「下一步」往下播。
Bob 看到舊比分,不是因為系統沒有複製資料,恰恰是因為有多個副本、而其中一個落後了。線性一致性要解決的,正是這種「同一時刻不同地方看到不同事實」的一頭霧水。
用「暫存器」想清楚:並行讀取能回傳什麼?
分散式系統文獻把單一個鍵叫做 暫存器(register) ,它可以是 key-value store 的一個鍵,也可以是關聯式資料庫的一列。假設 x 一開始是 0,某客戶端正在把它寫成 1:
一定得回傳舊值 0——這時候新值根本還沒發生。
一定得回傳新值 1——這時候寫入已經生效了。
0 或 1 都合法——我們想像在寫入期間有個瞬間,值原子地從 0 翻成 1。
這也是它常被搞混的地方:線性一致性很容易跟 可序列化(serializability) 搞混,因為兩個詞看起來都像「可以排成某種順序」。但可序列化管的是多筆交易要不要對得起帳,它不要求順序等於真實時間;線性一致性管的是單一物件讀寫夠不夠新鮮,而且死死綁住真實時間的先後。兩者都要的組合,書中叫 嚴格可序列化(strict serializability)。
可序列化像超市結帳:只要帳目能排成某個合理順序就好,誰先誰後不重要。線性一致性像看同一場現場直播:只有一個畫面,一旦比分更新,所有在那之後看的人都必須看到新比分——真實時間的先後才是重點。
看比分過期沒關係,但有些事真的不能錯
看球賽比分晚個幾秒沒什麼大不了,但書中舉了幾類「少了線性一致性就會出事」的情境:
單主複製必須確保只有一個領導者,否則就會 腦裂(split brain) 。不論鎖怎麼實作,它都必須是線性一致的:所有節點必須對「誰持有鎖」有一致的看法。
帳號名稱、電子郵件都要求唯一。兩人同時搶同一個名稱,必須剛好只有一人成功——這需要 線性一致性 。
系統裡有多個溝通通道時,缺乏新鮮度保證會造成競態條件——像照片先寫進檔案儲存,縮圖指令再丟進訊息佇列,兩條通道賽跑。
一場演唱會只剩最後一個座位,兩位顧客同時點擊「訂位」。系統必須保證只有一個人訂到,另一個人收到「已售完」——這就像帳號註冊:用原子比較並設定,把名稱設成那位使用者的 ID,前提是它還沒被佔走。
但不是所有約束都得這麼硬:硬性唯一約束(如關聯式資料庫的 unique)需要線性一致性;可寬鬆對待的約束則不一定——例如機位超賣時,可以事後把客人改到別班機並給補償。外鍵、屬性約束等其他約束,通常不需要線性一致性也能實作。像 ZooKeeper、etcd 這類 協調服務 ,就是靠共識演算法以容錯方式提供這種線性一致操作。
動手配配看:這些情境,需要線性一致性嗎?
把下面幾種情境拖到正確的分類。配完按「對答案」。
線性一致性的讀取就像每次都喝到現榨的果汁,保證最新鮮;最終一致性則像冰箱裡放了幾天、可能已經不新鮮的果汁——雖然最後會換新,但你不知道何時。動態時間軸可以喝「冰箱果汁」,搶票系統不行。
小試身手
把「什麼是線性一致性」跟「什麼時候真的需要它」再檢查一次,來兩題。
線性一致性聽起來像萬靈丹,但它跟系統的可用性、跟網路分區之間,藏著一個沒辦法兩者兼得的取捨。往下捲,我們去看看這筆帳到底要怎麼算。
線性一致性的代價
強一致性聽起來很美好——但天下沒有白吃的午餐,這一站我們把帳單翻出來看清楚
只用一份資料,最簡單也最脆弱
上一站我們認識了 線性一致性 ——系統表現得「像只有一份資料」。最簡單的做法,就是真的只留一份。但這樣完全無法容錯:那台節點一掛,資料就沒了,或至少暫時拿不到。
所以實務上得靠 複製 來容錯。但複製一多,「線性一致」就不是預設值了——四種主流複製方法,命運完全不同:
從領導者或同步追隨者讀,有潛力線性一致——但用了快照隔離、有並行錯誤,或誤把舊領導者當領導者,就會破功。
ZooKeeper、etcd 這類系統內建防止腦裂與過期副本的機制,能安全提供線性一致儲存。
多個節點並行處理寫入、再非同步交換,會產生衝突,不是線性一致。
基於時鐘的「最後寫入勝出」,大概不是線性一致——時鐘會偏移,靠不住。
公司只有一位官方發言人(領導者),所有對外消息先經過他,再轉達給助理(追隨者)。你直接問發言人、或問已同步過的助理,答案就是最新的。但如果助理還沒同步、你卻去問他,就可能拿到舊消息——這就是為什麼不是「每一個」單主資料庫都線性一致。而多主複製像每個分公司都有自己的發言人,各自接消息、事後才互相同步,結果可能互相矛盾,需要事後解衝突——沒有單一份真相,本質上就無法線性一致。
常有人以為在無主系統裡,只要滿足
法定人數
w + r > n 就穩了。但書中給了反例:寫者把 x 從 0 改成 1、送往三個副本(n=3, w=3);客戶端 A 讀兩個節點看到新值 1;客戶端 B 在 A 完成之後才開始讀、卻讀到另兩個節點,拿回舊值 0。法定人數條件明明滿足,B 卻比 A 看到更舊的值——順序就這樣亂了。要補救,讀者得同步做讀取修復、寫者得先讀過法定節點才寫,但這要付出效能代價,而且比較並設定(cas)這種操作靠這招也做不到,它需要共識演算法才行。
直接讀原文:CAP 定理到底在講什麼
兩個資料中心之間網路斷了,會發生什麼事?書裡用這個場景,把後來被稱為 CAP 定理的洞見講得很清楚。
如果你的應用需要線性一致性,而部分副本因網路問題連不到其他副本,那斷線的那些副本就不能處理請求——它們只能等網路修好,或直接回錯誤,兩種結果都是「變得不可用」。
如果你的應用不需要線性一致性,就可以讓每個副本各自獨立處理請求,即使跟其他副本斷線也照樣運作。這時應用在網路故障下能保持可用,但行為就不是線性一致的了。
所以,不需要線性一致性的應用,能更耐得住網路問題。這個洞見就是廣為人知的 CAP 定理,由 Eric Brewer 在 2000 年命名。
CAP 有時被講成「一致性、可用性、分區容忍,三選二」。可惜這種講法很誤導人,因為網路分區是一種故障,不是你能選擇要不要的東西——它就是會發生,不管你喜不喜歡。
更貼切的講法應該是:分區發生時,你只能二選一——保一致,或保可用。
連鎖店總部(領導者)與分店之間的電話線斷了。堅持一致的分店規定「沒問過總部不准賣限量商品」——電話一斷,就只能停賣那項業務,保住一致、犧牲可用。堅持可用的分店說「先賣再說,等通了再對帳」——照常營業,但可能兩家分店把同一件限量品都賣掉了,保住可用、犧牲一致。最關鍵的體悟是:電話線會不會斷,不是你能選的——它就是會斷。
點點看:CAP 三角的三個角
把 CAP 定理的三個字拆開,點下面三個角,看每一個到底在講什麼白話意思。
很多人把 CAP 講成「C、A、P 三個裡面選兩個」,聽起來 P 好像是可以放棄的選項——這是誤導。P(網路分區)不是你能選擇要不要的選項——分區會不會發生,不是系統設計者能決定的事,你唯一能選的,是分區真的發生時,要犧牲 C 還是犧牲 A。網路正常時,系統可以同時擁有一致性與可用性;只有分區真的發生的那一刻,才被迫二選一。更貼切的講法是「分區時:保一致還是保可用(Consistent or Available when Partitioned)」。
而且 CAP 的正式定義範圍其實很窄:只考慮一種一致性模型(線性一致)和一種故障(網路分區),對網路延遲、節點當機這些其他重要的取捨隻字不提。「可用性」本身的定義也有多種矛盾說法。所以書裡的結論很直接——CAP 雖然有歷史影響力,對實際設計系統的幫助卻很有限,最好避免用它來下結論。
為什麼線性一致性天生就慢?
更讓人意外的是:實務上很少有系統真的是線性一致的——就連現代多核 CPU 的 RAM 都不是!如果一個核心寫入某個記憶體位址,另一個核心隨後讀取同一位址,並不保證讀到剛才寫入的值(除非用了 記憶體屏障 )。
每個 CPU 核心都有自己的 快取 ,像員工桌上的便利貼——查便利貼很快,但可能不是最新的。員工改了數字,先改自己的便利貼,晚點才更新總倉的白板(主記憶體),於是同一個數字在不同人桌上、白板上可能都不一樣。
若要保證線性一致,每次讀寫都得親自跑一趟總倉的白板確認最新值——這當然準,但慢得多。而且這個慢是隨時都慢,不只是網路出問題的時候才慢。
放棄線性一致性的真正理由是效能,不是容錯——用 CAP 定理去解釋多核記憶體模型根本說不通:一台電腦內部我們假設通訊可靠,也不會期待一個核心斷線了還能繼續正常運作。下面這個流程,說明「線性一致的讀寫」為什麼比較弱一致性的讀寫慢:
Attiya 與 Welch 證明了:若你要線性一致,讀寫請求的回應時間至少正比於網路延遲的不確定性。沒有更快的線性一致演算法存在,但較弱的一致性模型可以快很多。系統對延遲敏感(如即時互動)、或要跨地理區域部署時,這個取捨格外重要——若業務能接受較弱一致性,往往能換來顯著的效能提升。
小試身手
從複製方法的取捨、到 CAP 的正確理解,來檢查一下有沒有抓對重點。
線性一致性又慢又貴,很多系統乾脆不追求它。但完全不管順序也不行——那至少要保證什麼?答案是:順序,尤其是有因果關係的順序,不能亂。往下捲,看看「因果一致性」怎麼用更便宜的方式,守住這條底線。
順序的保證:因果關係與全序廣播
先搞懂「誰先誰後」為什麼重要,再看時間戳與廣播怎麼把順序「釘死」
順序為什麼一再出現:因為要保留「因果」
這一整本書反覆繞回「順序」這個主題,其中一個核心原因是:順序有助於保留 因果關係(causality)。 想想幾個你早就知道的常識:問題一定先於答案(回答的人一定先看過問題);一列資料必須先被建立,才能被更新;訊息必須先被送出,才能被接收。
對任兩個操作 A 與 B,其實只有三種可能:A 發生在 B 之前、B 發生在 A 之前,或者 A 與 B 是 並行(concurrent) 的——彼此互不知道對方。這就是 happened-before 關係, 也是因果的另一種說法:若 A 發生在 B 之前,代表 B 可能知道 A、建立於 A 之上、或依賴 A。
B 可能知道 A、建立於 A 之上,或依賴 A——兩者之間有因果連結。
方向反過來,一樣有因果連結,只是誰是因、誰是果對調了。
沒有因果連結——我們能確定的是,兩邊都不知道對方的存在。
用過 Git 就懂:一個 commit 常常接在另一個之後,排成一直線;但有時會出現分支(多人同時開發),之後再合併起來。這正是 部分順序(partial order) 的樣子——不是每兩個事件都能比出先後,正如集合 {a, b} 和 {b, c} 誰也不是誰的子集。相對地,自然數是 全序(total order) ——5 和 13 你一定說得出誰大。線性一致性給的是全序(單一時間線);因果關係給的是部分順序(並行事件無法比較,就像 Git 的並行分支)。
若系統遵守因果關係施加的順序,就說它是 因果一致(causally consistent) 的——例如快照隔離就提供因果一致:讀到某筆資料時,也必能讀到因果上先於它的資料。而線性一致性蘊含因果關係:任何線性一致的系統都會自動、正確地保留因果,這正是它好理解的原因。但好消息是,線性一致性不是保留因果的唯一辦法——因果一致是「不會被網路延遲拖慢、且分區時仍可用」的最強一致性模型,CAP 定理對它並不適用。
直接讀原文:happened-before 與因果
這一段是本節最關鍵的定義,我們把它原文放左邊,白話放右邊,一句對一句讀。
如果你有兩個操作 A 和 B,只有三種可能:A 發生在 B 之前、B 發生在 A 之前,或者 A 與 B 是並行的。
這個 happened-before 關係,其實是因果的另一種說法:若 A 先於 B,代表 B 可能知道 A、建立於 A 之上、或依賴 A。
如果 A 與 B 並行,兩者之間就沒有因果連結——換句話說,我們確定雙方都不知道對方存在。
交易讀到的是一個「一致的快照」——這裡的一致指的是與因果一致:如果快照裡有一個答案,它也必須包含那個被回答的問題。
想像兩個團隊各自為自己的工作編號:A 隊只用奇數、B 隊只用偶數。問題是兩隊的速度不同,A 隊的奇數計數器可能遠遠領先 B 隊的偶數計數器——拿一個奇數工作和一個偶數工作比,你根本說不準誰先發生。這正是「非因果的序號產生器」的毛病:奇偶數分配、附上實體時鐘戳記、預先配置號碼區塊,這幾種常見做法產生的序號,統統都不與因果一致。
動手看:Lamport 時間戳怎麼「見面就對錶」
追蹤每一條因果依賴太貴了——更好的辦法是用 邏輯時鐘(logical clock) 替每個操作編號。 Lamport 時間戳 的妙處就在一條規則:每個節點都記住自己見過的最大計數器值,並把它附在每次請求上;一旦收到的值比自己大,就立刻把自己的計數器跳到那個值。三個節點各自的實體時鐘完全不同步也沒關係——按「下一步」看它怎麼運作。
Lamport 時間戳長相很簡單:是一對 (計數器, 節點 ID)——先比計數器,計數器相同就比節點 ID,保證每個時間戳都唯一。
Lamport 時間戳只負責強制出一條全序,卻分辨不出兩個操作到底是並行、還是有因果關係——它只是硬把它們排成一條線。想同時看出「誰跟誰並行」,得用 版本向量(version vector) ,代價是它比 Lamport 時間戳大、記的東西更多。
Lamport 時間戳留下的缺口
光有一個「與因果一致的全序」,還不足以解決像帳號名稱唯一這類問題:因為你不知道這個全序何時定案。要實作這種約束,你必須確定沒有別的節點能在全序中搶到你前面——而 Lamport 時間戳給不了這個保證,因為隨時可能冒出一個更小的時間戳,事後插隊到你前面。
這個「知道全序何時定案」的概念,就是 全序廣播(total order broadcast) ,又稱原子廣播。
沒有訊息遺失:只要一則訊息傳給了一個節點,就一定會傳給所有節點。網路中斷時訊息暫時送不出去,但修好後仍要補送。
訊息以完全相同的順序,傳遞給每一個節點——即使節點或網路出錯,這個順序保證也必須永遠成立。
想像一個所有節點都收聽的廣播電台,主持人依序唸出一條條訊息:可靠傳遞是「只要唸出來,所有聽眾都會聽到,不會有人漏掉」;全序傳遞是「每位聽眾聽到的順序完全一樣」。關鍵在於,順序在訊息傳遞時就固定了——主持人不能事後把一條訊息硬塞到已經播出訊息的前面。這正是全序廣播比時間戳排序更強大的地方:時間戳排序可能事後才發現有更早的操作要插進來,但廣播一旦播出,就不能反悔。換個角度看,全序廣播就像在附加寫入一份共用日誌:傳遞一則訊息,等於在日誌尾端加一筆,所有節點讀同一份日誌,就看到同一串訊息序列。
點點看:全序廣播能拿來做什麼
全序廣播不是紙上談兵的抽象概念——它是好幾個重量級機制的地基。點下面每張卡片看它具體撐起了什麼。
線性一致的比較並設定(或遞增並取值)暫存器、全序廣播,三者其實都等價於共識——解開其中一個,就等於解開了其他兩個。單主複製其實就是把所有寫入丟給領導者、在單一 CPU 核心上排序;全序廣播要解決的,正是如何擴展吞吐量、以及怎麼處理領導者故障切換。
動手配配看:這幾種順序機制,各解決什麼問題?
把下面幾個機制,拖到它真正解決的問題上。配完按「對答案」。
小試身手
因果、Lamport 時間戳、全序廣播——三個環環相扣的概念,來檢查一下有沒有串起來。
下一站:把順序講清楚之後,回到最現實的問題——多台機器要怎麼「一起」完成一個交易,或者更根本地,怎麼在誰都可能故障的情況下,對一件事達成共識?往下捲。
分散式交易與共識:全章總結
當一筆交易跨越好幾台機器,怎麼讓大家「說一不二」——兩階段提交的完美與破綻,以及容錯共識如何補上那個洞
單機很簡單,跨機器就麻煩了
在單一節點上,交易的原子性靠儲存引擎就能搞定:先把資料寫進磁碟,再附加一筆提交記錄——寫下那筆記錄的瞬間,就是決定提交或中止的關鍵時刻。是「單一裝置」讓提交成為原子動作。
但如果一筆交易得同時碰觸好幾個節點呢?各自獨立提交,看似省事,其實藏著地雷:有些節點可能偵測到衝突要中止,有些卻順利提交;有些提交請求在網路裡遺失、逾時中止,有些卻送達了;有些節點在寫完提交記錄前就當機回滾,有些卻成功了。
一旦有些節點提交、有些中止,它們就互相不一致了。
已提交的交易資料會被其他交易讀到、依賴——不能事後反悔。
一個節點只有在確定所有其他節點也會提交時,才能提交。
交易一旦提交,資料就對其他交易可見,別人會開始依賴它——這正是「讀已提交」隔離等級的基礎。如果提交後還能反悔,所有依賴這筆資料的後續交易都得跟著回滾,代價會一路擴散下去。
直接讀原文:什麼是兩階段提交
先看書怎麼定義這個解決跨節點原子提交問題最常見的演算法——兩階段提交(2PC)。
兩階段提交,是一套讓跨多個節點的交易達成原子提交的演算法。
2PC 引入一個單節點交易沒有的新角色:協調者,又叫交易管理員。
我們把這些資料庫節點叫做這筆交易的「參與者」。
應用準備提交時,協調者啟動第一階段:向每個節點送出「準備好了嗎」的請求,問它們能不能提交。
如果所有參與者都回答「可以」,協調者就在第二階段送出真正的提交請求;只要有任何一個參與者說「不行」,協調者就對所有節點送出中止請求。
牧師(協調者)先分別問新郎與新娘(參與者):「你願意嗎?」——這是準備階段。收到雙方的「我願意」後,牧師才宣布兩人結為夫妻——交易提交,昭告全場。說「我願意」之前你還能喊停;說出口之後,就不能反悔了。就算你說完「我願意」後昏倒、沒聽到牧師宣布,你還是結婚了——醒來後可以查詢全域交易 ID 問結果,或等牧師重試。
跑一遍 2PC:兩個「不歸點」與一個致命弱點
按「下一步」,看協調者如何指揮參與者完成一次提交——並看見那個讓 2PC 惡名昭彰的破綻:協調者一旦在關鍵時刻消失,會發生什麼事。
參與者投下「可以」之後、還沒收到最終結果,這個狀態叫懷疑狀態(in doubt)——就像你說完「我願意」,但牧師突然昏倒,沒人宣布結果,你只能站在原地等他醒來。真正的痛不是等待本身,而是等待期間鎖一直握著不放:其他交易碰不到那些列,甚至連讀都可能被擋,整個應用大片功能卡死,直到協調者恢復、或管理員手動介入。
協調者,其實是系統裡最脆弱的單點
書中有一句話點破 2PC 最根本的體質問題:
它儲存著交易結果,必須像對待任何重要資料庫一樣謹慎對待——因為它一旦壞掉,後果不是「這筆交易失敗」,而是「所有卡在懷疑狀態的交易,鎖永遠握著」。
許多協調者實作預設沒有高可用,它一掛,其他應用伺服器就卡在懷疑交易的鎖上動彈不得。
協調者日誌成了關鍵持久狀態,原本無狀態、可以隨意重啟的應用伺服器,被迫變得有狀態。
2PC 要所有參與者都回應才能提交,任一環節壞掉交易就失敗——這跟「容錯」的目標恰恰相反。
實務上真的會發生:協調者日誌遺失或損毀,這些交易永遠卡著、握著鎖,唯一出路是管理員手動裁決,或動用 XA 的緊急逃生口「啟發式決策」——其實是「可能破壞原子性」的委婉說法。
共識登場:用「多數決」取代「單一協調者」
2PC 的痛點很清楚:它依賴一個不經選舉、單點故障的協調者,而且要求全體參與者同意。容錯共識(fault-tolerant consensus)演算法——像 Raft、Paxos 這一系——正是為了解決這個結構性弱點而生。
沒有兩個節點決定不同的值、沒有節點決定兩次、決定的值必須是某節點提議過的——這三項是「安全性質」,就算大多數節點故障也始終維持,不會做出錯誤決定。
沒當機的節點最終都會決定某值——這是唯一的「活躍性質」,正式化了容錯:即使有節點故障,其他節點仍須做出決定,不能無限期卡住。
共識只需要多數節點(quorum)正常運作即可繼續前進——要容忍 1 個故障,至少要 3 個節點;要容忍 2 個故障,至少要 5 個節點。
共識的協調者(領導者)是選舉產生的,2PC 的協調者不是;共識只需要多數投票,2PC 要每一個參與者都說 yes。共識還有恢復程序——新領導者選出後,能讓節點回到一致狀態,不會像 2PC 那樣永遠卡死。這一切靠一個叫時代編號(epoch number)的機制保證:每個時代內,領導者唯一。
共識即服務:ZooKeeper 在真實系統裡扮演什麼角色
ZooKeeper、etcd 常被說成「分散式 key-value 儲存」,但它們的核心其實是把共識包裝成一個大家都能用的協調服務——用容錯的全序廣播,把少量、能放進記憶體的協調資料一致地複製到所有節點。作為應用開發者,你很少直接用它,而是透過 HBase、Kafka、Hadoop YARN 這類專案間接依賴它。
ZooKeeper 通常只跑在 3 或 5 個固定節點上做多數投票,卻能服務大量客戶端——等於把協調工作「外包」給外部服務。它存的是慢速變動的資料,像「10.1.1.23 是分區 7 的領導者」,可能幾分鐘到幾小時才變一次;完全不適合存每秒變動上百萬次的應用執行狀態。
小試身手,然後回頭看看這三章走了多遠
先檢查兩題,再一起把第 7 到 9 章串成一條線。
第 7 章的交易,保護的是單機上的並行——多個操作同時發生,怎麼不互相踩到腳。第 8 章列出了分散式世界會出錯的所有方式:網路會延遲、會丟包、會分區;時鐘會偏移、會跳動;節點會停頓、會半死不活地假裝正常。第 9 章的答案是——即使如此,我們還是能讓多個節點對同一件事達成一致:2PC 是第一個嘗試,容錯共識則是更穩固的解法,而 ZooKeeper 這類協調服務,把共識包裝成了大家隨手可用的基礎設施。
第 7–9 章到此收尾:從單機交易的並行陷阱,到分散式系統的種種不可靠,再到共識如何在不可靠的基礎上立起可靠的保證。下一章,我們把視角切換到另一個世界——批次處理,看看整套資料系統除了「即時服務請求」之外,還怎麼消化海量資料。