我用 Python 實作了 Raft 共識演算法(分散式演算法的代表)。 這個實作相當糟糕! 不過也(算是)正確。
以下是一些筆記,想分享給其他有興趣接受類似挑戰的人。
回頭來看,以下是學習 Raft 並正確實作時最有用的資源。
- Raft 論文(讀到第 5 節,並多多參照圖 2)
- 給學生的 Raft 指南
- 使用最廣泛的 Raft 實作之一
- 複製儲存庫,略讀
raft.go,並與 LLM 反覆討論,以理解程式碼庫與設計決策
- 複製儲存庫,略讀
- Eli Bendersky 的部落格系列文章
我建議先花一小時左右閱讀論文,接著為 UDP 或 TCP 伺服器寫一些程式骨架,讓它能讀取傳入的位元組,並將這些位元組加入陣列。 之後我跟著 Eli 的實作,按相同的順序為自己的 Raft 實作加入功能。
在做出看起來能正常運作的選舉機制之後,我便開始尋找程式錯誤,以及自己對演算法理解上的錯誤。 我會反覆對照學生指南、Raft 論文中的圖 2,以及自己的實作,仔細思考我的實作在哪些地方相同(或不同)。 我也大量使用 LLM 來審查這份程式碼,並把上述資源的內容加入上下文。
針對日誌複寫、持久化等功能,重複上述流程。
關於實作
我在實作時做了一些簡化設計的選擇。 以下順序不分先後:
- 每個節點都在單一執行緒上運作並處理訊息
- 使用「邏輯時鐘」來追蹤系統上的本地「時間」(例如,呼叫
tick()並遞增各節點自己的計數器,而非使用系統時間) - 把所有東西都放在同一個檔案裡,讓實作變得「混雜」,例如網路資料解析、儲存/持久化、Raft 核心演算法,以及控制節點本身的工具/命令
第 2 點看來是健全且正確的設計選擇(etcd 的實作用的就是邏輯時鐘)。 第 3 點對學習/教學來說,可以說更好。 把所有東西都放在同一個檔案裡很方便,因為你能一次看到全貌,也能得到一份可以拆開重整的實作,試試哪些抽象方式最適合這個演算法。
第 1 點對我來說就有點離譜了。 這確實讓實作簡單許多(你比較不用擔心死結,以及節點內部狀態更新的原子性),但最後做出來的東西也就不完全是 Raft 了。 以第一次實作來說,這樣似乎沒問題。 演算法本身已經夠複雜了,我想你應該會更願意把時間花在修正 Raft 核心演算法中的邏輯錯誤上,而不是為互斥鎖傷腦筋。
我認為以這種方式實作 Raft,是一個約需 30 小時的專案。 一開始閱讀 Raft 論文和研讀相關資料,應該會花上幾個小時。 假期期間,我花了約 3 天完成大部分的程式撰寫,每天埋頭寫程式約 6-8 小時。 我目前的實作還有一些地方需要修整和改進(例如修正一些不易察覺的錯誤),可能還要再花半天的時間。