首先明确一点,Paxos算法的目的:是在分布式环境下确定一个值,这个值被所有节点承认。注意,这个值并不是狭义上的某个数,它可以是一条日志,也可以是一条命令(command)等等,根据应用场景不同,有不同的含义。
为何要设计出这么一套协议,其他协议不行么?
如最容易想到的,一个值A,往3台机器都写一次,这样一套简单的协议,能不能达到一致性的效果?这里就涉及到另外一个概念,Paxos一致性协议是在特定的环境下才需要的,这个特定的环境称为异步通信环境。而恰恰,几乎所有的分布式环境都是异步通信环境,在计算机领域面对的问题,非常需要Paxos来解决。
异步通信环境指的是消息在网络传输过程中,可能发生丢失,延迟,乱序现象。在这种环境下,上面提到的三写协议就变得很鸡肋了。消息乱序是一个非常恶劣的问题,这个问题导致大部分协议在分布式环境下都无法保证一致性,而导致这个问题的根本原因是网络包无法控制超时,一个网络包可以在网络的各种设备交换机等停留数天,甚至数周之久,而在这段时间内任意发出的一个包,都会跟之前发出的包产生乱序现象。无法控制超时的原因更多是因为时钟的关系,各种设备以及交换机时钟都有可能错乱,无法判断一个包的真正到达时间。
什么是 consensus (一致性)问题?
在一个分布式系统中,有一组的 process,每个 process 都可以提出一个 value,consensus 算法就是用来从这些 values 里选定一个最终 value。如果没有 value 被提出来,那么就没有 value 被选中;如果有1个 value 被选中,那么所有的 process 都应该被通知到。
异步通信环境并非只有paxos能解决一致性问题,经典的两阶段提交也能达到同样的效果,但是分布式环境里面,除了消息网络传输的恶劣环境,还有另外一个让人痛心疾首的,就是机器的当机,甚至永久失联。在这种情况下,两阶段提交将无法完成一个一致性的写入,为了解决这个问题,大家提出了各种各样的 protocols(协议),其中最有名的就是 Lamport 的 Paxos.。而paxos,只要多数派机器存活就能完成写入,并保证一致性。
至此,总结一下paxos就是一个在异步通信环境,并容忍在只有多数派机器存活的情况下,仍然能完成一个一致性写入的协议。
Paxos算法解决的问题是在一个可能发生异常(进程可能会慢、被杀死或者重启,消息可能会延迟、丢失、重复,不考虑消息篡改即拜占庭错误的情况)的分布式系统中如何就某个值达成一致,保证不论发生以上任何异常,都不会破坏决议的一致性。
Paxos协议提出只要系统中2f+1个节点中的f+1个节点可用(多数派,容灾保证),那么系统整体就可用并且能保证数据的强一致性,它对于可用性的提升是极大的。
算法中的参与者主要分为三个角色,同时每个参与者又可兼领多个角色:
(1)、proposer 提出提案,提案信息包括提案编号和提议的value;
(2)、acceptor 收到提案后可以接受(accept)提案;
(3)、learner 只能"学习"被批准的提案;
我们对于client/server的角色较为熟悉,而在Paxos算法中可以简单的认为,Client其实承担了Proposer和Learner的作用,而Server则扮演Acceptor的角色。
算法保证一致性的基本语义:
(1)、决议(value)只有在被proposers提出后才能被批准(未经批准的决议称为"提案(proposal)");
(2)、在一次Paxos算法的执行实例中,只批准(chosen)一个value;
(3)、learners只能获得被批准(chosen)的value;
有上面的三个语义可演化为四个约束:
(1)、P1: 一个acceptor必须接受(accept)第一次收到的提案;
(2)、P2a:一旦一个具有value v的提案被批准(chosen),那么之后任何acceptor 再次接受(accept)的提案必须具有value-v;
(3)、P2b:一旦一个具有value v的提案被批准(chosen),那么之后任何proposer 提出的提案必须具有value-v;
(4)、P2c:如果一个编号为n的提案具有value-v,那么存在一个多数派,要么他们中所有人都没有接受(accept)编号小于n的任何提案,要么他们已经接受(accpet)的所有编号小于n的提案中编号最大的那个提案具有value-v;
算法(决议的提出与批准)主要分为:prepare阶段、预批准阶段、accept批准阶段,接下来展开来看。
1. prepare阶段
(1). 当Porposer希望提出方案V1,首先发出Proposal请求至大多数Acceptor。Proposal请求内容为序列号
; (2). 当Acceptor接收到Proposal请求
时,检查自身上次回复过的Proposal请求
a). 如果SN2>SN1,则忽略此请求,直接结束本次批准过程;
b). 否则检查上次批准的accept请求(SNx,Vx),并且回复(SNx,Vx);如果之前没有进行过批准,则简单回复OK;
2. accept预批准阶段/Promise阶段
(1a)、(处理方案一)经过一段时间,收到一些Acceptor回复,回复可分为以下几种:
a)、回复数量满足多数派,并且所有的回复都是,则Porposer发出accept请求,请求内容为议案(SN1,V1); b)、回复数量满足多数派,但有的回复为:(SN2,V2),(SN3,V3)……则Porposer找到所有回复中超过半数的那个,假设为(SNx,Vx),则发出accept请求,请求内容为议案(SNx,Vx);
c)、回复数量不满足多数派,Proposer尝试增加序列号为SN1+,转1继续执行;(1b)、(处理方案二)经过一段时间,收到一些Acceptor回复,回复可分为以下几种:
a)、回复数量满足多数派,则确认V1被接受;
b)、回复数量不满足多数派,V1未被接受,Proposer增加序列号为SN1+,转1继续执行;
3、accept最终批准阶段
(1)、在不违背自己向其他proposer的承诺的前提下,acceptor收到accept 请求后即接受并回复这个请求。
(2)、否则拒绝这个Proposal,由于已经批准其他Proposal,则剩余被拒绝的Proposer需要进行学习
通过Paxos算法流程,反复prepare-accept来达到一致要求,最终确定出一个值。
单纯提到Paxos只是一个算法、理论基础,包含一整套处理流程,它需要的是对应于这套算法的实现。我们熟知的Zookeeper、PhPaxos都在实现过程中运用到了该算法并且都有变化。但不要将Paxos与侠义的分布式事务划等号。
Paxos算法一个典型的场景是,在一个分布式数据库系统中,如果各节点的初始状态一致,每个节点都执行相同的操作序列,那么他们最后能得到一个一致的状态。分布式系统中一般是通过多副本来保证可靠性,而多个副本之间会存在数据不一致的情况。所以必须有一个一致性算法来保证数据的一致。
也被称为“拜占庭容错”、“拜占庭将军问题”。是Leslie Lamport(2013年的图灵讲得主)用来为描述分布式系统一致性问题(Distributed Consensus)在论文中抽象出来一个著名的例子。
这个例子大意是这样的:
拜占庭帝国想要进攻一个强大的敌人,为此派出了10支军队去包围这个敌人。这个敌人虽不比拜占庭帝国,但也足以抵御5支常规拜占庭军队的同时袭击。这10支军队在分开的包围状态下同时攻击。他们任一支军队单独进攻都毫无胜算,除非有至少6支军队(一半以上)同时袭击才能攻下敌国。他们分散在敌国的四周,依靠通信兵骑马相互通信来协商进攻意向及进攻时间。困扰这些将军的问题是,他们不确定他们中是否有叛徒,叛徒可能擅自变更进攻意向或者进攻时间。在这种状态下,拜占庭将军们才能保证有多于6支军队在同一时间一起发起进攻,从而赢取战斗?
拜占庭将军问题中并不去考虑通信兵是否会被截获或无法传达信息等问题,即消息传递的信道绝无问题。Lamport已经证明了在消息可能丢失的不可靠信道上试图通过消息传递的方式达到一致性是不可能的。所以,在研究拜占庭将军问题的时候,已经假定了信道是没有问题的.
问题分析
单从上面的说明可能无法理解这个问题的复杂性,我们来简单分析一下:
叛徒发送前后不一致的进攻提议,被称为“拜占庭错误”,而能够处理拜占庭错误的这种容错性称为「Byzantine fault tolerance」,简称为BFT。
相信大家已经可以明白这个问题的复杂性了。
Paxos算法在出现竞争的情况下,其收敛速度很慢,甚至可能出现活锁的情况,例如当有三个及三个以上的proposer在发送prepare请求后,很难有一个proposer收到半数以上的回复而不断地执行第一阶段的协议。如下图:
通过选取主Proposer,就可以保证Paxos算法的活性。选择一个主Proposer,并规定只有主Proposer才能提出议案。这样一来,只要主Proposer和过半的Acceptor能够正常进行网络通信,那么肯定会有一个提案被批准(第二阶段的accept),则可以解决死循环导致的活锁问题。
参考:
Paxos-->Fast Paxos-->Zookeeper分析
Zookeeper系列(3)--Paxos算法的原理及过程透彻理解
zookeeper面试题
分布式系统理论进阶 - Paxos
Paxos 算法浅析