第4章 Paxos算法精解:Basic Paxos、Multi-Paxos、角色与阶段
在零信任网络里,每个节点都可能被攻破,但整个系统还得对外输出一致的结果。这靠什么?靠共识算法。Paxos是其中教科书级的存在。说实话,我早年刚接触Paxos时,觉得它就是一套晦涩的数学证明。后来调了几年分布式系统,才真正摸到它的筋骨。
4.1 Basic Paxos 经典两阶段
Basic Paxos定义了三个角色:Proposer(提议者)、Acceptor(接受者)、Learner(学习者)。它们之间的交互分两个阶段。
- 阶段一(Prepare):Proposer生成一个全局递增的提案编号N,向所有Acceptor发送Prepare(N)。Acceptor承诺:如果没接受过编号≥N的提案,就响应自己的已接受记录(如果有);否则忽略。
- 阶段二(Accept):Proposer收到多数派响应后,从响应中选取已接受提案的值,或自己选一个新值。然后向所有Acceptor发送Accept(N, V)。Acceptor检查编号是否仍然最大,是则接受。
关键约束:Acceptor一旦承诺更高的编号N+1,就不能再接受编号N的提案。这就是Paxos正确的核心——安全性(Safety)保证只有一个值被选定。
我做第一个分布式KV系统时就是按这个两阶段手写的。结果遇到一个坑:Proposer不断自增编号,导致活锁,谁都没法提交成功。后来加了一个超时随机回退,才跑通。嗯,这里要特别提醒——Basic Paxos不保证必然终止,实际工程必须加冲突规避机制。
4.2 Multi-Paxos 性能优化
Basic Paxos每决定一个值就要两轮RTT,成本太高。Multi-Paxos的思路是:选一个Leader,让Leader在其任期内跳过Prepare阶段。
| 对比项 | Basic Paxos | Multi-Paxos(Leader模式) |
|---|---|---|
| 阶段数 | Prepare + Accept(2 RTT) | 仅Accept(1 RTT,Leader稳定后) |
| Leader角色 | 无固定Leader,多Proposer竞争 | 选举一个Leader,其他为Follower |
| 提案编号 | 每次提案需新编号 | Leader使用唯一递增序列号 |
| 典型应用 | 元数据选主 | 日志复制、状态机同步 |
说白了,Multi-Paxos把“选主”和“复制”分开。Leader先通过一轮Basic Paxos或其它方式确立自己的身份。之后所有提案都由Leader发出,Acceptor只需要对序列号做单调递增检查。
曾经踩过的坑:我在一个6节点集群里部署Multi-Paxos,网络出现分区时,两个分区各自选出Leader,导致“双主”同时写日志。Raft用超时随机化解决了这个问题,而Multi-Paxos原并没严格规定Leader选举时序。所以你实现Multi-Paxos时,必须自己设计一套租约或Epoch机制来防止脑裂。
4.3 角色与阶段的本质
不管你用Basic还是Multi,角色本质上是一组逻辑职责。同一个节点可以兼任多个角色。零信任网络下,每个角色做的事都要经过最少权限验证。比如Acceptor不能主动发起提案,Learner只读不能写。
我建议你在设计协议时,把角色和网络身份绑定成独立的微服务单元。一个服务如果只当Learner,那它的网络权限只开放读端口,写端口全关掉。这样即使被攻破,攻击者也做不了破坏。
4.4 核心流程可视化
下面这张图展示Basic Paxos两阶段握手,以及Multi-Paxos如何通过Leader跳过Prepare简化流程。我把它画成流程图,方便你对照代码推导。
4.5 工程落地与零信任适配
你可能会问:“Paxos是几十年前的老东西,跟零信任有什么直接关系?”关系大了。零信任网络里,每次访问决策都需要跨节点一致确认。例如一个用户要访问机密文件,策略决策点(PDP)需要从多个策略引擎获取评分,然后对评分做共识。这正好用Paxos来保证每个决策全局唯一。
我的工程建议:
- 不要自己手写Paxos,用成熟库(比如etcd的Raft实现)。但理解Basic Paxos有助于调试脑裂和活锁。
- 在零信任场景下,Acceptor数量建议至少5个,容忍2个节点被攻破仍然正确。
- Learner可分布在所有控制面节点上,它们只读,这样攻击者即使控制一个Learner也改不了已经提交的决策。
4.6 避坑三连(我亲身经历的)
- 编号绕回:曾经有一个系统把提案编号设为uint32,结果高并发下编号溢出绕回,Acceptor把新提案当成旧提案忽略——整个集群锁死。建议用uint64+单调递增,或干脆用时间戳+节点ID。
- 日志截断与快照:Multi-Paxos如果不做定期snapshot,Acceptor的内存/磁盘会被日志撑爆。我当时没预估好,线上节点OOM了三次才老实加上快照机制。
- 网络分区下的脏读:零信任环境常有多路径网络,某个Acceptor可能延迟很久才收到Accept消息。如果你在Learner端不按“多数派提交”来读取,就会读到未最终确认的值。记住:只有被多数Acceptor接受的值才是最终值。
好了,Basic Paxos和Multi-Paxos的骨架就这些。你如果要去实现一个零信任下的信任评分共识模块,完全可以复用这里的思路:选一个Leader,让Leader批量提交评分到Acceptor,Learner从Acceptor拿到多数确认后广播给执行层。注意编号不回绕,超时加随机化,做好snapshot——这几点做到,基本不会翻车。