01

總覽與定位

如何讓一群節點對「一個單一且有序的決策序列」達成一致——這是本篇唯一的主軸。

多台機器各自接受寫入,狀態必然分叉。要讓它們表現得像「一台機器」,系統需要一份所有存活節點都同意、且一旦回覆客戶端成功就不會被回捲的 log。Raft 把這件事做成可實作的協定:同一個 term 最多一個 Leader 負責編號與複製,Follower 只附加、不改寫歷史;多數派持久化之後,那筆決策才算 committed,才能安全地套用到狀態機、回覆客戶端。

資料複寫基礎講的是主從拓撲與同步/異步取捨;進階複寫與一致性講的是讀到什麼(R + W > N、Session Guarantees、Version Vector)。那些處理「多個副本上的值如何收斂」。本篇處理的是協定如何決定唯一順序——log 裡第 i 筆是什麼、何時不能再改。產品化之後對外的 Watch、Ephemeral Node、分散式鎖,交給 協調服務與分散式鎖;每個 shard 一個 Raft group、跨 shard 交易與 TrueTime,是 Distributed SQL 的事,這裡只點出「分片邊界 = 共識組邊界」。

1 · 選舉
Term · Candidate · Majority
Term 是邏輯時鐘。隨機 Election Timeout 拆開同時超時;每個 term 一票,log 不夠新的候選人拿不到票。
2 · 複製
AppendEntries · commitIndex
Leader 編號、附加、用 nextIndex 回溯截斷。多數派持久化後才推進 commitIndex,才能回覆客戶端。
3 · Safety 與腦裂
Log Matching · Fencing
已確認的寫入不可遺失。舊 Leader 在分區另一側仍自認 Leader 時,外部資源只能靠單調遞增的 term 拒絕過期寫入。
座標共識不是「多數派讀寫」的另一種說法

Quorum 讀寫保證「讀到的集合與寫入的集合相交」,所以你看得到最新值——但它不產生一份全序 log,衝突仍可能並存。Raft 的多數派是為了對「下一筆要附加什麼」投票,產出一條不會分叉的決策序列。面試把兩者混成「都是多數派所以一樣」會直接失分。

一句話答法Raft 讓一群節點對單一有序 log 達成一致:Leader 編號、多數派持久化才算提交,已提交的項目在後續 term 不可消失。
02

動機:為什麼多副本會分歧

沒有單一決策者時,每個節點都是合法的寫入入口——分區一來,歷史就拆成兩條。

兩台都接受 SET x 的複本,在網路正常時看起來像同步;一分區,左側寫 x=A、右側寫 x=B,修復後沒有客觀的「誰比較新」。時間戳不可靠(時鐘跳動),「最後寫入獲勝」只是再引入一個衝突規則,不是共識。要讓狀態機確定性重放,所有節點必須先同意同一份指令序列,再各自套用。

各自接寫
無共識 · 兩條歷史
  • 分區兩側都認為自己可寫
  • 修復時不知該丟哪一邊
  • 客戶端已拿到的成功回覆可能被回捲
單一 replicated log
一條歷史 · 多數派封印
  • 同一時間只有一個 Leader 編號
  • 多數派落盤後才回覆成功
  • 少數派上未提交的尾巴可以被截斷

兩欄的差別不在「有沒有複製」,而在誰有權編號:左邊兩側都能編號,於是同一個 index 可以是兩筆不同的東西;右邊只有一個 Leader 編號,少數派寫下的尾巴因此可以被安全地切掉。

這份 log 有兩個不可妥協的性質:

  • 唯一順序:index i 在所有節點上要麼還沒有、要麼是同一筆(同 term、同指令)。
  • 不可回捲已確認寫入:一旦 Leader 告訴客戶端「成功」,該項目必須出現在之後每一位 Leader 的 log 裡。否則金融入帳、鎖釋放、組態變更都會幽靈消失。
客戶端成功 ⇒ 該項目已在多數派持久化 ⇒ 後續每一任 Leader 的 log 必含此項目

注意箭頭是單向的。反過來不成立:一筆項目就算已經複製到多數派,也未必已經提交、更未必回覆過客戶端。這個縫隙看起來像吹毛求疵,卻是共識協定最容易出錯的地方——第 6 章會用論文的 Figure 8 場景說明它為什麼會吃掉已確認的寫入。

陷阱「選一個主」本身不解分歧

主從複寫的 failover 常靠心跳 + 提升最接近的 Follower。若舊主帶著未同步寫入回來,你要嘛丟棄它們(客戶端以為成功的寫入消失),要嘛合併衝突。Raft 把「誰是主」和「log 夠不夠新才能當主」綁在同一套投票規則裡,就是為了不走到這條歧路。

一句話答法多副本各自接寫必然分叉;共識要的是一份全節點同意、已回覆成功就不可回捲的 log,不是另一套衝突合併規則。
03

三態、Term 與選舉

Follower 待命、Candidate 拉票、Leader 編號。Term 單調增加,用來作廢過期的權力。

1 · 三個角色

狀態 做什麼 何時離開
Follower 接收 AppendEntries(心跳或 log)、對 RequestVote 投票 一段時間沒聽到 Leader → 變 Candidate
Candidate 自增 term、投自己、向其他人拉票 得多數派 → Leader;發現更高 term 或現任 Leader → Follower;超時 → 新 term 再選
Leader 接受客戶端寫入、編號、複製、推進 commitIndex 收到更高 term 的 RPC → 立刻降為 Follower

同一 term 最多一個 Leader。兩個 Leader 同時存活,只可能發生在它們處於不同 term——這正是後面 fencing 要處理的腦裂。

2 · Term 是邏輯時鐘,不是牆鐘

每次選舉開始,候選人把 currentTerm 加一。任何 RPC 若攜帶更大的 term,收件者先更新自己的 term、放棄手上的 Leader/Candidate 身分,再處理請求。這樣過期的 Leader 一旦聽到新世界,權力自動作廢。牆鐘不同步不會讓 term 倒退;它只影響「誰先超時」。

Raft 三態轉換圖:Follower、Candidate、Leader 之間的狀態轉移條件 start Follower 接 AE · 投票 Candidate 自增 term · 拉票 Leader 編號 · 複製 · commit election timeout currentTerm += 1 majority votes ⌊N/2⌋+1 票 發現現任 Leader,或看到更高 term 收到更高 term 的 RPC → 立刻降為 Follower

實線是「往上爬」的轉移,虛線是「被作廢」的轉移。注意兩條虛線都終止於 Follower——Raft 沒有任何一條邊可以從 Leader 直接跳到 Candidate,權力只會先歸零再重選。圖上刻意不畫 Candidate 的自迴圈(split vote 後換新 term 重選),那是下一段的主題。

3 · 隨機 Election Timeout 拆開同時超時

若所有 Follower 用同一個超時、在同一毫秒發現 Leader 靜音,他們會在同一 term 同時變成 Candidate、互不投票,形成票數瓜分(split vote)。Raft 的解法極其工程:每個節點從區間(例如 150–300 ms)抽隨機超時。先響的那位先發 RequestVote,其他人還在等,就會把票投給他。即使偶發瓜分,下一個隨機超時幾乎不會再對齊。

「拆開」到什麼程度才夠?這句口訣其實是一道可以算的題。設區間寬度 W = T_max − T_min、單程網路延遲 D、參與抽籤的存活節點 m 台。最早超時者在 t 發出 RequestVote,訊息在 t + D 抵達其他人;凡是自己的籤落在這段 D 寬的前導區間裡的節點,會在收到拉票之前先自己變成 Candidate——那就是一次瓜分。把「所有人的籤都落在安全側」的機率積出來,會得到一個乾淨的閉式:

P(不分裂) = ( (W − D) / W )m · E[選舉輪數] = 1 / P · E[min 超時] = W / (m + 1)
互動 · 選舉參數掃描 拖動任一參數,機率與時間讀數即時重算
安全側:籤落在這裡會先收到拉票並投票 危險前導段(寬 D):籤落在這裡就是第二個 Candidate 連乘階梯:每多一台存活節點,安全比例就再乘一次

上半部每一條橫條是一台存活節點的抽籤區間(不是時間線):整條寬 W,左端 D 寬的斜紋是危險前導段,未斜紋的比例就是 (W − D) / W。下半部的連乘階梯把「為什麼是 m 次方」畫出來——每多一台一起抽籤的節點,安全比例就再乘一次,最後一根就是 P。兩處申報:E[T] = C / P 是簡化模型(失敗的那一輪也按整輪成本 C 計),且區間被拖成上界小於下界時一律夾成 W = max(1, T_max − T_min)、只剩一台在抽籤時 P 定義為 1。

關鍵心跳間隔必須明顯短於超時下界

Leader 用空的 AppendEntries 當心跳。若心跳週期接近超時,正常抖動也會觸發選舉,集群會抖動切主。實務把心跳設在超時下界的幾分之一,並把超時上界拉到足以吸收 GC 與短暫停滯——但又不能太長,否則故障切換的 SLO 會被超時上限綁死。

一句話答法Term 單調增加以作廢舊 Leader;隨機超時避免同時拉票。先拿到多數派的 Candidate 成為該 term 唯一 Leader。
04

投票規則與 log 新舊

不是誰先舉手誰當選。票只投給「log 至少跟我一樣新」的候選人,這才保得住已提交的歷史。

RequestVote 攜帶候選人的 term、lastLogTerm、lastLogIndex。Follower 答應的條件有兩條,缺一不可:

  • 這個 term 還沒投過(每個 term 一票;已投給別人就拒絕)。
  • 候選人的 log 至少與自己一樣新:先比最後一筆的 term,term 相同再比 index。自己比較新就拒絕——否則一個空白節點靠先超時就能當選,然後用空 log 覆蓋已提交的歷史。
候選人最後一筆 投票者最後一筆 投票?
term 3, index 10 term 2, index 50 投——最後 term 較新,勝過更長但更舊的 log
term 2, index 8 term 2, index 10 拒——同 term 但比較短,投票者比較新
term 2, index 10 term 2, index 10 投——一樣新;若該 term 尚未投票

「新」的第一鍵是最後一筆的 term,不是 log 長度。較新的 term 代表那條歷史已經經歷過更新的選舉,較可能含有已提交項目。

多數派的定義是 ⌊N / 2⌋ + 1。五節點集群要三票(含自己)。候選人同時只會在一個 term 拉票;拿到多數立刻轉 Leader,開始心跳以阻止其他人再選。

這裡要避開一個常見誤讀:投票規則不保證「log 最新的那台一定當選」,它只擋下「舊到拿不出多數票」的候選人。若某台沒有跟上最後那幾筆未提交的項目,卻仍湊得出三票,它照樣合法當選——那段尾巴接著會被截斷。這不違反任何承諾,因為未提交的項目從來沒有回覆過客戶端成功。第 9 節的 log 推進互動可以讓你親手挑一條路走走看:同樣的崩潰,換一個接任者、或讓那條尾巴多複製一台,命運就完全相反。

陷阱「活著的節點過半」不夠

若三個空白新節點組成多數、兩個持有已提交 log 的舊節點在分區另一側,空白多數會選出一個沒有歷史的 Leader——除非投票規則擋下「log 比較舊」的候選人。Safety 不是從「過半機器開著」來的,是從「過半機器開著 且 當選者的 log 夠新」來的。

一句話答法每個 term 一票;候選人的 last log 必須至少與投票者一樣新。這條規則讓已提交項目成為當選的入場券。
05

Log Replication 與 commitIndex

Leader 附加、Follower 對齊、多數派落盤後才推進 commitIndex——這一步之前不能回覆客戶端成功。

1 · AppendEntries 與一致性檢查

Leader 為每位 Follower 維護 nextIndex(下一個要送的位置)與 matchIndex(已知已複製到的最高位置)。RPC 帶著 prevLogIndex / prevLogTerm:Follower 若在該位置對不上,回覆失敗。Leader 把該 Follower 的 nextIndex 往回退,重送更早的項目,直到對上為止。對上之後,Follower 截斷後面衝突的尾巴,再附加 Leader 送來的新項目。未提交的分歧允許被切掉;已提交的前綴因為 Log Matching,本來就不會衝突。

  1. 1
    Client SET x=1 抵達 Leader
    寫入還沒進任何 log。送到 Follower 的請求會被轉介或拒絕——只有 Leader 能編號。
  2. 2
    Leader 本地 append 到 index i
    順序在這一刻決定。此時回覆客戶端成功就是說謊:這筆只存在一台機器上。
  3. 3
    AppendEntries 帶 prevLogIndex / prevLogTerm 送出
    錨點檢查。對不上的 Follower 回失敗,Leader 把它的 nextIndex 往回退再重送,直到對上為止。
  4. 4
    Follower 截斷衝突尾巴、持久化、回 ACK
    落盤才算數——回 ACK 代表「已寫進我的磁碟」,不是「已收到」。
  5. 5
    多數派 ACK → commitIndex 推進
    五節點要三份(含 Leader 自己)。而且該項目的 term 必須等於現任 term——第 6 節解釋這個限制從哪來。
  6. 6
    套用到狀態機 → 回覆客戶端 OK
    到這一步,那筆寫入才真正成為歷史的一部分。

2 · 何時能回覆客戶端

commitIndex 是 Leader 知道「多數派已持久化」的最高 index。只有 commitIndex 推進過的項目,Leader 才套用到狀態機並回覆成功。只寫在 Leader 本地、或只複製到少數派,都還是 uncommitted——Leader 崩潰後可以被下一任截斷。這就是「成功」的契約:不是 Leader 記憶體裡有,是多數派磁碟裡有。

commitIndex ← max( commitIndex, max { i | 項目 i 的 term = 現任 term 且複製數 ≥ ⌊N/2⌋+1 } )

這是一條推進規則,不是絕對定義:commitIndex 只增不減。新 Leader 剛當選、本 term 還沒提交任何項目時,右邊的集合是空的,但它從前任繼承的 commitIndex 不會因此倒退。式子裡的「現任 term」也不是筆誤,第 6 節會解釋為什麼不能單憑計數把前任 term 的項目標成 committed。

關鍵心跳也是複製通道

空的 AppendEntries 重置 Follower 的選舉計時器,同時捎帶 leaderCommit,讓落後的 Follower 把已經在多數派的項目往前套用。Catch-up 不是另開一條協定,就是同一支 RPC 在 nextIndex 回溯之後連續送 log。

一句話答法Leader 編號並用 AppendEntries 對齊 Follower;多數派持久化後才推進 commitIndex,這之前回覆成功等於說謊。
06

Safety:Matching、Completeness、前任 term

兩條不變量保住已確認寫入;一條反直覺規則防止「數人頭」把未封印的歷史提前定稿。

1 · Log Matching

若兩個節點的 log 在某 index 有相同 term,則從開頭到該 index 的所有項目完全相同。AppendEntries 的 prevLog 檢查 + 衝突截斷,讓這條不變量在每一步複製後都成立。它的用途是:Leader 不必重傳整份歷史來證明前綴一致,檢查一個錨點就夠。

2 · Leader Completeness

若一筆項目在某 term 被提交,之後任何 term 的 Leader 都帶有這筆項目。證明骨架:提交需要當時的多數派;下一任要當選也需要一個多數派;兩個多數派必相交,相交的那台持有已提交項目,又因為投票規則拒絕 log 比較舊的候選人,當選者至少跟相交者一樣新——於是已提交項目跟著走。

陷阱新 Leader 不能單憑計數提交前任 term 的 entry

假設 index 2 是 term 1 留下的、尚未提交。新 Leader(term 2)把它複製到多數派之後,仍不可把 commitIndex 推到 2。原因是:在它複製途中,另一場選舉可能讓一個不含這筆項目的節點當選(term 3),該新 Leader 會截斷這筆未封印的歷史。若 term 2 的 Leader 已經對客戶端說成功,寫入就消失了。

Raft 的修補:Leader 只把「本 term 自己附加的項目」靠多數派計數來提交。一旦本 term 有一筆(常見做法是當選後立刻寫一筆 no-op)被提交,Log Matching 會把前面的前綴一起封印。這就是論文 Figure 8 的工程結論,面試常考。

若允許「數人頭」提交前任 term
index 2 的 T2 被複製到 3 / 5 就宣告 committed
階段 1 · term 4:S1 當選並補齊前任的 T2
1
2
3
S1
T1
T2
 
S2
T1
T2
 
S3
T1
T2
 
S4
T1
 
 
S5
T1
T3
 
S1 看到 index 2 已在 S1/S2/S3 三台,誤以為可以提交並回覆客戶端成功。注意 S5 手上的 index 2 是 T3——它在 term 3 當過 Leader,寫了自己的版本。
階段 2 · term 5:S1 崩潰,S5 拉票
1
2
3
S1
T1
T2
 
S2
T1
T3
 
S3
T1
T3
 
S4
T1
T3
 
S5
T1
T3
 
S5 的最後一筆是 (T3, idx2),比 S2/S3 的 (T2, idx2) 更新,投票規則擋不住它。S5 當選後用自己的 T3 蓋掉 S2/S3 的 index 2(S4 原本是空的,屬於新增)——剛才回覆過成功的 T2 憑空消失。S1 手上那份 T2 隨著它崩潰成了孤本,救不回任何人。
Raft 的規則:先提交一筆本 term 的項目
T4 靠多數派提交,Log Matching 把前綴一起封印
階段 1 · term 4:補齊 T2,同時 append 一筆 T4
1
2
3
S1
T1
T2
T4
S2
T1
T2
T4
S3
T1
T2
T4
S4
T1
 
 
S5
T1
T3
 
T2 複製到多數派但不宣告提交。真正靠計數提交的是 index 3 的 T4(本 term 項目,實務上常是當選後立刻寫的 no-op);Log Matching 讓它前面的 T1、T2 一併封印。
階段 2 · term 5:S1 崩潰,S5 同樣拉票
1
2
3
S1
T1
T2
T4
S2
T1
T2
T4
S3
T1
T2
T4
S4
T1
 
 
S5
T1
T3
 
這次 S2/S3 的最後一筆是 (T4, idx3),比 S5 的 (T3, idx2) 新,拒絕投票。S5 只拿得到 S4 與自己共 2 票,選不上。能當選的人必定帶著 T2。

綠色 = 已提交、黃虛線 = 已複製但未提交、斜線底 = 該位置還沒有項目、紅色 = 原本的項目被後續 Leader 覆蓋掉。左欄階段 2 的 S1 已經崩潰,它的 log 停在崩潰當下、不再參與任何決策。兩欄的差別只有一件事:右邊多提交了一筆現任 term 的項目。這就是論文 Figure 8 的全部結論。

一句話答法Log Matching 保證前綴一致,Leader Completeness 保證已提交項目不丟。新 Leader 必須先提交一筆本 term 的項目,不能靠清點前任 term 的複本來宣告成功。
07

成員變更,以及為什麼是 Raft 不是 Paxos

換機器比換 log 更危險。Joint consensus 讓舊集合與新集合同時過半;強 Leader 則是拿彈性換可理解性。

1 · 一次換掉多數派會裂成兩個合法集群

五節點 {A,B,C,D,E} 若直接改成 {C,D,E,F,G},舊多數 {A,B,C} 與新多數 {E,F,G} 可以在過渡期同時選出 Leader——兩份 log、兩份真實。這不是實作 bug,是「新舊多數不相交」的組合後果。

Joint consensus 把變更本身寫進 log,而且規則很反直覺:節點一 append 到 C_old,new 就立刻採用它,不等提交。之後任何決策(含下一場選舉)都必須同時取得舊集合多數 與 新集合多數;兩組多數的交集非空,所以不會出現兩個 Leader。等 C_old,new 被提交,才可以 append C_new;C_new 提交後舊節點才能下線。若改成「提交後才生效」,append 到 commit 之間會留下一段只需舊多數的窗口,Safety 就從那裡漏掉。實務上 etcd / Consul 常把變更限制成一次加一台或減一台,讓每一步的新舊多數自然相交,同等安全、狀態機比較簡單。

關鍵成員變更是 log 項目,不是帶外指令

把「誰有投票權」當成 committed log 的一部分,新 Leader 一當選就知道當前配置。若配置只活在某個運維腳本裡,分區兩側會對「多數派是誰」得出不同答案,Safety 從根上裂開。

2 · Paxos / Multi-Paxos 對照

Paxos(單值) Multi-Paxos Raft
誰可以提案 任何 Proposer 穩定後的 distinguished leader 當選的強 Leader
log 是什麼 一次決定一個值 同一 Leader 連續對多 slot 提案 從第一天就是 replicated log
工程負擔 活鎖、角色交錯難實作 要補齊 Leader 選舉與空洞 slot 角色、RPC、Safety 寫在同一份演算法
彈性 無 Leader 也能推進單值 Leader 失效可換人續寫 無 Leader 期間寫入停住,換可理解性

Raft 並沒有放寬 Safety,它把「先選出強 Leader,再由他編號」做成預設路徑。代價是選舉期間集群不接受寫入;換到的是可教學、可實作、可審查的狀態機。

在 Distributed SQL 裡,這份協定的應用面很窄、但關鍵:每個 shard / range 一個 Raft group。單一分片的線性寫入由該 group 的 Leader 提供;跨分片原子性是另一層交易協定的事,不是把全世界放進同一個 Raft log。

一句話答法成員變更要讓舊新多數相交(joint consensus 或一次一台)。Raft 用強 Leader 換可理解性;Paxos 更彈性,但工程上幾乎都會收成 Multi-Paxos。
08

腦裂與 Fencing Token

舊 Leader 在分區另一側仍自認 Leader。Raft log 是安全的;寫到外部資源則必須另加單調 token。

分區把 N1(舊 Leader, term 1)隔開,N2–N5 選出 N2(term 2)。N1 還沒聽到更高 term,心跳繼續、客戶端若連到它仍會把寫入送進去。對 Raft log 本身這是安全的:N1 湊不齊多數派,commitIndex 不會前進,它不該回覆成功。危險在協定外面:N1 可能對磁碟陣列、物件儲存、或另一個沒有共識的資料庫發出副作用——那些系統不知道 term 已經換人。

只靠 TTL / lease 到期
時鐘與 GC 會說謊
  • 持有者 GC pause、長 IO、行程被凍結,本地以為 lease 還在
  • 對端已經過期並把權力給了新人
  • 醒來後的寫入沒有任何欄位證明「我比較舊」
Fencing Token = 單調 term
被保護資源自己檢查
  • 每次當選,token := term(或 epoch)
  • 資源記住見過的最大 token,拒絕較小者
  • 即使舊 Leader 還活著、lease 還沒在它的牆上到期,寫入也進不去

關鍵差別在誰做檢查:左欄把判斷留在持有者身上(我的 lease 還沒到期),右欄把判斷搬到被保護的資源上(我見過更大的 token)。只有後者不依賴任何一方的時鐘。

accept(write, token) ⟺ token ≥ lastSeenToken;然後 lastSeenToken ← token

這是 協調服務與分散式鎖 那一篇展開的同一條正確性論證:鎖 = lease + fencing token。那篇的鎖競態時間軸互動可以把這段競態一秒一秒攤開——GC pause 多長、keepalive 多久一次、租約何時過期、危險窗有多寬,都是算出來的;本篇不重複那個模擬。token 的定義是由共識集群分配、單調遞增、客戶端無法偽造的世代號——在 Raft 裡它就是 term;換成協調服務,會是 ZooKeeper 的 zxid 或 etcd 鎖 key 的 revision。沒有這樣一個世代號,任何「我還是主」的宣告在分區下都只是意見。

陷阱「等 TTL 過了再切主」不是 fencing

給 Leader 一段 lease、保證 lease 內不選舉,可以減少誤切。它不能代替外部資源上的 token 檢查:lease 依賴時鐘與「持有者能在到期前停止寫入」的假設,GC 與時鐘跳動正好打破這兩個假設。面試被問腦裂,先答 term 作廢舊 Leader 的 RPC,再答外部副作用靠 fencing;不要停在「設一個超時」。

一句話答法分區下舊 Leader 仍可能對外寫入。Raft 多數派擋住 log 提交;外部資源必須拒絕小於目前 term 的 fencing token,TTL 不夠。
09

互動 · 五節點 log 推進與提交規則

你選誰當 term 1 的 Leader、第二筆寫入複製到幾台、崩潰後由誰接任;投票規則與提交規則負責算出每一步的 log 矩陣、票型與 commitIndex。

第 6 節的 Figure 8 對照圖是靜態的結論。這裡把同一組規則交到你手上:三個選擇一共一百種組合,每一步的狀態都由規則推出來,沒有預寫的劇本。特別注意第二筆寫入的複製台數那支滑桿——它同時決定兩件事:那筆寫入算不算已提交,以及接任者拿不拿得到票。

互動 · log 推進 選角與複製台數是你的決定,其餘由規則推導
決定一 誰的 election timeout 先到、成為 term 1 的 Leader?
決定二 Leader 崩潰後,誰發起 term 2 選舉?
步 0 / 8
已提交(在封印線左側) 已寫入、未提交 本步被截斷 該位置尚無項目

橫軸是 log index、縱軸是節點,所以「同一 index 在不同節點上是不是同一筆」可以直接對齊著看。三個讀法:格子裡印的是該位置的 term 號(不是佔用與否);綠色直線是 commitIndex 的封印線,線左側的前綴再也不會被任何後任 Leader 改寫;節點名右邊那道欄位在選舉步會變成票標,把每一張 YES/NO 連同「我的 last log 比你新」的理由一起印出來。(nextIndex 回溯要 Follower 落後多筆才會觸發,這裡刻意讓衝突都落在下一個 index,好把注意力留給提交規則。)

一句話答法多數派複製前任 term 的項目仍不能宣告提交;本 term 先 commit 一筆,前綴才一起封印。舊 Leader 對外寫入靠 token 擋,不靠 TTL。
10

面試折衷與邊界

先畫「誰在決定順序」,再談讀取、分片與鎖。協定講完就停,不要滑進產品 API。

面試常問 先答這句 不要滑去
Raft 在解什麼? 讓節點對單一有序、不可回捲的 log 達成一致 R+W>N、Read Repair、Version Vector
為什麼隨機超時? 拆開同時選舉,降低 split vote;心跳必須遠短於超時下界 「隨機比較公平」或跟負載均衡混談
何時能回覆客戶端? 該項目的 index ≤ commitIndex;而 commitIndex 只靠本 term 項目的多數派推進 Leader 本地 append 成功就算寫入完成
舊主回來怎麼辦? 聽到更高 term 降為 Follower,未提交尾巴可被截斷;外部副作用靠 fencing token 「等 TTL 到了就安全」或手動合併兩份 log
怎麼換機器? joint consensus,或一次只加/減一台,讓新舊多數相交 一次替換過半節點、配置只寫在運維腳本
和 Paxos 差在哪? 強 Leader 換可理解性與工程可實作性;Safety 目標相同 「Raft 更一致」或「Paxos 比較快」

本篇停在協定。Watch、Ephemeral Node、分散式鎖的正確用法見 協調服務與分散式鎖;訊息骨幹從 訊息佇列基礎 進;API 契約的先備是 gRPC 與 Protocol Buffers。

關鍵一個 Raft group 管一份 log,不是整個資料庫

把熱 key 跟冷 key 塞進同一個 group,提交延遲被最慢那台綁死,成員變更也變成全庫停寫。Distributed SQL 用「每個 shard 一個 Raft group」把共識的範圍切到分片大小:單分片線性可寫,跨分片另走交易層。共識的成本是多數派往返;範圍選錯,再快的磁碟也救不了。

一句話答法Raft 面試分數來自三句:隨機超時選出強 Leader、多數派持久化才提交、已提交項目靠 Matching/Completeness 不丟。外部寫入再加 fencing;不要用 quorum 讀寫或 TTL 來代替這三句。