Paxos算法 ZAB协议

前言

    我们都知道之前所做的项目大部分都是集中式,单个节点的系统,而现在随着微服务和分布式的兴起,大部分企业慢慢的把集中式的系统拆分成各个服务,保证各个服务的模块单一,轻量级。微服务和分布式所带来的好处在于各个服务比较轻,每次修改功能只需要发布负责特定的服务即可。分布式所带来的更严重的问题就是数据的一致性,各个服务之间怎么保证数据的一致性。针对分布式一致性问题,现在有许多著名的理论,2PC、3PC、BASE、Paxos等经典理论,还有在Paxos算法上进行改进的ZAB协议。


1.事务的ACID特性

    事务是由一些列对系统数据进行访问和修改的操作,所组成的一个程序执行逻辑单元,从狭义上来说就是指数据库事务,我们都清楚数据库事务由四大特性:原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability),也就是我们常说的ACID特性

        原子性(Atomicity)

        原子性是指多个sql语句组成一个整体,要么全部执行成功,要么全部不执行。任何一个sql执行失败都导致整个事务失败,只有所有的操作全部成功,整个事务才算成功

        一致性(Consistency)

        一致性是指一个事务在执行之前和执行之后,数据库都必须处于一致性状态。就比如:银行转账业务,A账户有10元,B账户有10元,总共是20元,然后A账户转账5元给B账户,那么A账户就变成5元,B账户变为15元,总共还是20元,不能出现A账户减成功,而B账户增失败,需要保证总账不变,数据的一致性

        隔离性(Isolation)

        事务的隔离性是指数据库在并发的环境下,并发的事务是相互隔离的,一个事务的执行不能被其他事务干扰,在标准的SQL规范中,又定义了4个隔离级别,如读未提交、不可重复读、可重复读、串行化。这里不做介绍了,后面单独抽一讲MVCC多版本并发控制来说明

        持久性(Durability)

        事务的持久性是指一个事务一旦提交,那么它在数据库中是持久有效的

    上面简单介绍了事务的ACID特性,在单机的系统中,我们很容易实现ACID的事务处理,因为事务都在一个系统中,事务可以得到很好的控制。但是在分布式的环境下,事务处理具有非常大的挑战。后面出现了经典的CAPBASE等分布式理论


2.CAP/BASE理论

    CAP和BASE理论是分布式系统经典理论,而BASE理论是在CAP理论上进行改进的。

        CAP理论

        CAP理论是由一致性(Consistency)、可用性(Availability)、分区容错性(Partition tolerance)组成的。同时也告诉我们一个分布式系统不能同时满足这三项,只能满足其中的两项

        一致性

        在分布式环境中,一致性是指数据在多个分布式节点中要能保持一致的特性。假如客户端对其中一个节点的数据进行了更新操作,却没有使得其它节点的数据得到更新,这样就会出现各个节点之间的数据不一致,会导致客户端读到脏数据,如果能使其它节点的数据得到相应的更新,保证客户端每次读到都是最新的数据,那么这样的系统就被认为具有强一致性

        可用性

        可用性是指系统只要不是宕机的情况下,那么该系统必须24小时对用户的每一个操作请求提供服务,在有限的时间对请求作出响应,那么就称这样的系统就用高可用性

        分区容错性

        分区容错性是指分布式系统在遇到任何网络分区故障的时候,仍然需要保证系统对外提供满足一致性和可用性服务,只要不是整个网络环境发生了故障。就好比我们部署了三个网络环境,一个在北京,一个在上海,还是一个在深圳。部署在北京的机房网络出现故障,不影响到上海和深圳的机房,上海和深圳的服务器还是能够对外提供服务

        下面我们来简单讲解一下为什么CAP理论中,只能保证其中两个。上面一张是一个简单的分布式系统,两两之间互相通信。我们首先看一下能否CAP三个都满足的话,是什么样的。假设A,B,C三个服务各自保存了用户信息数据,现在用户对A发起了更新用户信息操作,那么服务A更新成功后,需要同步一下服务B和服务C,将服务B和服务C中的用户信息也进行更新,那么在服务A同步的过程中,可能就会出现用户对服务B发起读操作,那么用户可能就会读到还没有更新的数据(脏数据),也就会出现数据不一致,不满足C(一致性)。如果要满足C的话,那就是在同步的过程中,整个分布式系统对外不提供服务,等数据同步完成后,再对外提供服务,这样就能满足C(一致性)了,但是这样又不满足A(可用性),因为系统在这段时间内不对外提供服务。如果要同时满足CA的话,我们可以将三个服务做成一个系统,也就是不想要进行同步操作,这样的话数据在一个系统中,这样即能满足一致性,又能对外提供服务,但是这样的话因为做成了一个系统,也就不存在分布式的概念,也就是不满足P(分区容错性),已经没有了分区的概念了。所以一个分布式系统至多满足其中的两项

        舍弃P,满足CA

        上面已经讲过如果要满足CA,那么系统就是单节点的,集中式的。不是分布式的概念了,而在分布式系统中,往往要满足P(分区容错性),一个网络分区出现问题,其他网络分区还是能够对外提供服务,而一个网络分区出现问题,从而影响到整个网络环境的话,那么代价太大了,所以舍弃P往往是不可取的

        舍弃A,满足CP

        舍弃A,满足CP在上面也提到过,也就是同步过程中,这个系统对外不提供服务,等系统同步完成后,再对外提供服务。这样的话就是舍弃A,满足了C

        舍弃C,满足AP

        舍弃C,满足AP的话,也就是允许用户读到旧数据,用户读到旧数据,对我们的系统也没有太大的影响,那么这样就是舍弃C,满足了A

    一个分布式系统基本上要保证P,那么只能在CA做取舍了,具体是舍弃C还是A的话,可以由具体的业务而定,如果我们的业务需要保证强一致性的话,那么我们就可以舍弃A可用性,来保证C了。而如果我们的系统对数据的一致性要求不高,那么我可以舍弃C,来保证系统的可用性A了。像zookeeper就是保证了CP,舍弃了A。像eureka就是保证AP,舍弃了C。针对CAP,后面又出现BASE理论


    BASE理论

    Base理论是在CAP理论上进行改进的,对CAP中的一致性和可用性权衡的结果,既然CAP中无法做到强一致性和高可用性,那么可以采用适当的方式使系统达到最终一致性和基本可用的特点。BASE理论是Basically Available(基本可用)、Soft State(软状态)、Eventually consistent(最终一致性)组成的,Base理论则是通过牺牲强一致性来获得可用性,允许数据在一段时间内是不一致的,但最终达到一致的状态

        基本可用

        还是拿上面的同步的案例来说,之前我们讨论的是舍弃A,系统无法对外提供服务,我们大可不必将整个系统的功能置为不可用,而是允许牺牲部分功能的可用性,其他的功能模块还是能对外提供服务,来保证系统的基本可用。系统的基本可用体现在两个方面,一个是请求响应时间上的损失,也就是用户获取用户信息的请求可能处理时间比较长,需要等同步操作完成。这样系统还是能对外提供服务。还有一种就是功能上的损失,也就是在同步的过程中,将获取用户信息的接口禁用掉,不对外提供服务,将用户的请求引导到一个降级页面中,但是系统还是对外提供服务的,保证了系统的基本可用

        软状态

        软状态是指系统中的数据存在中间状态,即允许系统在不同节点的数据副本之间进行数据同步的过程存在延时

        最终一致性

        最终一致性跟强一致性不同的是,最终一致性强调的是系统中所有的数据副本,在经过一段时间的同步后,最终能够达到一个一致的状态,本质是需要系统最终数据能够达到一致,而不需要实时保证系统数据的强一致性


3.一致性协议2PC和3PC协议

    为了解决分布式一致性的问题,涌现了一批经典的一致性协议和算法,最著名的就是2PC、3PC和Paxos算法了。

        2PC

        2PC又被称为二阶段提交,常常被用来解决分布式事务的一种方案。首先2PC协议中有两类角色:协调者、参与者。协调者负责提交事务请求,并发出事务提交还是回滚操作给参与者执行。而参与者对协调者发送的事务请求作出响应,反馈Yes或No,最后执行协调者发出的commit或rollback操作。2PC是将事务的提交过程分成了两个阶段来进行处理,分为提交事务请求、执行事务请求

        阶段一:提交事务请求

        由协调者向所有的参与者发出事务内容,询问协调者是否可以执行事务提交操作,如果协调者执行了事务操作,并会将事务执行记录保存到Undo和Redo中。如果协调者成功执行了事务操作,那么就会发送Yes响应给协调者,表明可以执行。而如果执行失败,则会发送No响应给协调者,表明不可以执行

        总的来说,阶段一就是协调者向参与者发出事务请求,由参与者投票表明该事务请求是否可以执行

        阶段二:执行事务提交

        阶段一说到参与者会对事务请求作出响应,假设协调者从参与者那里收到的反馈都是Yes,即表明可以执行事务操作,那么协调者则会向参与者发送commit请求,参与者接受到commit请求后,并会执行事务提交操作,释放整个事务期间占用的事务资源。假设协调者从参与者那里收到了一个No反馈响应,那么协调者并会向参与者发送rollback请求,而参与者接受到了rollback请求,并会从之前记录的Undo日志中进行操作的回滚

        整体上讲,阶段二就是协调者向参与者发送commit或rollback请求,参与者接受到了相应的请求,作出相应的操作

        优缺点:2PC协议优点是原理简单,实现起来也方便,缺点就是:

                同步阻塞问题:在阶段二时,参与者一直是等待协调者发出commit或rollback请求,参与该事务操作的逻辑都处于阻塞状态,占用事务资源

                单点问题:过度依赖与协调者,如果在阶段二,协调者出现故障,那么参与者就会一直处于锁定事务资源的状态中,无法继续完成事务操作

                数据不一致问题:在阶段二中,协调者向参与者发送commit请求时,有些参与者可能因为网络原因,没有收到commit请求,就会出现有些参与者执行了commit操作,有些参与者没有执行commit操作,于是整个分布式系统中就会出现数据不一致现象

                太过保守:在阶段一中,协调者向参与者发送事务请求,并会等待事务反馈,如果有些参与者出现故障没有发送反馈,这时协调者只能依靠超时机制来判断是否需要中断事务,任意节点的失败都会导致整个事务的失败,像Paxos和ZAB都是采用少数服从多数来解决,并不需要获得所有人的同意

        3PC

        3PC是2PC的改进版,被称为三阶段提交协议。将2PC的提交事务请求过程分为了两部分,形成了CanCommit、PreCommit、doCommit三个阶段组成的协议。CanCommit和doCommit有点类似于2PC中的两个阶段,只是增加了一个PreCommit预提交事务阶段

        阶段一:CanCommit

        还是由协调者向参与者发送提交事务请求,等待参与者的响应,参与者收到来自协调者发送的canCommit请求后,并反馈Yes或No,表明事务请求是否可以被执行,这个阶段跟2PC的阶段一意思差不多

        阶段二:PreCommit

        该阶段是事务的预提交操作,也就是会根据阶段一参与者反馈的Yes还是No,来决定是否可以进行事务的PreCommit操作,如果参与者反馈的都是Yes,那么协调者则会发出preCommit请求,执行事务的预提交,并将Undo和Redo信息记录到事务日志中。如果有一个参与者响应的是No,那么协调者则会发送abort请求,参与者无论是收到协调者的abort请求或者是在等待协调者请求过程中超时,参与者都会中断事务

        阶段三:doCommit

        阶段三有点类似2PC中的阶段二,就是真正的执行事务提交操作或者是事务回滚操作。协调者向参与者发送doCommit或者是abort请求,参与者接受到了doCommit或者是abort请求判断是否执行事务提交还是事务的中断操作。需要注意的是,一旦进入阶段三,如果协调者出现问题或者协调者与参与者之间的网络出现故障,那么参与者都会在等待超时之后,继续执行事务的提交

        优缺点:优点是降低了参与者的阻塞范围,参与者不会一直被阻塞,假如了超时机制,如果参与者一直没有收到协调者的PreCommit请求,则会自己中断事务,并在阶段三中,如果协调者宕机,参与者依然会进行事务的提交,解决了2PC中的单点问题。缺点就是在阶段三协调者出现问题,参与者依然执行事务的提交,这必然会出现数据的不一致性


4.Paxos算法

    Paxos算法是一种一致性算法,在该算法中,有三种参与的角色:Proposer(提议者)、Acceptor(表决者)、Learner(同步者)

        Proposer(提议者):每次事务提案的发起者,相当于国会上的议员

        Acceptor(表决者):提案的表决者,是否批准该提案,相当于国会上的代表,对提案进行投票表决

        Learner(同步者):提案的记录者,相当于国会上的记录员,记录每次选定的提案

    Paxos算法的一致性主要体现在下面几个方面:

        1. 每个Proposer提案者在提出提案时,都会首先获取一个全局唯一性的、递增的提案编号N

        2. 每次表决者在accept某提案后,会将该提案的编号N记录在本地,这样表决者中会存在一个编号最大的提案,其编号假设为maxN。以后每个表决者只会接受编号大于自己本地的maxN的编号提案

        3. 在众多提案中最终只有一个被选定

        4. 一旦一个提案被选定,则其他服务器会主动同步(Learn)该提案到本地

    Paxos算法大致上也可以分为两个阶段,类似于2PC两阶段提交算法的执行过程,但是跟2PC不同的是Paxos采用的少数服从多数,并不需要得到每个Acceptor表决者的同意。过程大致分为:一个是prepare准备阶段与accept接受阶段

        阶段一:prepare阶段

        首先Proposer会获取一个全局的提案编号Mn,然后向Acceptor发送编号为Mn的Prepare请求。Acceptor收到了Proposer发送过来的Prepare请求,且编号Mn大于该Acceptor已经响应的所有Prepare请求的编号,那么Acceptor则会将它已经批准过的最大编号的提案作为响应反馈给Proposer,同时不会再接受比Mn小的提案编号了

        阶段二:accept阶段

        在阶段一后,如果Proposer收到来自半数以上的Acceptor发出的批准响应,那么Proposer就会发出一个针对[Mn, Vn]提案的Accept请求给Acceptor。Vn为阶段一Acceptor发出响应的批准过的最大编号的提案。那么Acceptor接受到[Mn, Vn]提案的Accept请求时,并且在此期间没有批准过比Mn还大的提案编号Preprare请求,那么Acceptor则会通过这个提案

    前面大概了解了Paxos算法的核心逻辑,Paxos还存在一个比较严重的问题就是可能会导致"活锁"问题,也就是由两个Proposer提议者相互的争夺提案的执行,导致谁也不让谁,谁都执行不了。具体过程如下:

        假如Proposer1首先发起了提案编号为Mn,并且通过了阶段一,准备执行阶段二accept时,Proposer2这时发起了提案编号为Mn+1,这时Acceptor本地最大的提案编号就会被更新为Mn+1,Proposer1准备执行accept阶段时,发现Acceptor不能通过,因为Acceptor本地最大的提案是Mn+1了,比Mn大了,Proposer1就会重新获取提案编号Mn+2,重新执行,这时候又会导致Proposer2无法执行Accept阶段,Proposer2又会重新获取提案编号,如此反复下去,就会导致谁也执行不了

        解决Paxos算法的活锁问题大概有下面几种方案:

                1. 选择一个主的Proposer,只能由主的Proposer发起提案,这样就避免了多个Proposer相互争夺了

                2. Proposer执行Accept阶段失败时,不要重新获取提案编号重新执行了,直接抛弃该提案

                3. Proposer执行Accept阶段失败时,等待一段时间再新获取提案编号重新执行

    总的来说,Paxos是目前来说最优秀的分布式一致性协议之一,后面要讲的ZAB协议是Paxos算法的一种工业上的实现。Paxos算法支持分布式节点之间的角色的轮换,避免了分布式单点的问题,还解决了2PC中参与者无限等待问题,因为Paxos中的Acceptor不需要一直等待具体的某一个Proposer发出Accept请求,反正无论哪一个Proposer发出的Accept请求都可以得到响应,判断是否可以通过该提案


5.ZAB(zookeeper原子广播协议)

    ZAB协议是专门为Zookeeper设计的一种支持崩溃恢复的原子广播协议,因为当ZAB协议中的Leader角色崩溃时,ZAB协议中有Leader重新选举功能。ZAB协议中存在着三种角色:Leader(领导者)、Follower(跟随者)、Observer(观察者)

        Leader(领导者):事务请求的唯一处理者,负责发出事务Proposal,也可以处理客户端的读请求

        Follower(跟随者):对Leader发出的事务Proposal具有投票权,将客户端写的事务请求转发给Leader,处理客户端的读请求,并且可以参与Leader的选择

        Observer(观察者):用于提高Follower的读性能,当读请求并发量上来的时候,Observer可以分担一部分的读请求,有人可能会这么想,我们也可以增加Follower节点的数量,来扛住并发量,如果我们一旦增加Follower节点数量的话,那么事务Proposal的投票表决和Leader的选举的效率就会降低。Observer不参与Leader的选举

    ZAB协议优点类似于Paxos算法,算是一种Paxos算法在工业上的实现。ZAB协议包括两种基本的模式,分别是崩溃模式和消息广播

    消息广播

        ZAB协议的消息广播有点类似于一个二阶段提交过程。Zookeeper使用了一个单一的主进程来接收并处理客户端的所有事务请求,这个消息广播协议是基于FIFO特性的队列,将需要广播的事务Proposal依次放入队列中,并且采用TCP协议进行网络通信的

        1. 针对客户端的事务请求,Leader服务器会为其生成对应的事务Proposal,并为该事务Proposal分配一个全局递增的唯一ZXID,ZXID由64位组成,其中高32位表示epoch(每个Leader的周期,年号),低32位表示xid事务id,然后将事务Proposal发送给Follower进行投票表决

        2. Follower接受到了事务Proposal请求后,并会对其进行投票,返回ACK响应。

        3. 如果Leader收到半数以上的ACK响应后,就会广播一个Commit消息给Follower通知其进行事务提交

    在消息广播的过程中,可能会出现Leader崩溃的场景,因此ZAB协议中添加了崩溃恢复模式来解决这个问题,像之前说的2PC和3PC,包括Paxos算法都不存在崩溃恢复模式

    崩溃恢复

        以前说到如果在消息广播模式中,如果一旦出现Leader服务器崩溃,那么就会进入到崩溃恢复模式,在ZAB协议中,为了保证程序的正常运行,需要重新在Follower中选举一个新的Leader

        Leader选举

        Leader选举一般会出现在两个场景下,一个是服务器初始化启动,还有一个就是Leader服务器崩溃。需要在集群中选举一个Leader,这种选举状态被称为"LOOKING",当服务器处于LOOKING状态的时候,那么它就会向集群中所有机器发送消息,称这个消息为"投票"。消息需要包含两个最基本的信息:一个是所举荐的服务器的SID和ZXID,分别表示被举荐服务器的唯一标识和事务ID。SID也就是我们在启动zookeeper进行设置的myid。如果服务器要举荐SID为1,ZXID为8的服务器称为Leader,那么投票信息就是(1, 8)

        选举规则:首先会选举ZXID最大的服务器称为Leader,因为ZXID最大代表事务是最新的,数据是最新的,如果ZXID相等的话,那么SID较大的服务器就会成为Leader

        1. 假设现在有三台服务器,分别SID是1,2,3. 三台服务器对应的ZXID分别是9,8,8。在第一次投票的时候,都会投票给自己,再将自己的投票信息分别发送给其他机器,投票信息为:(1, 9), (2, 8), (3, 8)

        2. 当机器1接收到另外两台机器发送过来的投票信息(2, 8), (3, 8),发现他们的ZXID都没有自己的ZXID=9大,那么不会修改投票结果,坚持投票给自己

        3. 当机器2接受到(1, 9), (3, 8)时,假设先收到(3, 8),发现它的ZIXD跟自己的ZXID=8相等,然后就会比较SID,发现它的SID比自己的SID=2大,那么就会投票给机器3了,后面再接受到了(1, 9)发现比机器3的ZXID=8要大,后面又会修改投票结果,将投票投给机器1

        4. 当机器3接受到(1, 9), (2, 8),假如先收到(2, 8),发现它的ZXID跟自己的ZXID=8相等,然后比较SID,发现比自己的小,那么不会修改自己的投票结果,坚持投票给自己,后面再接受到了(1, 9)发现比自己的ZXID=8要大,后面又会修改投票结果,将投票投给机器1

        最后,三台机器都投给机器1,获得的投票数超过了半数,即机器1就会当选为Leader服务器

        数据同步

        崩溃恢复模式中,当Leader选举完成之后,要进行数据同步,因为事务日志可能存在还没有被提交的事务,即是否完成数据同步。

        1. 首先Leader服务器会首先确认事务日志中的所有Proposal是否都已经被集群中过半的机器提交了

        2. 如果没有的话,Leader服务器会向那么没有被Follower服务器同步的事务以Proposal消息的形式逐个发送给Follower服务器,并在每一个Proposal消息后面紧接着再发送一个Commit消息,表示该事务已经被提交

        3. 等到Follower服务器将所有尚未同步的事务Proposal同步过来成功应用到本地数据库后,Leader服务器就可以接受客户端的事务请求了,然后提出新的提案了

    总的来说,ZAB协议提交事务提案的过程跟Paxos有点类似,都是由Leader发送给下面的Follower,让Follower进行投票表决,都是超过半数以上才会通过。但是ZAB协议比Paxos多的是崩溃恢复模式,也就是Leader崩溃时,能够自我恢复。所以ZAB协议跟Paxos算法最主要的区别就是:两个设计的目标不同,ZAB协议主要用于构建一个高可用的分布式数据的主备系统,因为有崩溃恢复,Leader崩溃能够重新选举,达到一个高可用的目的。而Paxos算法目的在于构建一个分布式数据一致性系统,强调的是数据的一致性,当Proposer提议者崩溃时不能自我恢复,从而丢失高可用的功能

****

    本篇较以分布式的理论为主,讲解都是理论知识,其中CAP, 2PC是我们必须要掌握的,因为CAP和2PC在分布式中是最基础的理论知识,后面还讲解了比较难懂的Paxos算法和ZAB协议,两者最主要的区别:设计目标不同,Paxos算法强调的是一致性,而ZAB协议强调的是高可用的系统

posted @ 2020-07-05 12:09  半分、  阅读(820)  评论(1编辑  收藏  举报