如何設計基於租約的 Leader 選舉?
題目與使用情境
多個副本共用協調儲存,需要在任一時刻只有一個實例執行定時結算任務。請設計租約記錄、競選、續租、交接與觀測,並說明網路分區、程序暫停、時鐘漂移及儲存故障時的行為。租約只控制「誰可以開始工作」,業務寫入仍需具備冪等與條件驗證。
面試官考察什麼
- 能否分開租約安全邊界與業務副作用。
- 是否理解原子比較並更新、任期 fencing token 與法定人數。
- 能否說明暫停、分區、舊 Leader 復活時為何不會雙主寫入。
- 是否提出可驗證的指標、故障注入與恢復策略。
作答前的釐清問題
先確認任務是否允許短暫停頓、最多容忍多久的重複執行、協調儲存的線性一致能力、跨區延遲、實例數量與時鐘同步品質。若必須跨區強一致,需明確取捨選舉延遲與可用性。
30 秒回答框架
我會使用具備線性一致與條件寫入的協調儲存。每個候選者以唯一身分與遞增任期競爭租約,只有成功原子建立或更新者成為 Leader;Leader 在租約到期前續租。每次業務寫入攜帶任期 token,下游拒絕舊 token。候選者只有確認讀到新狀態後才接管。網路分區或長時間暫停會讓實例主動停止工作,寧可短暫不可用也不冒險雙主。
分步驟深入解答
1. 資料模型與原子操作
租約記錄包含 holder identity、期限、任期 token、版本號與最後續租時間。競爭使用 compare-and-set:記錄不存在時建立,或只有版本未變且已過期時更新。Kubernetes 的 Lease 物件就是把持有者、續租時間與租期作為協調資料;實作必須確認底層讀寫語意,不能依賴普通最終一致快取。
2. 續租與自我降級
續租間隔應明顯小於租期,並為網路抖動與排程暫停留出餘量。續租失敗、讀不到確認或程序暫停超過安全窗口時,實例立即停止產生副作用;恢復後重新競選。不要只用本機牆上時鐘判斷他人是否過期。
3. Fencing 與業務冪等
每次成功競選產生單調遞增 token。任務執行器、資料庫條件更新或下游服務必須拒絕小於目前 token 的請求。即使舊 Leader 在暫停後恢復,也不能覆蓋新 Leader 的寫入。結算任務仍需冪等鍵、交易邊界與可重試設計。
高品質示範回答
我先定義安全目標:同一資源不能同時接受兩個有效 Leader 的寫入;可用目標則允許租約到期後短暫暫停。協調儲存提供線性一致 CAS,競選者寫入自己的身分與遞增任期,Leader 以心跳續租。每次副作用攜帶任期 token,下游用條件更新拒絕舊 token。若網路分區、GC 停頓或續租確認遺失,實例停止工作並在重新確認後競選;不能靠睡眠幾秒繼續執行。Raft 用任期與多數派投票解決日誌領導權,Kubernetes Lease 更像輕量協調記錄;兩者都要求把儲存一致性、超時比例與故障恢復納入設計。驗收會注入 Leader 崩潰、分區、時鐘偏移、長暫停與協調儲存不可用,檢查是否出現雙寫、舊 token 寫入與恢復時間超標。
常見錯誤
- 只在記憶體設定鎖或依賴 Redis 最終一致讀,卻宣稱不會雙主。
- 只比較時間戳,沒有遞增任期與 fencing token。
- 續租失敗後繼續處理目前批次,替舊 Leader 留下寫入窗口。
- 把「同一時刻一個 Leader」誤寫成「永遠沒有重複執行」。
- 只測正常選舉,不測 GC 暫停、分區、讀寫延遲與儲存故障。
追問及應對
租約到期是不是代表舊 Leader 一定停止?
不是。程序可能暫停或在網路隔離後繼續執行,因此必須在業務入口驗證 fencing token;租約到期只表示協調層不再承認它。
如何選擇租期與續租間隔?
以故障偵測目標、跨區 p99 延遲、排程暫停與儲存抖動預算推導,並保留多個續租週期的餘量。上線後持續觀察續租失敗率與選舉抖動,不能照搬固定毫秒數。
協調儲存不可用時怎麼辦?
停止啟動新的副作用,保留唯讀或已安全提交的結果;恢復後重新讀取任期並競選。若業務必須持續運行,就要明確降級為分片、多活或人工接管,並重新定義安全證明。