Paxos论文 Paxos Made Simple 、author:Leslie Lamport(兰伯特)
Paxos算法是一种共识算法,一些常用的共识算法都是基于它改进的,如Fast Paxos算法、Cheep Paxos算法、Raft算法等。Paxos算法包含2个部分:
实现一个分布式集群,这个集群由多个组成。现有多个客户端(客户端1、客户端2)访问这个集群,试图创建同一个只读变量(如X),客户端1试图创建值为3的X,客户端2试图创建值为7的X。那如何达成共识,实现各节点上X值的一致呢?使用Paxos算法。
Paxos算法中有一些独有而且比较重要的概念:提案、准备请求、接受请求、角色等。其中角色是对Basix Paxos中最核心三个功能的抽象。
在Basic Paxos中,有提议者(Proposer)、接受者(Acceptor)、学习者(Learner)三种角色。
一个节点可以身兼多种角色。例如一个3节点的集群,1个节点收到了客户端的请求,那么该节点将作为提议者发起二阶段提交,然后这个节点和另外两个节点一起作为接受者进行共识协商。
这三种角色,本质上代表的是三种功能:
在Basic Paxos中,使用提案代表一个提议。在提案中,除了提案编号,还包含了提议值。如[n,v]表示一个提案,其中n为提案编号,v为提议值。
假设客户端1的提案编号为1,客户端2的提案编号为5;节点A、B先收到来自客户端1的准备请求,节点C先收到来自客户端2的准备请求。
提议者 一般由收到客户端请求的节点担任,这里为了便于理解,将客户端作为了提议者。你可以这样理解:每个客户端对应一个节点,其所对应的节点即担任提议者又担任接受者。所以下述的客户端1、2可以看成节点A、C。
准备(Prepare)阶段
第一个阶段是准备阶段。首先客户端1、2作为提议者,分别向所有接受者发送包含提案编号的准备请求。
在准备请求的时候不需要指定提议的值,只需要携带提案编号。
随后,节点A、B收到提案编号为1的准备请求,节点C收到提案编号为5的准备请求,进行以下处理。
随后,当节点A、B收到提案编号为5的准备请求,节点C收到提案编号为1的准备请求的时候,进行以下处理。
如果节点收到准备请求中的提案编号,大于之前响应的准备请求的提案编号,并且已经通过了之前的提案,那么接受者会在请求的响应中包含已经通过的最大编号的提案信息(也就是说接受者会将其通过的最大编号的提案信息发送给提议者),而不是“尚无提案”的响应。
接受(Accept)阶段
第二个阶段也就是接受阶段,首先客户端 1、2 在收到大多数节点的准备响应之后,会分别发送接受请求:
当客户端 1 收到**大多数的接受者(节点 A、B)**的准备响应后,根据响应中提案编号最大的提案的值,设置接受请求中的值。因为该值在来自节点 A、B 的准备响应中都为空(也就是“尚无提案”),所以就把自己的提议值 3 作为提案的值,发送接受请求[1, 3]。
当客户端 2 收到大多数的接受者的准备响应后(节点 A、B 和节点 C),根据响应中提案编号最大的提案的值,来设置接受请求中的值。因为该值在来自节点 A、B、C 的准备响应中都为空(也就是图 5 和图 6 中的“尚无提案”),所以就把自己的提议值 7 作为提案的值,发送接受请求[5, 7]。
当节点 A、B、C 收到接受请求[1, 3]的时候,由于提案的提案编号 1 小于三个节点承诺能通过的提案的最小提案编号 5,所以提案[1, 3]将被拒绝。
当节点 A、B、C 收到接受请求[5, 7]的时候,由于提案的提案编号 5 不小于三个节点承诺能通过的提案的最小提案编号 5,所以就通过提案[5, 7],也就是接受了值 7,三个节点就 X 值为 7 达成了共识。
如果节点的准备响应中包含了提案信息而不是“尚无提案”,那么提议者会将响应的提案编号最大的提案所对应的值,作为接受请求中的值,而不是将自己的提议值作为提案值。这样就保证了大多数节点就某个值达成共识后,这个值就不会改变了,哪怕有新的提案这个值也不会改变了。
如果集群中有学习者,当接受者通过一个提案时,就通知给所有学习者。当学习者发现大多数的接受者都通过了提案,那么它也通过该提案,接受该提案的值。
除了共识之外,Basic Paxos还实现了容错,这个容错能力,源自**“大多数”**的约定(大多数接受者准备响应)。可以理解为:当少于一半的节点出现故障的时候,共识协商仍能正常工作。
如果节点 A、B 已经通过了提案[5, 7],节点 C 未通过任何提案,那么当客户端 3 提案编号为 9 时,通过 Basic Paxos 执行“SET X = 6”,最终三个节点上 X 值是多少呢?
准备阶段:
当节点A、B收到提案编号为9的准备请求的时候,因为提案编号9大于之前响应的准备请求的提案编号5,并且这两个节点已经通过了之前的提案[5,7],接受者A、B会在准备请求的响应中,包含已经通过的最大编号的提案信息[5,7],并承诺以后不再响应提案编号小于9,不会通过编号小于9的提案;由于节点C之前没有通过任何提案,所以节点C将返回一个“尚无提案”的响应,并承诺以后不再响应议案编号小于等于9的准备请求,不会通过编号小于9的提案。
接受阶段:
当客户端3收到大多数的接受者的准备请求后(节点A、B和C),根据响应中提案编号最大的提案的值,来设置请求的值,即来自A、B节点的准备响应的提案[5,7]。因此就把A、B响应值7作为提案的值,发送接受请求[9,7]。当节点A、B、C收到接受请求[9,7]后,由于提案的提案编号不小于三个节点承诺的能通过的提案的最小提案编号9,所以就通过提案[9,7]。三个节点就达成了共识。最后的值为7。
大多是值就某个节点达成共识了,值就不变了。哪怕有新的提案,这个值也能保证不再变了。
Basic Paxos 只能就单个值(Value)达成共识,一旦遇到为一系列的值实现共识的时候,它就不管用了。可以通过多次执行 Basic Paxos 实例(比如每接收到一个值时,就执行一次 Basic Paxos 算法)实现一系列值的共识。
Multi-Paxos 是一种思想,不是算法。而 Multi-Paxos 算法是一个统称,它是指基于 Multi-Paxos 思想,通过多个 Basic Paxos 实例实现一系列值的共识的算法(比如 Chubby 的 Multi-Paxos 实现、Raft 算法等)。
Basic Paxos 是通过二阶段提交来达成共识的。在第一阶段,也就是准备阶段,接收到大多数准备响应的提议者,才能发起接受请求进入第二阶段(也就是接受阶段)
如果我们直接通过多次执行 Basic Paxos 实例,来实现一系列值的共识,就会存在这样几个问题:
可以通过引入领导者和优化 Basic Paxos 执行来解决上面的两个问题。
领导者节点作为唯一提议者,这样就不存在多个提议者同时提交提案的情况,也就不存在提案冲突的情况了。
在论文中,并没有说如何选举领导者,需要我们在实现 Multi-Paxos 算法的时候自己实现如在 Chubby 中,领导者节是通过执行 Basic Paxos 算法,进行投票选举产生的。
可以采用**“当领导者处于稳定状态时,省掉准备阶段,直接进入接受阶段”**这个优化机制,优化 Basic Paxos 执行。也就是说,领导者节点上,序列中的命令是最新的,不再需要通过准备请求来发现之前被大多数节点通过的提案,领导者可以独立指定提案中的值。这时,领导者在提交命令时,可以省掉准备阶段,直接进入到接受阶段:
和重复执行 Basic Paxos 相比,Multi-Paxos 引入领导者节点之后,因为只有领导者节点一个提议者,只有它说了算,所以就不存在提案冲突。另外,当主节点处于稳定状态时,就省掉准备阶段,直接进入接受阶段,所以在很大程度上减少了往返的消息数,提升了性能,降低了延迟。
感觉Multi-Paxos 的实现需要执行多轮的 Basic Paxos,但Multi-Paxos 对每一轮 Basic Paxos进行了上述优化。
读写请求都在主节点上了,主节点的压力就很大了呀!!!