分布式系统: 弹性伸缩,高性能,高可用,容错能力
分布式系统本质上是一种并发或并行系统,在单个机器的并行系统中,每个并行任务独立执行并与其他任务互相通信协作,并且由于 共享机器的时钟,不会出现时间和事件的顺序不一致问题。而分布式系统的进程在不同机器上,可能存在有的机器时钟是错误的,这就需要一种逻辑时钟用于区分事件发生的顺序,为分布式系统设计一种状态机,而且分布式系统中有的节点可能发生宕机,网络通信可能异常,这也为分布式系统的设计带来更大的挑战。
分布式系统一致性协议:
熟悉关系型数据库的同学应该很清楚,一个事务的执行流程包括三个阶段:
1. 开始事务,对应SQL的BEGIN;
2. 执行事务,执行SQL语句;
3. 事务提交或回滚,对应SQL的COMMIT或ROLLBACK;
很多关系型数据库引擎的事务是默认提交的,或者根本不支持事务,这也是为什么大部分情况下只需要我们写增删查改的SQL语句,而不需要关注事务。
将事务的执行流程扩展到 分布式系统中,每个服务器节点都只能 知道自己处理的事务结果,而无法获取到其他节点处理的事务结果。如果一个事务需要多个节点处理,为了保持事务的一致性,就需要一个或多个协调者统一调度每个节点的执行逻辑,这些节点称为“协调者”(Coordinator),而其他被协调的节点称为“参与者”(Participant)。协调者通过每个参与者返回的状态来决定最终提交执行事务还是回滚事务。二阶段提交和三阶段提交协议就是基于这个简单的思想。还记得老王约麻将的例子吗?老王和他的朋友们在晚上七点打麻将可以看做是一个分布式事务,而老王作为“协调者”,老陈,老刘和老张则作为“参与者”。老王首先会询问其他三人,每个人都返回确认的消息后,打麻将这件事才会真正发生,也就是事务才会正式提交。
2PC
二阶段提交协议是为了解决分布式架构下事务处理的一致性和原子性而设计的。二阶段协议实现起来也不复杂,而且应用广泛,很多关系型数据库如MySQL的分布式事务处理就是基于二阶段提交协议。
顾名思义,二阶段协议将事务的提交过程分成了两个阶段:
阶段一,提交事务请求
1. 事务询问:协调者询问所有的参与者是否可以执行提交,然后阻塞等待参与者的响应;
2. 参与者执行事务操作:各参与者节点执行事务操作并记录操作日志;
3. 参与者回复协调者:如果参与者节点成功执行了事务操作,则回复YES,否则回复NO。还阶段一就是类似投票的过程,不同的是大家都要投赞成票才能成功,有任何一个人投了反对票就会导致老王约麻将失败,而不是少数服从多数。
如果所有参与者都回复了YES,那么进入阶段二。
阶段 二,执行事务提交
1. 发送提交请求:协调者向所有参与者节点发送提交请求;
2. 事务提交:每个参与者收到协调者的提交请求后都会尝试提交事务;
3. 反馈事务提交结果:每个参与者向协调者反馈自己的事务提交结果;
4. 完成事务:协调者收到了所有参与者的事务提交成功的确认,则提交完成事务;如果任何一个参与者节点回复自己的 事务提交失败,或者该节点等待超时,协调者会向所有参与者节点发送回滚请求,然后每个参与者节点回滚自己的事务,并向协调者反馈回滚结果,协调者收到所有参与者节点回滚成功的消息后,即完成事务回滚。
二阶段协议的优点是理解简单,实现也简单,但是缺点也很明显:
1. 单点故障:系统中任何一个节点,包括协调者节点和参与者节点出现故障,都会导致事务流程失败。如果协调者节点在提交阶段出现了故障,将会导致参与者节点卡死在事务中;
2. 太过保守:任何一个参与者节点故障都会导致协调者判定为事务失败,也可以理解为二阶段提交协议没有完善的容错机制;
3. 同步阻塞:二阶段提交的过程中,各个参与者都会等待其他参与者响应的过程中,无法执行任务操作,这对资源是一种浪费,也严重影响系统性能。
三阶段提交协议
三阶段提交协议是二阶段提交协议的改进版,相对于2PC多了预提交环节,也就是将2PC中提交事务请求阶段分为两个阶段:预提交与执行提交。
阶段一,事务询问:
1. 协调者询问所有参与者是否 可以提交一个事务,然后等待响应;
2. 各个参与者向协调者回复YES or NO
假如协调者收到了所有参与者回复的YES,则进入阶段二。
阶段二,预提交:
1. 协调者 向所有参与者发送预提交请求;
2. 预提交事务:参与者执行事务 操作,并记录日志;
3. 参与者向协调者反馈自己的事务执行结果;
如果任何参与者的事务执行失败,协调者会中断事务。
阶段三,正式提交:
1. 协调者发送正式提交的请求;
2. 参与者执行事务的正式提交,并在提交后释放事务执行期间的资源;
3. 参与者反馈事务提交的结果;
4. 协调者完成事务,或中断事务。
3PC解决了2PC中单点故障引起的阻塞问题,但是也引入了新的问题:如果在预提交阶段网络出现了分区,那么就会导致有的小集群的事务提交,有的小集群事务 回滚,出现了数据不一致的情况。
Paxos协议
本章节将介绍Paxos协议,不过在介绍协议的工作流程之前,首先让我们来了解一下Paxos协议的作者Leslie Lamport。如果你写过论文,那么一定对LaTex不陌生,没错,Lamport也是LaTex的作者。不过他的最大贡献在分布式领域,他发表的一系列关于分布式理论的论文奠定了构建大规模分布式系统的基石,各种计算机领域的大奖拿到手软,比如因1978年发表的论文《分布式系统中的时间,时钟和事件顺序》获得了2013年度图灵奖。大神就是大神,我们只能望其项背。大道至简,接下来将介绍Paxos这个既简洁又功能强大的协议,关于该协议的完整理论证明,有兴趣的同学可以查看Lamport的论文《The Part-time Parliament》(这篇论文看似讲了一个故事,其实很难理解)和 《Paxos Made Simple》。
Paxos协议提出,只要分布式系统中2f + 1个节点中的f + 1个节点可用,那么系统整体就可用,并且能保证数据的强一致性,而对于可用性,Paxos也对其进行了最大的优化:假设单个节点的可用性为P(其实就是一个0~1之间的概率),那么2f + 1个节点中任意组合的f + 1及以上个节点的可用性P(total)是多少呢?这时候,我们大一学过的概率论就派上用场了。
P(total) =
假设P = 0.99,f = 2,那么P(total) = 0.99999015,即在单个节点可用性0.99的由5个节点组成的分布式系统中,系统的整体可用性由2个9提升到了5个9,这也意味着大大减少了基于分布式系统构建的应用的宕机时间,而我们知道,互联网应用每分钟的宕机都有可能导致成千上万的损失 。
Paxos协议把每个数据写请求看做一个提案(Proposal),这里沿用了论文当中的名词,一个提案可以看做包含ID和数据内容的写请求。每个提案会交由提交者(Proposer)来提交,而提交者可以看做是分布式系统中的一个节点。现在有一个委员会,包含了2f + 1个Acceptor,而任何一个提案必须经过这2f + 1个节点中的f + 1个节点批准才会生效。而Acceptor不能什么提案都批准,需要满足两个约束条件:
1. Acceptor必须批准它收到的第一个提案;
2. 如果一个提案的值被大多数Acceptor批准了,那么后续被接受的提案中也必须包含这个值。
Paxos协议也可以划分为两个阶段:
阶段一,
1. Proposer选择一个提案ID为n ,然后向半数以上的Acceptors发送ID为n的prepare请求。
2. 如果一个Acceptor收到一个ID为n的prepare请求,且n大于它已经响应的所有prepare请求的编号,那么它就会保证不会再通过(accept)任何ID小于n的提案,同时将它已经通过的最大ID的提案(如果存在的话)作为响应。
阶段二,
1. 如果Proposer收到来自半数以上的Acceptor对于它的prepare请求的响应,那么它就会发送一个针对自己的ID为n,值为v的提案的accept请求给Acceptors,在这里v是收到的响应中编号最大的提案的值,如果响应中不包含提案,那么它就是任意值。
2. 如果Acceptor收到一个针对编号n的提案的accept请求,只要它还未对编号大于n的prepare请求作出响应,它就可以通过这个提案。
Paxos的整体流程其实就是这些,但是它真正复杂的地方在于各种failure情况下的如何正常工作,即任何消息都有可能丢失,而一致性的保证不依赖任何一个特殊的消息。也就是说,Paxos协议解决了分布式一致性协议中的容错问题。