直接照搬论文伪代码会卡在Prepare阶段,因proposal number需全局唯一单调递增,而本地自增计数器在多proposer下易冲突;Lamport要求编号为(round, serverID)二元组,须用int64高位存round、低位存serverID,禁用时间戳;Acceptor的promiseNumber与acceptedNumber须原子更新,推荐atomic.CompareAndSwapInt64及成对更新;消息通道应使用带缓冲chan并模拟丢包;Learner需确认提案已被多数Acceptor接受且无更大promiseNumber才可apply;生产环境必须日志落盘与重启恢复。

为什么直接照搬论文伪代码会卡在 Prepare 阶段
因为 proposal number 必须全局唯一且单调递增,但很多初版实现只用本地自增计数器,多个 proposer 同时运行时会生成冲突编号,导致 Acceptor 拒绝所有 Prepare 请求。Lamport 原文强调编号是 “(round, serverID)” 二元组,不是单整数。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 用
int64存储编号,高位放round(每轮自增),低位放serverID(确保不同节点不重) - 避免用
time.Now().UnixNano()—— 时钟漂移或回拨会导致编号乱序,Acceptor 会因promiseNumber < number判定为过期请求而忽略 - 每个
proposer在发起新提案前,必须先读取自己已知的最高round,再加 1 构造新编号
Acceptor 的 promiseNumber 和 acceptedNumber 必须原子更新
Go 中若用普通 int 字段,在并发 Prepare/Propose 混合到达时,可能出现“先检查再写入”的竞态:两个 Proposer 同时读到 promiseNumber == 0,都通过检查,随后都写入自己的编号,最终只有一个生效,另一个被静默丢弃 —— 这违反 Paxos 安全性(Safety)。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 用
sync/atomic的CompareAndSwapInt64实现带条件的写入 -
promiseNumber更新逻辑:仅当newNumber > currentPromise时才更新,否则返回当前值 -
acceptedNumber和acceptedValue必须成对更新,不能分开赋值;推荐封装为结构体 +atomic.StorePointer或用 mutex 保护小临界区
消息通道设计别用无缓冲 chan 直接模拟网络
很多教学实现用 chan message 让 Proposer 和 Acceptor 直接通信,看似简洁,但一旦某个 Acceptor 处理慢(比如阻塞在日志写入),整个 chan 就卡住,导致 Proposer 无法发给其他 Acceptor —— 这违背 Paxos 对“异步、消息可能延迟”的建模前提,也掩盖了真实网络分区下的行为。
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 为每个 Acceptor 单独配一个带缓冲的
chan message(缓冲大小 ≥ 2),允许消息暂存 - Proposer 发送时用
select { case ch <- msg: ... default: log.Warn("dropped") },模拟消息丢失 - 真正测试容错时,必须主动 kill 某个 goroutine 或关闭其 chan,而不是依赖“运气式超时”
learner 收集多数派 Accept 响应后不能立刻 apply
常见错误是 Learner 一收到 > N/2 个 Accept 响应就立即执行 v_a,但此时可能有更高编号的提案正在途中 —— 如果那个提案最终也获得多数 Accept,就会覆盖当前结果,破坏线性一致性。
关键点在于:Learner 必须确认该提案是“已被选定(chosen)”的,而判定依据是它在多数 Acceptor 上的 acceptedNumber 等于提案编号,且没有更大编号的提案在 Prepare 阶段已获 Promise。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- Learner 不自己做决策,而是监听 Acceptor 的持久化日志(哪怕只是内存 map + atomic 标记)
- 只有当某个
number在 ≥ ⌈N/2⌉ 个 Acceptor 的acceptedNumber中出现,且这些 Acceptor 中无人记录比它更大的promiseNumber,才算 chosen - 生产环境必须引入日志落盘和重启恢复逻辑,否则 Learner 内存状态丢失即导致状态机分裂
最易被忽略的是:Paxos 正确性不依赖任何超时或心跳,所有决策必须基于确定性消息交换。加了超时重试反而可能引入活锁,而没做持久化的 learner 在 crash 后根本无法重建共识上下文。

















