分布式事务简介
Posted 技术分享tech
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了分布式事务简介相关的知识,希望对你有一定的参考价值。
什么是分布式事务
在大型应用开发中,经常要做业务拆分,把原来的单一架构的应用拆分成不同的服务。不同的服务之间松耦合可以很好的解决业务耦合问题,但是这样也会带来事务处理的问题,如果一个操作会同时写入两个数据库,那么如何保证这两个写入的一致性?
单一数据库可以通过ACID原则保证自己的事务处理,但是如果有两个不同的数据库,如何保证针对这两个数据库的事务都成功呢?在 JavaEE 规范中使用 2PC (2 Phase Commit, 两阶段提交) 来处理跨 DB 环境下的事务问题。
简单来说J2EE的2PC协议是这样的,先把事物请求发送给中间协调器,协调器负责各个数据源的事物处理。处理过程分两个阶段:
投票阶段
协调器把事物请求发送给各个数据源,数据源负责各自的事物处理。
完成阶段
协调器根据各个数据源的返回结果,决定是处理成功或者失败,只要有一个结果是失败的,那么会回滚所有数据源的事物处理。
这种处理方式,实际上是一个放大版的ACID原则。但是在分布式环境下,2PC 是反可伸缩模式,在事务处理过程中,参与者需要一直持有资源直到整个分布式事务结束。这样,当业务规模达到千万级以上时,2PC 的局限性就越来越明显,系统可伸缩性会变得很差。
分布式事务的原则
CAP原则
在过去Inktomi的Eric Brewer曾提出过分布式系统的一种猜想(conjecture),在分布式系统中的三项重要指标:
Consistency一致性
Availability可用性
Partition-tolerance分区容忍度
这三个指标是不能同时成立的,在任意时刻,只有两项能同时成立。对于高流量的网站来说,为了提高系统伸缩性,一般都会牺牲一致性。
一致性
在分布式环境中,一致性是指数据在多个副本之间是否能够保持一致的特性(这点跟ACID中的一致性含义不同)。
对于一个将数据副本分布在不同节点上的分布式系统来说,如果对第一个节点的数据进行了更新操作并且更新成功后,却没有使得第二个节点上的数据得到相应的更新,于是在对第二个节点的数据进行读取操作时,获取的依然是更新前的数据(称为脏数据),这就是典型的分布式数据不一致情况。在分布式系统中,如果能够做到针对一个数据项的更新操作执行成功后,所有的用户都能读取到最新的值,那么这样的系统就被认为具有强一致性(或严格的一致性)。
可用性
可用性是指系统提供的服务必须一直处于可用的状态,对于用户的每一个操作请求总是能够在有限的时间内返回结果,如果超过了这个时间范围,那么系统就被认为是不可用的。
有限的时间内是一个在系统设计之初就设定好的运行指标,不同的系统会有很大的差别。比如对于一个在线搜索引擎来说,通常在0.5秒内需要给出用户搜索关键词对应的检索结果。而对应Hive来说,一次正常的查询时间可能在20秒到30秒之间。
返回结果是可用性的另一个非常重要的指标,它要求系统在完成对用户请求的处理后,返回一个正常的响应结果。正常的响应结果通常能够明确地反映出对请求的处理结果,及成功或失败,而不是一个让用户感到困惑的返回结果。
让我们再来看看上面提到的在线搜索引擎的例子,如果用户输入指定的搜索关键词后,返回的结果是一个系统错误,比如"OutOfMemoryErroe"或"SystemHas Crashed"等提示语,那么我们认为此时系统是不可用的。
分区容错性
分区容错性要求一个分布式系统需要具备如下特性:分布式系统在遇到任何网络分区故障的时候,仍然能够保证对外提供满足一致性和可用性的服务,除非是整个网络环境都发生了故障。
网络分区是指在分布式系统中,不同的节点分布在不同的子网络(机房或异地网络等)中,由于一些特殊的原因导致这些子网络之间出现网络不连通的状况,但各个子网络的内部网络是正常的,从而导致整个系统的网络环境被切分成了若干个孤立的区域。
以上就是对CAP定理中一致性、可用性和分区容错性的讲解。
既然一个分布式系统无法同时满足上述三个要求,而只能满足其中的两项,因此在对CAP定理应用时,我们就需要抛弃其中的一项,下表是抛弃CAP中任意一项特性的场景说明。
需要明确的一点是:对于一个分布式系统而言,分区容错性可以说是一个最基本的要求。因为既然是一个分布式系统,那么分布式系统中的组件必然需要被部署到不同的节点,否则也就无所谓的分布式系统了,因此必然出现子网络。而对于分布式系统而言,网络问题又是一个必定会出现的异常情况,因此分区容错性也就成为了一个分布式系统必然需要面对和解决的问题。因此系统架构师往往需要把精力花在如何根据业务特点在C(一致性)和A(可用性)之间寻求平衡。
BASE原则
BASE是Basically Available(基本可用)、Soft state(软状态)和Eventuallyconsistent(最终一致性)三个短语的简写。BASE是对CAP中一致性和可用性权衡的结果,其来源于对大规模互联网系统分布式实践的总结,是基于CAP定理逐步演化而来的,其核心思想是即使无法做到强一致性,但每个应用都可以根据自身的业务特点,采用适当的方法来使系统达到最终一致性。接下来,我们着重对BASE中的三要素进行讲解。
基本可用
基本可用是指分布式系统在出现不可预知故障的时候,允许损失部分可用性——但请注意,这绝不等价于系统不可用。一下就是两个"基本可用"的例子。
响应时间上的损失:正常情况下,一个在线搜索引擎需要在0.5秒之内返回给用户相应的查询结果,但由于出现故障(比如系统部分机房发生断电或断网故障),查询结果的响应时间增加到了1~2秒。
功能上的损失:正常情况下,在一个电子商务网站(比如淘宝)上购物,消费者几乎能够顺利地完成每一笔订单。但在一些节日大促购物高峰的时候(比如双十一、双十二),由于消费者的购物行为激增,为了保护系统的稳定性(或者保证一致性),部分消费者可能会被引导到一个降级页面,如下:
软状态
软状态是指允许系统中的数据存在中间状态,并认为该中间状态的存在不会影响系统的整体可用性,即允许系统在不同的数据副本之间进行数据同步的过程存在延时。
最终一致性
最终一致性强调的是系统中所有的数据副本,在经过一段时间的同步后,最终能够达到一个一致的状态。因此,最终一致性的本质是需要系统保证最终数据能够达到一致,而不需要实时保证系统数据的强一致性。
最终一致性是一种特殊的弱一致性:系统能够保证在没有其他新的更新操作的情况下,数据最终一定能够达到一致的状态,因此所有客户端对系统的数据访问都能够获取到最新的值。同时,在没有发生故障的前提下,数据到达一致状态的时间延迟,取决于网络延迟、系统负载和数据复制方案设计等因素。
常用分布式事务解决方案
两阶段提交(2PC)
两阶段提交就是使用XA协议的原理,我们可以从下面这个图的流程来很容易的看出中间的一些比如commit和abort的细节。
XA 是一个两阶段提交协议,该协议分为以下两个阶段:
第一阶段:事务协调器要求每个涉及到事务的数据库预提交(precommit)此操作,并反映是否可以提交.
第二阶段:事务协调器要求每个数据库提交数据。
两阶段提交这种解决方案属于牺牲了一部分可用性来换取的一致性。
另外说一句,TransactionScop 默认不能用于异步方法之间事务一致,因为事务上下文是存储于当前线程中的,所以如果是在异步方法,需要显式的传递事务上下文。
优点:
尽量保证了数据的强一致,适合对数据强一致要求很高的关键领域。(其实也不能100%保证强一致)
缺点:
1、同步阻塞问题。执行过程中,所有参与节点都是事务阻塞型的。当参与者占有公共资源时,其他第三方节点访问公共资源不得不处于阻塞状态。
2、单点故障。由于协调者的重要性,一旦协调者发生故障。参与者会一直阻塞下去。尤其在第二阶段,协调者发生故障,那么所有的参与者还都处于锁定事务资源的状态中,而无法继续完成事务操作。(如果是协调者挂掉,可以重新选举一个协调者,但是无法解决因为协调者宕机导致的参与者处于阻塞状态的问题)
3、数据不一致。在二阶段提交的阶段二中,当协调者向参与者发送commit请求之后,发生了局部网络异常或者在发送commit请求过程中协调者发生了故障,这回导致只有一部分参与者接受到了commit请求。而在这部分参与者接到commit请求之后就会执行commit操作。但是其他部分未接到commit请求的机器则无法执行事务提交。于是整个分布式系统便出现了数据部一致性的现象。
三阶段提交(3PC)
三阶段提交(Three-phasecommit),也叫三阶段提交协议(Three-phase commit protocol),是二阶段提交(2PC)的改进版本。
与两阶段提交不同的是,三阶段提交有两个改动点。
1、引入超时机制。同时在协调者和参与者中都引入超时机制。
2、在第一阶段和第二阶段中插入一个准备阶段。保证了在最后提交阶段之前各参与节点的状态是一致的。
也就是说,除了引入超时机制之外,3PC把2PC的准备阶段再次一分为二,这样三阶段提交就有CanCommit、PreCommit、DoCommit三个阶段。
CanCommit阶段
3PC的CanCommit阶段其实和2PC的准备阶段很像。协调者向参与者发送commit请求,参与者如果可以提交就返回Yes响应,否则返回No响应。
PreCommit阶段
协调者根据参与者的反应情况来决定是否可以记性事务的PreCommit操作。
doCommit阶段
该阶段进行真正的事务提交,也可以分为两种情况。
在doCommit阶段,如果参与者无法及时接收到来自协调者的doCommit或者rebort请求时,会在等待超时之后,会继续进行事务的提交。(其实这个应该是基于概率来决定的,当进入第三阶段时,说明参与者在第二阶段已经收到了PreCommit请求,那么协调者产生PreCommit请求的前提条件是他在第二阶段开始之前,收到所有参与者的CanCommit响应都是Yes。(一旦参与者收到了PreCommit,意味他知道大家其实都同意修改了)所以,一句话概括就是,当进入第三阶段时,由于网络超时等原因,虽然参与者没有收到commit或者abort响应,但是他有理由相信:成功提交的几率很大。)
2PC与3PC的区别
相对于2PC,3PC主要解决的单点故障问题,并减少阻塞,因为一旦参与者无法及时收到来自协调者的信息之后,他会默认执行commit。而不会一直持有事务资源并处于阻塞状态。但是这种机制也会导致数据一致性问题,因为,由于网络原因,协调者发送的abort响应没有及时被参与者接收到,那么参与者在等待超时之后执行了commit操作。这样就和其他接到abort命令并执行回滚的参与者之间存在数据不一致的情况。
补偿事务(TCC)
TCC 其实就是采用的补偿机制,其核心思想是:针对每个操作,都要注册一个与其对应的确认和补偿(撤销)操作。它分为三个阶段:
Try 阶段主要是对业务系统做检测及资源预留
Confirm 阶段主要是对业务系统做确认提交,Try阶段执行成功并开始执行 Confirm阶段时,默认 Confirm阶段是不会出错的。即:只要Try成功,Confirm一定成功。
Cancel 阶段主要是在业务执行错误,需要回滚的状态下执行的业务取消,预留资源释放。
举个例子,假入 Bob 要向 Smith 转账,思路大概是:
我们有一个本地方法,里面依次调用
1、首先在 Try 阶段,要先调用远程接口把Smith 和Bob 的钱给冻结起来。
2、在 Confirm 阶段,执行远程调用的转账的操作,转账成功进行解冻。
3、如果第2步执行成功,那么转账成功,如果第二步执行失败,则调用远程冻结接口对应的解冻方法 (Cancel)。
优点:
跟2PC比起来,实现以及流程相对简单了一些,但数据的一致性比2PC也要差一些
缺点:
缺点还是比较明显的,在2,3步中都有可能失败。TCC属于应用层的一种补偿方式,所以需要程序员在实现的时候多写很多补偿的代码,在一些场景中,一些业务流程可能用TCC不太好定义及处理。
本地消息表(异步确保)
本地消息表这种实现方式应该是业界使用最多的,其核心思想是将分布式事务拆分成本地事务进行处理,这种思路是来源于ebay。我们可以从下面的流程图中看出其中的一些细节:
基本思路就是:
消息生产方,需要额外建一个消息表,并记录消息发送状态。消息表和业务数据要在一个事务里提交,也就是说他们要在一个数据库里面。然后消息会经过MQ发送到消息的消费方。如果消息发送失败,会进行重试发送。
消息消费方,需要处理这个消息,并完成自己的业务逻辑。此时如果本地事务处理成功,表明已经处理成功了,如果处理失败,那么就会重试执行。如果是业务上面的失败,可以给生产方发送一个业务补偿消息,通知生产方进行回滚等操作。
生产方和消费方定时扫描本地消息表,把还没处理完成的消息或者失败的消息再发送一遍。如果有靠谱的自动对账补账逻辑,这种方案还是非常实用的。
这种方案遵循BASE理论,采用的是最终一致性,是这几种方案里面比较适合实际业务场景的,即不会出现像2PC那样复杂的实现(当调用链很长的时候,2PC的可用性是非常低的),也不会像TCC那样可能出现确认或者回滚不了的情况。
优点:
一种非常经典的实现,避免了分布式事务,实现了最终一致性。
缺点:
消息表会耦合到业务系统中,如果没有封装好的解决方案,会有很多杂活需要处理。
MQ 事务消息
有一些第三方的MQ是支持事务消息的,比如RocketMQ,他们支持事务消息的方式也是类似于采用的二阶段提交,但是市面上一些主流的MQ都是不支持事务消息的,比如 RabbitMQ 和 Kafka 都不支持。
以阿里的 RocketMQ 中间件为例,其思路大致为:
也就是说在业务方法内要想消息队列提交两次请求,一次发送消息和一次确认消息。如果确认消息发送失败了RocketMQ会定期扫描消息集群中的事务消息,这时候发现了Prepared消息,它会向消息发送者确认,所以生产方需要实现一个check接口,RocketMQ会根据发送端设置的策略来决定是回滚还是继续发送确认消息。这样就保证了消息发送与本地事务同时成功或同时失败。
优点:
实现了最终一致性,不需要依赖本地数据库事务。
缺点:
实现难度大,主流MQ不支持,RocketMQ事务消息部分代码也未开源。
Sagas长事务
在Sagas事务模型中,一个长事务是由一个预先定义好执行顺序的子事务集合和他们对应的补偿子事务集合组成的。典型的一个完整的交易由T1、T2、……、Tn等多个业务活动组成,每个业务活动可以是本地操作、或者是远程操作,所有的业务活动在Sagas事务下要么全部成功,要么全部回滚,不存在中间状态。
Sagas事务模型的实现机制:
每个业务活动都是一个原子操作;
每个业务活动均提供正反操作;
任何一个业务活动发生错误,按照执行的反顺序,实时执行反操作,进行事务回滚;
回滚失败情况下,需要记录待冲正事务日志,通过重试策略进行重试;
冲正重试依然失败的场景,提供定时冲正服务器,对回滚失败的业务进行定时冲正;
定时冲正依然失败的业务,等待人工干预;
Sagas长事务模型支持对数据一致性要求比较高的场景比较适用,由于采用了补偿的机制,每个原子操作都是先执行任务,避免了长时间的资源锁定,能做到实时释放资源,性能相对有保障。
Sagas长事务方式如果由业务去实现,复杂度与难度并存。在我们实际使用过程中,需要一整套支持Sagas事务模型的框架来支撑业务快速交付。
以上是关于分布式事务简介的主要内容,如果未能解决你的问题,请参考以下文章