我用 Python 实现了 Raft 共识算法(分布式算法中的典型代表)。 这个实现相当糟糕! 不过也(算是)正确的。
这里有一些笔记,想分享给同样有兴趣接受这类挑战的人。
回过头来看,下面这些资料对学习 Raft 并正确实现它最有帮助。
- Raft 论文(读到第 5 节,并多多参照图 2)
- Raft 学生指南
- 使用最广泛的 Raft 实现之一
- 克隆仓库,粗读
raft.go,并与 LLM 反复交流,理解代码库和设计决策
- 克隆仓库,粗读
- Eli Bendersky 的系列博文
我建议先花一个小时左右读论文,然后为 UDP 或 TCP 服务器写个代码框架,让它读取传入的字节并添加到数组中。 接着,我跟着 Eli 的实现,按同样的顺序为自己的 Raft 实现添加功能。
当选举功能看起来能正常运行后,我开始寻找 bug,以及自己对算法理解上的错误。 我会在学生指南、Raft 论文中的图 2 和自己的实现之间来回对照,仔细思考自己的实现在哪些地方与它们相符(或不同)。 我还大量使用 LLM 来审查这些代码,并将上述资料中的内容加入上下文。
对日志复制、持久化等部分,也重复上述过程。
关于实现
我在实现时做了一些简化设计的选择。 以下不分先后:
- 每个节点都在单个线程上运行并处理消息
- 使用“逻辑时钟”来跟踪系统中的本地“时间”(例如,调用
tick()并递增各节点自己的计数器,而不是使用系统时间) - 把所有东西都放在一个文件里,让实现变得“混杂”,例如网络解析、存储/持久化、Raft 核心算法,以及用于控制节点本身的工具/命令
2 看起来是一个合理且正确的设计选择(etcd 的实现用的就是逻辑时钟)。 3 可以说更适合学习/教学。 把所有内容都放在一个文件里很方便,既能一眼看到全貌,也能得到一份方便拆解的实现,用来探索哪些抽象最适合这个算法。
在我看来,1 这个选择有点离谱。 它确实让实现简单了很多(不用太操心死锁和节点内部状态的原子更新),但最后得到的东西也不完全是 Raft。 作为第一次实现,这样似乎也没问题。 算法本身已经足够复杂,我想,比起跟互斥锁较劲,你大概更愿意把时间花在调试 Raft 核心算法的逻辑错误上。
我觉得,用这种方式实现 Raft,可以看作一个耗时约 30 小时的项目。 最开始阅读 Raft 论文、查阅相关资料,应该需要几个小时。 我在假期里花了约 3 天完成大部分编码工作,每天写代码大约 6-8 小时。 现有实现还有一些需要打磨和改进的地方(例如修复一些隐蔽的 bug),可能还得再花半天时间。