分布式一致性的paxos算法

一致性协议

Paxos是一个一致性协议。什么叫一致性?一致性有很多种,从强到弱分了很多等级,如线性一致性、因果一致性、最终一致性,等等。什么是一致?这里举个例子,三台机器,每台机器的磁盘存储为128字节,如果三台机器这128字节数据都完全相同,那么可以说这三台机器是磁盘数据是一致的,更为抽象地说,就是多个副本确定同一个值,大家记录下来同一个值,那么就达到了一致性。

Paxos能达到什么样的一致性级别?这是一个较为复杂的问题。因为一致性往往不取决于客观存在的事实,如3台机器虽然拥有相同的数据,但是数据的写入是一个过程,有时间的先后,而更多的一致性取决于观察者,观察者看到的并未是最终的数据。这里就先不展开讲,先暂且认为Paxos保证了写入的最终一致性。

为何说是一个协议而不是一个算法,可以这么理解,算法是设计出来服务于这个协议的,如同法律是协议,那么算法就是各种机构的执行者,使得法律的约束能得到保证。

Paxos的协议其实很简单,就三条规定。我认为这三条规定也是Paxos最精髓的内容,各个执行者奋力去保护这个协议,使得这个协议的约束生效,自然就得到了一致性。

分布式环境

为何要设计出这么一套协议,其他协议不行么?如最容易想到的,一个值A,往3台机器都写一次,这样一套简单的协议,能不能达到一致性的效果?这里就涉及到另外一个概念,Paxos一致性协议是在特定的环境下才需要的,这个特定的环境称为异步通信环境。而恰恰,几乎所有的分布式环境都是异步通信环境,在计算机领域面对的问题,非常需要Paxos来解决。

异步通信环境指的是消息在网络传输过程中,可能发生丢失、延迟、乱序现象。在这种环境下,上面提到的三写协议就变得很鸡肋了。消息乱序是一个非常恶劣的问题,这个问题导致大部分协议在分布式环境下都无法保证一致性,而导致这个问题的根本原因是网络包无法控制超时,一个网络包可以在网络的各种设备交换机等停留数天,甚至数周之久,而在这段时间内任意发出的一个包,都会跟之前发出的包产生乱序现象。无法控制超时的原因更多是因为时钟的关系,各种设备以及交换机时钟都有可能错乱,无法判断一个包的真正到达时间。

异步通信环境并非只有Paxos能解决一致性问题,经典的两阶段提交也能达到同样的效果,但是分布式环境里面,除了消息网络传输的恶劣环境,还有另外一个让人痛心疾首的,就是机器的当机,甚至永久失联。在这种情况下,两阶段提交将无法完成一个一致性的写入,而Paxos,只要多数派机器存活就能完成写入,并保证一致性。

至此,总结一下Paxos就是一个在异步通信环境,并容忍在只有多数派机器存活的情况下,仍然能完成一个一致性写入的协议。

提议者

前面讲了这么多都是协议协议,在分布式环境当中,协议作用就是每台机器都要扮演一个角色,这个角色严格遵守这个协议去处理消息。在Paxos论文里面这个角色称之为Acceptor,这个很好理解。大家其实更关心另外一个问题,到底谁去发起写入请求,论文里面介绍发起写入请求的角色为提议者,称为Proposer。Proposer也是严格遵守paxos协议,通过与各个Acceptor的协同工作,去完成一个值的写入。在Paxos里面,Proposer和Acceptor是最重要的两个角色。

确定一个值

既然说到写入数据,到底怎么去写?写一次还是写多次,还是其他?这也是我一开始苦恼的问题,相信很多人都会很苦恼。

这里先要明确一个问题,Paxos到底在为谁服务?更确定地说,到底在为什么数据服务?还是引上面的例子,Paxos就是为这128字节的数据服务,Paxos并不关心外面有多少个提议者,写入了多少数据,写入的数据是不是一样的,Paxos只会跟你说,我确定了一个值,当这个值被确定之后,也就是这128字节被确定了之后,无论外面写入什么,这个值都不会改变再改变了,而且三台机确定的值肯定是一样的。

说到这估计肯定会有人懵了,说实话我当时也懵了。我要实现一个存储服务啊,我要写入各种各样的数据啊,你给我确定这么一个值,能有啥用?但先抛开这些疑问,大家先要明确这么一个概念,Paxos就是用来确定一个值用的,而且大家这里就先知道这么个事情就可以了,具体Paxos协议是怎样的,怎么通过协议里面三条规定来获得这样的效果的,怎么证明的等等理论上的东西,都推荐去大家去看看论文,但是先看完本文再看,会得到另外的效果。

如下图,有三台机器(后面为了简化问题,不做特别说明都是以三台机器作为讲解例子),每台机器上运行这Acceptor来遵守paxos协议,每台机器的Acceptor为自己的一份Data数据服务,可以有任意多个Proposer。当Paxos协议宣称一个值被确定(Chosen)后,那么Data数据就会被确定,并且永远不会被改变。
这里写图片描述

Proposer只需要与多数派的Acceptor交互,即可完成一个值的确定,但一旦这个值被确定下来后,无论Proposer再发起任何值的写入,Data数据都不会再被修改。Chosen value即是被确定的值,永远不会被修改。

确定多个值

对我们来说,确定一个值,并且当一个值确定后是永远不能被修改的,很明显这个应用价值是很低的。虽然我都甚至还不知道确定一个值能用来干嘛,但如果我们能有办法能确定很多个值,那肯定会比一个值有用得多。我们先来看下怎么去确定多个值。

上文提到一个三个Acceptor和Proposer各自遵守paxos协议,协同工作最终完成一个值的确定。这里先定义一个概念,Proposer,各个Acceptor,所服务的Data共同构成了一个大的集合,这个集合所运行的paxos算法最终目标是确定一个值,我们这里称这个集合为一个Paxos实例。

一个实例可以确定一个值,那么多个实例自然可以确定多个值,很简单的模型就可以构建出来,只要我们同时运行着多个实例,那么我们就能完成确定多个值的目标。

这里强调一点,每个实例必须是完全独立,互不干涉的。意思就是说Acceptor不能去修改其他实例的Data数据,Proposer同样也不能跨越实例去与其他实例的Acceptor交互。

如下图,三台机器每台机器运行两个实例,每个实例独立运作,最终会产生两个确定的值。这里两个实际可以扩展成任意多个。

这里写图片描述

至此,实例成为了我现在介绍Paxos的一个基本单元,一个实例确定一个值,多个实例确定多个值,但各个实例独立,互不干涉。

然而比较遗憾的一点,确定多个值,仍然对我们没有太大的帮助,因为里面最可恨的一点是,当一个值被确定后,就永远无法被修改了,这是我们不能接受的。大部分的存储服务可能都需要有一个修改的功能。

有序确定多个值

我们需要转换一下切入点,也许我们需要Paxos确定的值,并不一定是我们真正看到的数据。我们观察大部分存储系统,如LevelDB,都是以AppendLog的形式,确定一个操作系列,而后需要恢复存储的时候都可以通过这个操作系列来恢复,而这个操作系列,正是确定之后就永远不会被修改的。到这已经很豁然开朗了,只要我们通过Paxos完成一个多机一致的有序的操作系列,那么通过这个操作系列的演进,可实现的东西就很有想象空间了,存储服务必然不是问题。

如何利用Paxos有序的确定多个值?上文我们知道可以通过运行多个实例来完成确定多个值,但为了达到顺序的效果,需要加强一下约束。

首先给实例一个编号,定义为i,i从0开始,只增不减,由本机器生成,不依赖网络。其次,我们保证一台机器任一时刻只能有一个实例在工作,这时候Proposer往该机器的写请求都会被当前工作的实例受理。最后,当编号为i的实例获知已经确定好一个值之后,这个实例将会被销毁,进而产生一个编号为i+1的实例。

基于这三个约束,每台机器的多个实例都是一个连续递增编号的有序系列,而基于Paxos的保证,同一个编号的实例,确定的值都是一致的,那么三台机都获得了一个有序的多个值。

下面结合一个图来详细说明一下这个运作过程,以及存在什么异常情况以及异常情况下的处理方式。

这里写图片描述

图中A,B,C代表三个机器,红色代表已经被销毁的实例,根据上文约束,最大的实例就是当前正在工作的实例。A机器当前工作的实例编号是6,B机是5,而C机是3。为何会出现这种工作实例不一样的情况?首先解释一下C机的情况,由于Paxos只要求多数派存活即可完成一个值的确定,所以假设C出现当机或者消息丢失延迟等,都会使得自己不知道3-5编号的实例已经被确定好值了。而B机比A机落后一个实例,是因为B机刚刚参与完成实例5的值的确定,但是他并不知道这个值被确定了。上面的情况与其说是异常情况,也可以说是正常的情况,因为在分布式环境,发生这种事情是很正常的。

下面分析一下基于图示状态的对于C机的写入是如何工作的。C机实例3处理一个新的写入,根据Paxos协议的保证,由于实例3已经确定好一个值了,所以无论写入什么值,都不会改变原来的值,所以这时候C机实例3发起一轮Paxos算法的时候就可以获知实例3真正确定的值,从而跳到实例4。但在工程实现上这个事情可以更为简化,上文提到,各个实例是独立,互不干涉的,也就是A机的实例6,B机的实例5都不会去理会C机实例3发出的消息,那么C机实例3这个写入是无法得到多数派响应的,自然无法写入成功。

再分析一下A机的写入,同样实例6无法获得多数派的响应,同样无法写入成功。同样假如B机实例5有写入,也是写入失败的结果,那如何使得能继续写入,实例编号能继续增长呢?这里引出下一个章节。

实例的对齐(Learn)

上文说到每个实例里面都有一个Acceptor的角色,这里再增加一个角色称为Learner,顾名思义就是找别人学习,她回去询问别的机器的相同编号的实例,如果这个实例已经被销毁了,那说明值已经确定好了,直接把这个值拉回来写到当前实例里面,然后编号增长跳到下一个实例再继续询问,如此反复,直到当前实例编号增长到与其他机器一致。

由于约束里面保证仅当一个实例获知到一个确定的值之后,才能编号增长开始新的实例,那么换句话说,只要编号比当前工作实例小的实例(已销毁的),他的值都是已经确定好的。所以这些值并不需要再通过Paxos来确定了,而是直接由Learner直接学习得到即可。

这里写图片描述

如上图所示,B机的实例5是直接由Learner从A机学到的,而C机的实例3-5都是从B机学到的,这样大家就全部走到了实例6,这时候实例6接受的写请求就能继续工作下去。

状态机

一个有序的确定的值,也就是日志,可以通过定义日志的语义进行重放的操作,那么这个日志是怎么跟Paxos结合起来的呢?我们利用Paxos确定有序的多个值这个特点,再加上这里引入的一个状态机的概念,结合起来实现一个真正有工程意义的系统。

状态机这个名词大家都不陌生,一个状态机必然涉及到一个状态转移,而Paxos的每个实例,就是状态转移的输入,由于每台机器的实例编号都是连续有序增长的,而每个实例确定的值是一样的,那么可以保证的是,各台机器的状态机输入是完全一致的。根据状态机的理论,只要初始状态一致,输入一致,那么引出的最终状态也是一致的。而这个状态,是有无限的想象空间,你可以用来实现非常多的东西。

如下图这个例子是一个状态机结合Paxos实现了一个具有多机一致的KV系统。

这里写图片描述

实例0-3的值都已经被确定,通过这4个值最终引出(b, ‘jeremy’)这个状态,而各台机器实例系列都是一致的,所以大家的状态都一样,虽然引出状态的时间有先后,但确定的实例系列确定的值引出确定的状态。

下图例子告诉大家Proposer,Acceptor,Learner,State machine是如何协同工作的。

这里写图片描述

一个请求发给Proposer,Proposer与相同实例编号为x的Acceptor协同工作,共同完成一值的确定,之后将这个值作为状态机的输入,产生状态转移,最终返回状态转移结果给发起请求者。

开源地址: https://github.com/tencent-wechat/phxpaxos

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值