这一篇主要介绍分布式事务的基础知识,一些基础的算法、定理、简单应用等。下篇文章介绍互联网业界的具体实践方案。
CAP定理是由加州大学伯克利分校Eric Brewer教授提出来的,其核心思想是任何基于网络的数据共享,系统最多只能满足数据一致性(Consistency)、可用性(Availability)和网络分区容忍(Partition Tolerance)三个特性中的两个,三个特性的定义如下:
具体地讲在分布式系统中,在任何数据库设计中,一个Web应用至多只能同时支持上面的两个属性。显然,任何横向扩展策略都要依赖于数据分区。因此,设计人员必须在一致性与可用性之间做出选择。
CA(一致性+可用性)without P(容错性)
单机的Mysql和Oracle;
分布式集群中不存在这种情况,因为多个节点必定考虑主备同步,也就是网络。
CP(一致性+容错性)without A(可用性)
分布式的数据库,如Redis,HBase,Zookeeper
任何时刻对ZooKeeper请求能得到一致的数据结果:当master节点网络故障,会进行选举机制,选举时集群不可用。但是它不能保证每次服务请求的可用性,ZooKeeper可能会丢弃一些请求,消费者程序需要重新请求才能获得结果。
AP(可用性+容错性)without C(一致性)
12306买票
购买的时候提示你是有票的(但是可能实际已经没票了),但是过了一会系统提示你下单失败,余票不足。其实舍弃的只是强一致性。退而求其次保证了最终一致性。
Eureka
各个节点平等;有节点挂掉,会立刻换至其他节点,保证服务可用,只不过查到的信息可能不是最新的。在网络稳定后,当前实例新的注册信息会被同步到其他节点中。
一旦网络问题发生,节点之间可能会失去联系。为了保证高可用,需要在用户访问时可以马上得到返回,导致全局数据的不一致性。
在分布式系统中,我们往往追求的是可用性,它的重要程序比一致性要高,那么如何实现高可用性呢? 前人已经给我们提出来了另外一个理论,就是BASE理论,它是用来对CAP定理进行进一步扩充的。BASE理论指的是:
BASE理论是对CAP中的一致性和可用性进行一个权衡的结果,理论的核心思想就是:我们无法做到强一致,但每个应用都可以根据自身的业务特点,采用适当的方式来使系统达到最终一致性(Eventual consistency)。
Paxos协议由Leslie Lamport最早在1990年提出,由于Paxos在云计算领域的广泛应用Leslie Lamport因此获得了2013年度图灵奖。
Paxos协议提出只要系统中2f+1个节点中的f+1个节点可用,那么系统整体就可用并且能保证数据的强一致性,它对于可用性的提升是极大的,仍然假设单节点的可用性是P,那么2f+1个节点中任意组合的f+1以上个节点正常的可用性P(total)=,又假设P=0.99,f=2,P(total)=0.9999901494,可用性将从单节点的2个9提升到了5个9,这意味着系统每年的宕机时间从87.6小时降到0.086小时,这已经可以满足地球上99.99999999%的应用需求。
Leslie写的两篇论文:《The Part-Time Parliament》和《Paxos Made Simple》比较完整的阐述了Paxos的工作流程和证明过程,Paxos协议把每个数据写请求比喻成一次提案(proposal),每个提案都有一个独立的编号,提案会转发到提交者(Proposer)来提交,提案必须经过2f+1个节点中的f+1个节点接受才会生效,2f+1个节点叫做这次提案的投票委员会(Quorum),投票委员会中的节点叫做Acceptor,Paxos协议流程还需要满足两个约束条件: a)Acceptor必须接受它收到的第一个提案;b)如果一个提案的v值被大多数Acceptor接受过,那后续的所有被接受的提案中也必须包含v值(v值可以理解为提案的内容,提案由一个或多个v和提案编号组成)。
Paxos协议流程划分为两个阶段,第一阶段是Proposer学习提案最新状态的准备阶段;第二阶段是根据学习到的状态组成正确提案提交的阶段,完整的协议过程如下:
阶段 1
Proposer选择一个提案编号n ,然后向半数以上的Acceptors发送编号为 n 的prepare请求。
如果一个Acceptor收到一个编号为n 的prepare请求,且 n 大于它已经响应的所有prepare请求的编号,那么它就会保证不会再通过(accept)任何编号小于 n 的提案,同时将它已经通过的最大编号的提案(如果存在的话)作为响应。
阶段 2
如果Proposer收到来自半数以上的Acceptor对于它的prepare请求(编号为n )的响应,那么它就会发送一个针对编号为 n ,value值为 v 的提案的accept请求给Acceptors,在这里 v 是收到的响应中编号最大的提案的值,如果响应中不包含提案,那么它就是任意值。
如果Acceptor收到一个针对编号n 的提案的accept请求,只要它还未对编号大于 n 的prepare请求作出响应,它就可以通过这个提案。
用时序图来描述Paxos协议如图3所示:
上述Paxos协议流程看起来比较复杂,是因为要保证很多边界条件下的协议完备性,譬如初试值为空、两个Proposer同时提交提案等情况,但Paxos协议的核心可以简单描述为:Proposer先从大多数Acceptor那里学习提案的最新内容,然后根据学习到的编号最大的提案内容组成新的提案提交,如果提案获得大多数Acceptor的投票通过就意味着提案被通过。由于学习提案和通过提案的Acceptor集合都超过了半数,所以一定能学到最新通过的提案值,两次提案通过的Acceptor集合中也一定存在一个公共的Acceptor,在满足约束条件b时这个公共的Acceptor时保证了数据的一致性,于是Paxos协议又被称为多数派协议。
Paxos协议的真正伟大之处在于它的简洁性,Paxos协议流程中任何消息都是可以丢失的,一致性保证并不依赖某个特殊消息传递的成功,这极大的简化了分布式系统的设计,极其匹配分布式环境下网络可能分区的特点,相比较在Paxos协议之前的“两阶段提交(2PC)”也能保证数据强一致性,但复杂度相当高且依赖单个协调者的可用性。
那既然Paxos如此强大,那为什么还会出现ZAB协议?
Paxos协议虽然是完备的,但要把它应用到实际的分布式系统中还有些问题要解决:
在多个Proposer的场景下,Paxos不保证先提交的提案先被接受,实际应用中要保证多提案被接受的先后顺序怎么办?
Paxos允许多个Proposer提交提案,那有可能出现活锁问题,出现场景是这样的:提案n在第二阶段还没有完成时,新的提案n+1的第一阶段prepare请求到达Acceptor,按协议规定Acceptor将响应新提案的prepare请求并保证不会接受小于n+1的任何请求,这可能导致提案n将不会被通过,同样在n+1提案未完成第二阶段时,假如提案n的提交者又提交了n+2提案,这可能导致n+1提案也无法通过。
Paxos协议规定提案的值v只要被大多数Acceptor接受过,后续的所有提案不能修改值v,那现实情况下我还要修改v值怎么办?
ZooKeeper的核心算法ZAB通过一个简单的约束解决了前2个问题:所有提案都转发到唯一的Leader(通过Leader选举算法从Acceptor中选出来的)来提交,由Leader来保证多个提案之间的先后顺序,同时也避免了多Proposer引发的活锁问题。
ZAB协议的过程用时序图描述如图4所示,相比Paxos协议省略了Prepare阶段,因为Leader本身就有提案的最新状态,不需要有提案内容学习的过程,图中的Follower对应Paxos协议中的Acceptor,Observer对应Paxos中的Learner。
ZAB引入Leader后也会带来一个新问题: Leader宕机了怎么办?其解决方案是选举出一个新的Leader,选举Leader的过程也是一个Paxos提案决议过程,这里不展开讨论。
那如何做到提案的值v可以修改呢?这不是ZAB协议的范畴,研究ZooKeeper源码后发现它是这么做的:ZooKeeper提供了一个znode的概念,znode可以被修改,ZooKeeper对每个znode都记录了一个自增且连续的版本号,对znode的任何修改操作(create/set/setAcl)都会促发一次Paxos多数派投票过程,投票通过后znode版本号加1,这相当于用znode不同版本的多次Paxos协议来破除单次Paxos协议无法修改提案值的限制。
从保证一致性的算法核心角度看ZAB确实是借鉴了Paxos的多数派思想,但它提供的全局时序保证以及ZooKeeper提供给用户可修改的znode才让Paxos在开源界大放异彩,所以ZAB的价值不仅仅是提供了Paxos算法的优化实现,也难怪ZAB的作者一直强调ZAB和Paxos是不一样的算法。
CAP理论告诉我们在分布式环境下网络分区无法避免,需要去权衡选择数据的一致性和可用性,Paxos协议提出了一种极其简单的算法在保障数据一致性时最大限度的优化了可用性,ZooKeeper的ZAB协议把Paxos更加简化,并提供全局时序保证,使得Paxos能够广泛应用到工业场景。
两阶段提交协议(Two-phase Commit Protocol,简称 2PC),是分布式事务的核心协议。在此协议中,一个事务管理器(Transaction Manager,简称 TM)协调 1 个或多个资源管理器(Resource Manager,简称 RM)的活动,所有资源管理器向事务管理器汇报自身活动状态,由事务管理器根据各资源管理器汇报的状态(完成准备或准备失败)来决定各资源管理器是“提交”事务还是进行“回滚”操作。
二阶段提交的具体流程如下:
Try-Confirm-Cancel(TCC)是初步操作(Try)、确认操作(Confirm)和取消操作(Cancel)三种操作的缩写,这三种操作的业务含义如下:
TCC 是二阶段提交协议(Two-phase Commit Protocol,简称 2PC)的扩展,Try 操作对应 2PC 中一阶段的准备提交事务(Prepare),Confirm 对应 2PC 中二阶段事务提交(Commit),Cancel 对应 2PC 中二阶段事务回滚(Rollback)。
与 2PC 不同的是,TCC 是一种编程模型,是应用层的 2PC;TCC 的 3 个操作均由编码实现,通过编码实现了 2PC 资源管理器的功能。
TCC 自编码的特性决定 TCC 资源管理器可以跨数据库、跨应用实现资源管理,将对不同的数据库访问、不同的业务操作通过编码方式转换一个原子操作,解决了复杂业务场景下的事务问题。同时 TCC 的每一个操作对于数据库来讲都是一个本地数据库事务,操作结束则本地数据库事务结束,数据库的资源也就被释放;这就规避了数据库层面的 2PC 对资源占用导致的性能低下问题。
刚性事务(如单数据库)完全遵循 ACID 规范,即数据库事务正确执行的四个基本要素:
柔性事务(如分布式事务)为了满足可用性、性能与降级服务的需要,降低一致性(Consistency)与隔离性(Isolation)的要求,遵循 BASE 理论:
同样的,柔性事务也部分遵循 ACID 规范:
柔性事务分为:两阶段型、补偿型、异步确保型、最大努力通知型。
两阶段型
分布式事务二阶段提交,对应技术上的 XA、JTA/JTS,这是分布式环境下事务处理的典型模式。
补偿型
TCC 型事务(Try-Confirm-Cancel)可以归为补偿型。在 Try 成功的情况下,如果事务要回滚,Cancel 将作为一个补偿机制,回滚 Try 操作;TCC 各操作事务本地化,且尽早提交(没有两阶段约束);当全局事务要求回滚时,通过另一个本地事务实现“补偿”行为。
TCC 是将资源层的二阶段提交协议转换到业务层,成为业务模型中的一部分。
异步确保型
将一些有同步冲突的事务操作变为异步操作,避免对数据库事务的争用,如消息事务机制。
最大努力通知型
通过通知服务器(消息通知)进行,允许失败,有补充机制。