共识算法
共识(Consensus),很多时候会见到与一致性(Consistency)术语放在一起讨论。严谨地讲,两者的含义并不完全相同。
一致性的含义比共识宽泛,在不同场景(基于事务的数据库、分布式系统等)下意义不同。具体到分布式系统场景下,一致性指的是多个副本对外呈现的状态。如前面提到的顺序一致性、线性一致性,描述了多节点对数据状态的共同维护能力。而共识,则特指在分布式系统中多个节点之间对某个事情(例如多个事务请求,先执行谁?)达成一致意见的过程。因此,达成某种共识并不意味着就保障了一致性。
实践中,要保证系统满足不同程度的一致性,往往需要通过共识算法来达成。
共识算法解决的是分布式系统对某个提案(Proposal),大部分节点达成一致意见的过程。提案的含义在分布式系统中十分宽泛,如多个事件发生的顺序、某个键对应的值、谁是主节点……等等。可以认为任何可以达成一致的信息都是一个提案。
对于分布式系统来讲,各个节点通常都是相同的确定性状态机模型(又称为状态机复制问题,State-Machine Replication),从相同初始状态开始接收相同顺序的指令,则可以保证相同的结果状态。因此,系统中多个节点最关键的是对多个事件的顺序进行共识,即排序。
算法共识/一致性算法有两个最核心的约束:1) 安全性(Safety),2) 存活性(Liveness):
Safety:保证决议(Value)结果是对的,无歧义的,不会出现错误情况。
只有是被提案者提出的提案才可能被最终批准;
在一次执行中,只批准(chosen)一个最终决议。被多数接受(accept)的结果成为决议;
Liveness:保证决议过程能在有限时间内完成。
决议总会产生,并且学习者最终能获得被批准的决议。
Paxos
Google Chubby 的作者 Mike Burrows 说过, there is only one consensus protocol, and that’s Paxos” – all other approaches are just broken versions of Paxos.
意即世上只有一种共识算法,那就是 Paxos,其他所有的共识算法都只是 Paxos 算法的残缺版本。虽然有点武断,但是自从 Paxos 问世以来,它便几乎成为了分布式共识算法的代名词,后来的许多应用广泛的分布式共识算法如 Raft、Zab 等的原理和思想都可以溯源至 Paxos 算法。
Paxos 是由 Leslie Lamport (LaTeX 发明者,图灵奖得主,分布式领域的世界级大师) 在 1990 年的论文《The PartTime Parliament》里提出的,Lamport 在论文中以一个古希腊的 Paxos 小岛上的议会制订法律的故事切入,引出了 Paxos 分布式共识算法。
Basic Paxos
业界一般将 Lamport 论文里最初提出分布式算法称之为 Basic Paxos,这是 Paxos 最基础的算法思想。
Basic Paxos 算法的最终目标是通过严谨和可靠的流程来使得集群基于某个提案(Proposal)达到最终的共识。
基础概念
- Value:提案值,是一个抽象的概念,在工程实践中可以是任何操作,如『更新数据库某一行的某一列』、『选择 xxx 服务器节点作为集群中的主节点』。
- Number:提案编号,全局唯一,单调递增。
- Proposal:集群需要达成共识的提案,提案 = 编号 + 值。
Proposal 中的 Value 就是在 Paxos 算法完成之后需要达成共识的值。
Paxos 算法中有三个核心角色:
- Proposer:生成提案编号 n 和值 v,然后向 Acceptors 广播该提案,接收 Acceptors 的回复,如果有超过半数的 Acceptors 同意该提案,则选定该提案,否则放弃此次提案并生成更新的提案重新发起流程,提案被选定之后则通知所有 Learners 学习该最终选定的提案值(也可以由 Acceptor 来通知,看具体实现)。Basic Paxos 中允许有多个 Proposers。
- Acceptor:接收 Proposer 的提案并参与提案决策过程,把各自的决定回复给 Proposer 进行统计。Acceptor 可以接受来自多个 proposers 的多个提案。
- Learner:不参与决策过程,只学习最终选定的提案值。
在具体的工程实践中,一个节点往往会充当多种角色,比如一个节点可以既是 Proposer 又是 Acceptor,甚至还是 Learner。
算法流程
相较于直接给出 Paxos 算法的流程,我想沿袭 Lamport 大师的经典 Paxos 论文《Paxos Made Simple》中的思路:通过循序渐进的方式推导出 Paxos 算法。
首先需要了解 Paxos 算法中的两个重要的约束:
C1. 一个 Acceptor 必须接受它收到的第一个提案。
C2. 只有当超过半数的 Acceptors 接受某一个提案,才能最终选定该提案。
C2 其实有一个隐含的推论:一个 Acceptor 可以接受多个提案,这也是为什么我们需要给每一个提案生成一个编号的原因,用来给提案排序。
我们前面提到过 Paxos 的最终目标是通过严谨和可靠的流程来使得集群基于某个提案(Proposal)达到最终的共识,也就是说基于某一个提案发起的一次 Paxos 流程,最终目的是希望集群对该提案达成一致的意见,而为了实现并维持集群中的这种一致性,前提是 Paxos 算法必须具有幂等性:一旦提案(Proposal)中的值(Value)被选定(Chosen),那么只要还在此次 Paxos 流程中,就算不断按照 Paxos 的规则重复步骤,未来被 Chosen 的 Value 都会是同一个。如果不满足这种幂等性,将可能导致不一致的问题。
因此,我们可以把 Paxos 的基本命题提炼出来:
P1. 在一次 Paxos 流程中,如果一个值(Value)为 v 的提案(Proposal)被选定(Chosen)了,那么后续任何被最终选定的带有更大编号(Number)的提案中的 Value 也必须是 v。
提案在被最终选定之前必须先被 Acceptor 接受,于是我们可以再进一步总结一个具有更强约束的命题:
P2. 在一次 Paxos 流程中,如果一个值(Value)为 v 的提案(Proposal)被选定(Chosen)了,那么后续任何被 Acceptor 接受的带有更大编号(Number)的提案中的 Value 也必须是 v。
这还不是具备最强约束的命题,因为提案在被 Acceptor 接受之前必须先由 Proposer 提出,因此还可以继续强化命题:
P3. 在一次 Paxos 流程中,如果一个值(Value)为 v 的提案(Proposal)被选定(Chosen)了,那么后续任何 Proposer 提议的带有更大编号(Number)的提案中的 Value 也