Paxos / Raft 笔记:旧主节点为什么不能继续写
沿着旧主节点、网络分区、提案编号、任期、日志匹配和隔离令牌,说明一致性协议如何阻止过期节点继续写入。
讲 Paxos 和 Raft,如果只说“多数派投票”,就没有抓住关键。
工程里最危险的场景通常长这样:
节点 A 是主节点
node A 和多数派断开
节点 B 在多数派中成为新主节点
节点 A 还活着,也仍以为自己是主节点
问题不是谁票多,而是:旧主节点还能不能继续写?旧消息晚到了怎么办?旧日志能不能覆盖新历史?外部存储会不会接受旧主节点的写入?
我现在读一致性协议,会先拿这个事故现场去套。
多数派交集让旧历史留下证人
5 个节点里,一个多数派至少 3 个节点。任意两个多数派一定相交:
多数派 1:n1 n2 n3
多数派 2:n3 n4 n5
n3 的意义不是投票公平,而是历史证人。
如果某个值已经在一个多数派中被接受或提交,后续另一个多数派必然会包含至少一个知道旧历史的节点。协议再通过提案编号、任期和日志规则,把这段旧历史带到新一轮中。
没有交集,就可能两个分区各自长出自己的历史。
任期和提案编号是写入权的版本号
旧主节点能不能继续写,第一道门是任期或提案编号。
Paxos:提案编号
Raft: 任期
节点一旦见过更高版本,就应该拒绝旧版本写入:
跟随者已经见过任期 8
旧主节点发送任期 7 的 AppendEntries
-> reject
这不只是“新编号大于旧编号”,而是在给写入权加版本。旧主节点即使进程还活着,也不能带着旧任期继续修改复制日志。
state flow
旧主节点失去写入权的过程
任期 7 的主节点
曾经拿到多数派
网络分区
失去多数派但进程还活着
选出任期 8 的主节点
多数派授予新主节点写入权
拒绝过期写入
旧任期消息不能推进日志
Paxos 的准备阶段迫使新主节点接受已有历史
Paxos 中的准备与承诺阶段很容易被看成流程仪式,其实它在解决一个具体问题:新的提案者不能随意覆盖可能已经被选定的旧值。
准备阶段做两件事:
- 接受者承诺不再接受更低编号的提案。
- 接受者返回自己曾接受的最高编号提案及其值。
如果返回内容中已经有旧值,新的提案者必须继承最高编号提案中的值。
prepare ballot=10
acceptor A -> accepted ballot=6 value=x
acceptor B -> none
acceptor C -> accepted ballot=8 value=y
proposer must choose y
这条规则防住的是:旧值可能已经在某个多数派里形成决定,新一轮不能把它抹掉。
Paxos 难读,不是因为步骤多,而是因为安全性藏在“你不能自由选择新值”这种约束里。
Raft 用日志匹配挡住错误后缀
Raft 更贴近工程实现。主节点复制日志,跟随者接受日志,看起来直观很多。但真正要看的不是正常流程,而是跟随者如何拒绝不连续的历史。
AppendEntries 会带:
prevLogIndex
prevLogTerm
entries
leaderCommit
跟随者只有在本地日志能对上 prevLogIndex/prevLogTerm 时,才接受后续日志项。
主节点: [1:a] [2:b] [3:c] [4:d]
跟随者: [1:a] [2:b] [3:x]
append after [3:c]
-> reject
-> 主节点回退
-> find common prefix [1:a][2:b]
这条规则保证日志不能随便从某个索引开始覆盖。不同节点要先找到共同前缀,再接上主节点的后缀。
提交旧任期日志是 Raft 中容易忽略的边界
Raft 主节点不会仅仅因为旧任期的日志项复制到多数派,就直接用它推进提交位置。它要等当前任期的日志项复制到多数派,再把前面的旧日志项一起带上。
这个规则看起来有些别扭,但它避免了一种危险:新主节点对旧任期日志的提交判断不够强,可能让后续选主过程中的日志安全性变得复杂。
更实用的理解是:
当前任期日志项已提交
-> 当前主节点的写入权被多数派确认
-> 它之前的前缀一并进入已提交状态
如果实现里忽略这个细节,系统可能在罕见选举交错下提交不该提交的旧日志。
选主成功不等于外部写安全
一致性协议能守住复制日志,但它不自动保护外部世界。
假设旧主节点曾经拿到写外部存储的权限。网络分区后,多数派选出新的主节点。旧主节点恢复时,如果外部存储仍接受它的写入,就会产生双主副作用。
所以工程系统需要隔离令牌:
任期 8 的主节点 -> 令牌 8
外部存储记录的最大令牌 = 8
任期 7 的旧主节点携带令牌 7 写入
-> reject
这一步经常不在一致性协议的核心流程图里,但生产系统不能省。主节点的世代或任期必须传到它控制的外部资源。
我会怎么读实现
看 Raft/Paxos 实现时,我不会从正常复制流程开始,而会找这些入口:
- 节点什么时候持久化当前任期或提案编号。
- 收到更高任期时如何退回跟随者状态。
- 投票请求是否检查候选人的日志新旧。
- AppendEntries 在哪里校验 previous log。
- conflict 后如何回退。
- 提交位置何时推进。
- 旧任期日志项是否由当前任期日志项间接提交。
- 安装快照后,日志前缀语义是否仍然成立。
- 对外资源是否使用隔离令牌。
能挡住旧主节点,才算真正理解了多数派、任期和日志匹配的意义。
最后记住的是写权,不是投票
Paxos/Raft 要守住的是一条不能被旧世界改写的历史。
多数派交集留下旧历史的证人,任期和提案编号让旧主节点失权,日志匹配防止错误后缀接上来,隔离令牌再把这个权力版本传到协议外部。
所以一致性协议不是“大家投票选一个值”,而是在一个消息会延迟、节点会恢复、主节点会过期的世界里,持续回答一个问题:现在谁还有资格写。