分布式事务最全解决方案

1.分布式事务理论依据(讨论的前提)

1.1本地事务、分布式事务

如果说本地事务是解决单个数据源上的数据操作的一致性问题的话,那么分布式事务则是为了解决跨越多个数据源上数据操作的一致性问题。

1.2 强一致性、弱一致性、最终一致性

从客户端角度,多进程并发访问时,更新过的数据在不同进程如何获取的不同策略,决定了不同的一致性。对于关系型数据库,要求更新过的数据能被后续的访问都能看到,这是强一致性。如果能容忍后续的部分或者全部访问不到,则是弱一致性。如果经过一段时间后要求能访问到更新后的数据,则是最终一致性.

1.3CAP理论

  • 一致性(C:Consistency):一致性是指数据在多个副本之间能否保持一致的特性。例如一个数据在某个分区节点更新之后,在其他分区节点读出来的数据也是更新之后的数据;
  • 可用性(A:Availability):可用性是指系统提供的服务必须一直处于可用的状态,对于用户的每一个操作请求总是能够在有限的时间内返回结果。这里的重点是"有限时间内"和"返回结果";
  • 分区容错性(P:Partition tolerance):分布式系统在遇到任何网络分区故障的时候,仍然需要能够保证对外提供满足一致性和可用性的服务。

分布式环境下(数据分布)要任何时刻保证数据一致性是不可能的,只能采取妥协的方案来保证数据最终一致性。这个也就是著名的CAP定理。

选择说明
CA放弃分区容错性,加强一致性和可用性,其实就是传统单机数据库的选择
AP放弃一致性(这里指强一致性),追求可用性和分区容错性,例如hdfs,分布式数据库
CP放弃可用性,追求一致性和分区容错性,基本不会选择,因为网络会导致系统不可用

需要明确的一点是,对于一个分布式系统而言,分区容错性是一个最基本的要求。因为 既然是一个分布式系统,那么分布式系统中的组件必然需要被部署到不同的节点,否则也就无所谓分布式系统了,因此必然出现子网络。而对于分布式系统而言,网 络问题又是一个必定会出现的异常情况,因此分区容错性也就成为了一个分布式系统必然需要面对和解决的问题。因此系统架构师往往需要把精力花在如何根据业务 特点在C(一致性)和A(可用性)之间寻求平衡。

1.4BASE 理论

它是对 CAP 中 AP 的一个扩展。对于我们的业务系统,我们考虑牺牲一致性来换取系统的可用性和分区容错性。BASE 是 Basically Available、Soft state 和 Eventually consistent 三个短语的缩写。

  • Basically Available(基本可用):通过支持局部故障而不是系统全局故障来实现的。如将用户分区在 5 个数据库服务器上,一个用户数据库的故障只影响这台特定主机那 20% 的用户,其他用户不受影响;
  • Soft State(软状态):状态可以有一段时间不同步;
  • Eventually Consistent(最终一致):最终数据是一致的就可以了,而不是时时保持强一致。

1.5柔性事务

不同于ACID的刚性事务,在分布式场景下基于BASE理论,就出现了柔性事务的概念。要想通过柔性事务来达到最终的一致性,就需要依赖于一些特性,这些特性在具体的方案中不一定都要满足,因为不同的方案要求不一样;但是都不满足的话,是不可能做柔性事务的。

2.分布式事务的几种解决方案

分布式事务的几种解决方案:
2PC(二阶段提交)方案、3PC
TCC(Try、Confirm、Cancel)
本地消息表
最大努力通知
Seata 事务

2.1 2PC(两阶段提交)

两阶段提交就是将事务提交拆成了准备阶段和提交阶段这两个阶段。

XA是X/Open CAE Specification (Distributed Transaction Processing)模型中定义的TM(Transaction Manager)与RM(Resource Manager)之间进行通信的接口。

在XA规范中,数据库充当RM角色,应用需要充当TM的角色,即生成全局的txId,调用XAResource接口,把多个本地事务协调为全局统一的分布式事务。

在这里插入图片描述

二阶段提交是XA的标准实现。它将分布式事务的提交拆分为2个阶段:prepare和commit/rollback。

2PC模型中,在prepare阶段需要等待所有参与子事务的反馈,因此可能造成数据库资源锁定时间过长,不适合并发高以及子事务生命周长较长的业务场景。两阶段提交这种解决方案属于牺牲了一部分可用性来换取的一致性。

两阶段提交的原理简单,缺点如下:

  • 协调者单点问题:若协调者宕机,那所有的参与者必须一直等待。
  • 性能问题:要等待所有的参与者处理完毕为止,性能较差(木桶原理)。
  • 一致性问题:协调者如果因为网络问题或者宕机了,向一部分参与者发送了commit,另一部分还没来得及发就宕机了,就会造成数据不一致。

2.2 3PC(三阶段提交)

3PC是将2PC的准备阶段又细分成了CanCommit和PreCommit两个阶段。新增的CanCommit是一个询问阶段,让参与者根据条件判断该事务是否能顺利完成(比如扣减库存,参与者判断库存是否足够)。

3PC解决了2PC的单点问题,怎么做的呢?如果协调者在PreCommit阶段开始了之后发生宕机,参与者没有收到DoCommit的消息,3PC默认的策略是将事务提交而不是回滚事务或者继续等待,这就避免了协调者的单点问题。

性能上,如果在事务能够正常提交的场景中,因为3PC多了一个询问阶段,所以性能反而会更差。如果事务需要回滚的场景中,三阶段的性能通常要好一些。

一致性问题并没有得到解决,在事务需要回滚的场景下,如果因为网络问题参与者一直没有收到协调者的的Abort回滚指令,这些参与者将会错误的提交事务,仍有数据不一致的问题。

2.3 补偿事务(TCC)

上面说到事务消息没有隔离性,这里说到的 TCC 方案,就适合用于需要强隔离性的分布式事务中。

TCC 其实就是采用的补偿机制,其核心思想是:针对每个操作,都要注册一个与其对应的确认和补偿(撤销)操作。TCC模型是把锁的粒度完全交给业务处理。它分为三个阶段:

  • Try 阶段:主要是对业务系统做检测及资源预留
  • Confirm 阶段:主要是对业务系统做确认提交,Try阶段执行成功并开始执行 Confirm阶段时,默认 Confirm阶段是不会出错的。即:只要Try成功,Confirm一定成功。
  • Cancel 阶段:主要是在业务执行错误,需要回滚的状态下执行的业务取消,预留资源释放

下面对TCC模式下,A账户往B账户汇款100元为例子,对业务的改造进行详细的分析:

在这里插入图片描述
汇款服务和收款服务分别需要实现,Try-Confirm-Cancel接口,并在业务初始化阶段将其注入到TCC事务管理器中。

[汇款服务]
Try:
    检查A账户有效性,即查看A账户的状态是否为“转帐中”或者“冻结”;
    检查A账户余额是否充足;
    从A账户中扣减100元,并将状态置为“转账中”;
    预留扣减资源,将从A往B账户转账100元这个事件存入消息或者日志中;
Confirm:
 不做任何操作;
Cancel:
    A账户增加100元;
 从日志或者消息中,释放扣减资源。

[收款服务]
Try:
 检查B账户账户是否有效;
Confirm:
    读取日志或者消息,B账户增加100元;
    从日志或者消息中,释放扣减资源;
Cancel:
 不做任何操作。

由此可以看出,TCC模型对业务的侵入强,改造的难度大。

如果将2PC的提交阶段的提交和回滚拆开,你会发现和TCC非常类似。但是TCC是位于用户代码层面,而不是基础设施层面,我们就需要投入更高的开发成本。

优点:

  • 隔离性强
  • 在业务执行的时候,只操作预留资源,几乎不会涉及到资源的争用,所以性能较高

缺点:

  • 业务侵入性高,开发成本高
  • 技术不可控。如果你对接的有第三方服务(比如银行),别人不愿意按照TCC的方式来修改代码,那就推动不下去了。

针对第二种方式,我们可以考虑采用下面的柔性事务方案:SAGA事务。

2.4 SAGA事务

SAGA 事务把大事务拆分成若干个子事务,每个子事务都可以被看作原子行为。我们需要对每个子事务设计对应的补偿动作。如果各个子事务都能提交成功,那么事务就可以顺利完成,否则我们就要采取以下两种恢复策略之一:

  • 正向恢复:如果某个子事务提交失败,则一直对该事务进行重试,直至成功为止,这种适用于事务最终都要成功的场景。
  • 反向恢复:如果某个子事务提交失败,则对该子事务及其之前所有的子事务进行补偿操作。
    与TCC相比,SAGA不需要为资源设计冻结状态和撤销冻结的操作,补偿操作往往要比冻结操作容易实现的多。

缺点:

  • SAGA必须保证所有子事务都能提交或者补偿,但是SAGA系统本身也可能会崩溃,所以它必须设计成与数据库类似的日志机制。
  • 需要保证正向、反向恢复过程能严谨执行。

SAGA是基于数据补偿来代替回滚的思路,也可以应用在其他事务方案上。比如阿里的 Seata 框架的 AT事务模式就是这样的一种应用。

2.5 AT事务

AT事务也是参照两阶段提交协议来实现的,针对两阶段提交涉及到的资源必须都等到最慢的事务完成后才能统一释放的问题,AT事务也设计了对应的解决方案。它在业务数据提交时,自动拦截所有的SQL,分别保存SQL对数据修改前后的快照,这就相当于记录了重做和回滚日志。

如果分布式事务成功了,那我们后续只需清理每个数据源中对应的日志数据即可。如果分布式事务需要回滚,就要根据日志数据自动产生用于补偿的逆向SQL。

基于这种补偿方式,分布式事务中所涉及的每一个数据源都可以单独提交,然后立刻释放资源,相比两阶段提交,极大的提升了系统的吞吐量。但是它和我们使用的事务消息一样,牺牲了隔离性。

2.6 本地消息表(异步确保)

本地消息表这种实现方式应该是业界使用最多的,其核心思想是将分布式事务拆分成本地事务进行处理,这种思路是来源于ebay。我们可以从下面的流程图中看出其中的一些细节:
在这里插入图片描述
基本思路就是:

消息生产方,需要额外建一个消息表,并记录消息发送状态。消息表和业务数据要在一个事务里提交,也就是说他们要在一个数据库里面。然后消息会经过MQ发送到消息的消费方。如果消息发送失败,会进行重试发送。

消息消费方,需要处理这个消息,并完成自己的业务逻辑。此时如果本地事务处理成功,表明已经处理成功了,如果处理失败,那么就会重试执行。如果是业务上面的失败,可以给生产方发送一个业务补偿消息,通知生产方进行回滚等操作。

生产方和消费方定时扫描本地消息表,把还没处理完成的消息或者失败的消息再发送一遍。如果有靠谱的自动对账补账逻辑,这种方案还是非常实用的。

2.7事务消息

事务消息作为一种异步确保型事务, 将两个事务分支通过MQ进行异步解耦,事务消息的设计流程同样借鉴了两阶段提交理论,整体交互流程如下图所示:
在这里插入图片描述

  • (1) 事务发起方首先发送prepare消息到MQ。
  • (2) 在发送prepare消息成功后执行本地事务。
  • (3) 根据本地事务执行结果返回commit或者是rollback。
  • (4) 如果消息是rollback,MQ将删除该prepare消息不进行下发,如果是commit消息,MQ将会把这个消息发送给consumer端。
  • (5) 如果执行本地事务过程中,执行端挂掉,或者超时,MQ将会不停的询问其同组的其它producer来获取状态。
  • (6) Consumer端的消费成功机制有MQ保证。
    有一些第三方的MQ是支持事务消息的,比如RocketMQ,但是市面上一些主流的MQ都是不支持事务消息的,比如 RabbitMQ 和 Kafka 都不支持。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

liuyunshengsir

微信:lys20191020

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值