本文是在学习文章《paxos made simple》时按自己的理解整理的笔记,仅作为记录供日后回顾。
1. 概述
Paxos 算法是基于消息传递1的、具有高度容错特性共识算法2。
-
拜占庭将军问题
若干拜占庭将军各自率领一支军队围困一座城市,将各军队行动简化为进攻和撤退两种,由于各军队行动不一致会造成灾难性的后果,因此各个将军需要通过投票达成一致性策略。
由于各将军所在的位置不同,他们之间只能通过一个信使将进攻还是撤退的信息传递给其他将军,这样每位将军就可以通过自己和其他将军的投票结果决定策略。
现假设将军中出现了叛徒,他们倾向于向较为糟糕的策略投票,且会选择性的发送投票信息。如:现有 9 各将军,有 1 名叛徒,4 名将军选择进攻,另外 4 名选择撤退。叛徒故意给选择进攻的将军发送进攻消息,给选择撤退的将军发送撤退信息。这时 4 名进攻将军会根据投票情况选择进攻,4 名撤退将军会选择撤退,导致军队间的一致性被破坏。
基于消息传递通信模型的分布式系统会有:进程较慢、被杀死或重启,消息延迟、重复、或丢失。在最普通的 Paxos 场景(不考虑消息篡改即拜占庭错误问题)中,通过一个共识机制,可以保证上述问题不发生,使集群对某个值达成一致,保证系统的一致性。
2. 算法推导
2.1 问题提出与定义
- 问题的提出
对于一个包含若干节点且可能发生节点宕机或网络异常的分布式系统,快速对某个值达成一致且保证整个系统的一致性。
- 共识算法
假定一个可以提议某个值 v 的提案 p,共识算法有以下安全性要求:
- 如果
v没有被任意一个p提出,则v不能被选择- 只有一个
v被选择v被选择后,其他节点都可以学习被选择的v
-
角色定义:Proposer、Acceptor、Learner
-
通信
不同角色间通过消息传递相互通信。
每个角色的运行相互独立,单个角色可能因故障宕机或重新启动。一个 v 被选中后角色能记录相关信息,即使 v 被选中后全部节点故障重启,也能确定被选中的 v
消息的传递可以容忍高时延、丢失、重复,但不能出现数据篡改(拜占庭错误)
2.2 选择一个值
- P1 的提出
对于一个存在若干节点的分布式系统,为了保证只有一个 v 被选择,我们需要规定:只有被一半以上的 Acceptor 接受的值才能被选择。
同时,为了保证 Proposer 提出的值能被有效选择,我们约定:
P1:Acceptor 必须接受它收到的第一个提案。
- P2 的提出
对于约定 P1,可能会存在如下问题:
Propo1 和 Propo2 几乎同时发布提案,提议的值分别为 v1 和 v2。并且,Accep1 和 Accep2 接受了 v1,Accep3 接受了 v2。
假设此时 Accep2 宕机,没有一个 v 被半数以上的 Acceptor 接受,当 Accep2 重启后,也不知道学习哪一个值。
对于这种情况,需要重新提出 p,这就意味着,每个 Proposer 会发布不止一个提案,一个 Acceptor 会接受不止一提案。因此我们需要给 p 分配一个唯一且有序的编号 n 进行标识,因此,一个提案可以表示为 p[n,v] 。
我们允许选择多个 p ,那我们就需要保证选择的每一个 p 都有相同的 v 来保证一致性。为了实现这点,我们约定:
P2:如果选择了值为
v0的提案p[i,v0],那么选择的每个编号大于i的提案p的值也是v0
- 的提出
考虑到提案的编号是有序的,在 P2 约束下我们可以保证只有一个值被选中。我们知道被大多数 Acceptor 接受的 p 才会被选择,因此对于约束 P2 可以理解为:
:如果选择了值为
v0的提案p[i,v0],那么 Acceptor接受的每个编号大于i的提案p的值也是v0
- 的提出
但对于约束 ,可能会存在如下问题:
如果 Accep3 刚从宕机状态恢复且之前没有接受过任何提案,此时,Propo2 发布了一个编号跟高但是值不同的提议,由于约束 P1,Accep3 必须接受该值,而这违反了 。
因此我们约定:
:如果选择了值为
v0的提案p[i,v0],那么 Proposer 发布的每个编号大于i的提案p的值也是v0
由于提案必须有 Proposer 发布后才能被 Acceptor 接受,因此 包含 。
- 的提出(满足 )
为了保证 能被满足,我们约定:
:对于任意的
n和v,如果提案p[n,v]被提出,那么存在一个半数以上的 Acceptor 集合 S,至少满足以下两个条件中的一个: (a)S 中没有一个 Acceptor 接受过编号比n更小的提案。 (b)S 中的 Accetper 接受过编号最大的提案的值也为v
通过保证 的不变性保证 的不变性。
而为了保证 的不变性,对于每个想要发布编号为 n 的 p 的 Proposer,必须满足:
-
- Proposer 先学习编号小于 n 中编号最大的那个提案(if any)。3
-
- Acceptor 不再接受任何编号小于 n 的提案,保证上述学习提案的有效性。
如 :若想发布编号为 5 的 p,学习的提案编号为 3,如果期间有另一个 Proposer 发布了编号为 4 的 p,可能导致约定 被打破,因此需要有对 Acceptor 的承诺。
3. 算法流程
对于上述推导流程推导出的约定 P1 和 ,我们可以获得下述提案发布的算法:
3.1 Proposer 提出提案
- 学习阶段:prepare request
Proposer 选择一个编号为 n 的新提案 p[n,?],向半数以上的一个 Acceptor 集合 S 发送请求,要求其响应:
-
- 承诺不再接受编号小于 n 的提案
-
- 已接受的编号小于 n 中编号最大的提案(if any)3
对于这样的请求,称为准备请求(prepare request)
- 接受阶段:accept request#
如果 Proposer 收到了大多数(半数以上)Acceptor 的响应,那么:
-
- 如果 Proposer 收到了 Acceptor 响应的提案,那么 Proposer 发布
p[n,v],其中v是 Proposer 收到的响应提案中编号最大的提案的值。
- 如果 Proposer 收到了 Acceptor 响应的提案,那么 Proposer 发布
-
- 如果 Proposer 未收到 Acceptor 响应的提案,那么 Proposer 发布
p[n,v],其中v由 Proposer 自己生成
- 如果 Proposer 未收到 Acceptor 响应的提案,那么 Proposer 发布
对于这样的请求,称为接受请求(accept request)
tips: 学习阶段和接受阶段的 Acceptor 集合 S 不要求是同一个
3.2 Acceptor 接受提案
对于 Accetper,它可以接收来自 Proposer 的两种请求:prepare request 和 accept request。为了保证安全,Acceptor 可以忽略任何请求。
因此对于 Acceptor,我们只需规定何时允许响应请求:
-
- Acceptor 始终被允许响应 准备请求(prepare request)
-
- Acceptor 可以响应接收请求, 接受提案,前提是它没有承诺过不这么做。
对于第二点,转化为约定:
:Acceptor 可以接受编号为 n 的提案,当且仅当它未响应编号大于 n 的准备请求(accept request)
可以观察到 包含 P1
通过上述规定,我们可以知道,对于 Acceptor,只需记录两个信息4:
对于这两个信息,即使 Acceptor 中断重启也必须保证正确记录。
3.3 两阶段提交
Proposer 可以提出多个提案,也可随时放弃执行中的提案。例如如果由 Proposer 尝试发布编号更高的提案,那么此时最好放弃当前的提案。同时,如果 Acceptor 收到了编号更高的准备请求而准备忽略当前请求时,可以通知 Proposer 放弃当前提案以获得一定的性能优化。
3.4 Learner 学习提案
当 Acceptor 接受提案之后,Acceptor 无法立刻得知接受的值是否被选择,因此当 Acceptor 接受提案后,会变为 Learner 发现被选择的值。
目前学习被选中的值的方式有以下三种:
| 方案一 | 方案二 | 方案三 | |
|---|---|---|---|
| 内容 | 每个 Acceptor 接受一个提案后就给所有 Learner 发送提案,发送数量为接收者数量与学习者数量的乘积 | Acceptor 将接受的提案发给一个主 Learner,由主 Learner 将提案发给其他 Learner | Acceptor 将接受的提案发给一个主 Learner 集合,由主 Learner 集合将提案发给其他 Learner |
| 优点 | Learner 能快速获取 value | 通信次数减少 (M+N) | 可靠性高 |
| 缺点 | 通信次数太多(M*N) | 单点故障(主 Learner 故障) | 通信网络复杂 |
3.5 Paxos 死循环
对于以下场景:
-
- 假设两个提议者
P1和P2
- 假设两个提议者
-
P1发布提案p[n1,v],Acceptors 响应Prepare_Req (n1)
-
P2发布提案p[n2,v],Acceptors 响应Prepare_Req (n2),忽略Accept_Req(n1,v)
-
P1发布提案p[n3,v],Acceptors 响应Prepare_Req (n3),忽略Accept_Req(n2,v)
-
- ...
可以看到由于 P1 和 P2 不断发布提案导致陷入死循环。为了解决这个问题,需要选择一个主 Proposer,只有主 Proposer 才能提出提案。