分布式事务解决方案、技术选型

一、基本概念

1、什么是事务

        事务可以看做是一次大的活动,它由不同的小活动组成,这些活动要么全部成功,要么全部失败。

2、本地事物

        在计算机系统中,更多的是通过关系型数据库来控制事务,这是利用数据库本身的事务特性来实现的,因此叫数据库事务,由于应用主要靠关系数据库来控制事务,而数据库通常和应用在同一个服务器,所以基于关系型数据库的事务又被称为本地事务。 

数据库事务的四大特性:ACID 

原子性(Atomicity),可以理解为一个事务内的所有操作要么都执行,要么都不执行。 

一致性(Consistency),可以理解为数据是满足完整性约束的,也就是不会存在中间状态的数据,比如你账上有400,我账上有100,你给我打200块,此时你账上的钱应该是200,我账上的 钱应该是300,不会存在我账上钱加了,你账上钱没扣的中间状态。 

隔离性(Isolation),指的是多个事务并发执行的时候不会互相干扰,即一个事务内部的数据对 于其他事务来说是隔离的。 

持久性(Durability),指的是一个事务完成了之后数据就被永远保存下来,之后的其他操作或故障都不会对事务的结果产生影响。数据库事务在实现时会将一次事务的所有操作全部纳入到一个不可分割的执行单元,该执行单元的所有操作要么都成功,要么都失败,只要其中任一操作执行失败,都将导致整个事务的回滚。

3、分布式事务

        在分布式系统中一次操作由多个系统协同完成,这种一次事务操作涉及多个系统通过网络协同完成的过程称为分布式事务。 

按照传统的系统架构,下单、扣库存等等,这一系列的操作都是 一在一个应用一个数据库中完成的,也就是说保证了事务的 ACID特性。如果在分布式应用中就会涉及到跨应用、跨库。这样就涉及到了分布式事务,就要考虑怎么保证这一系列的操作要么都成功要么都失败。保证数据的一致性。 


二、基础理论

1、CAP理论

CAP理论是:分布式系统在设计时只能在一致性(Consistency)、可用性(Availability)、分区容忍 性(Partition Tolerance)中满足两种,无法兼顾三种。 

一致性(Consistency):服务A、B、C三个结点都存储了用户数据, 三个结点的数据需要保持同一时刻数据一致性。 

可用性(Availability):服务A、B、C三个结点,其中一个结点宕机不影响整个集群对外提供服务, 如果只有服务A结点,当服务A宕机整个系统将无法提供服务,增加服务B、C是为了保证系统的可用性。 

分区容忍性(Partition Tolerance):分区容忍性就是允许系统通过网络协同工作,分区容忍性要 解决由于网络分区导致数据的不完整及无法访问等问题。分布式系统不可避免的出现了多个系统通过网络协同工作的场景,结点之间难免会出现网络中断、 网延延迟等现象,这种现象一旦出现就导致数据被分散在不同的结点上,这就是网络分区

无法兼容:在保证分区容忍性的前提下一致性和可用性无法兼顾,如果要提高系统的可用性就要 增加多个结点,如果要保证数据的一致性就要实现每个结点的数据一致,结点越多可用性越好,但是数据一致性越差。所以,在进行分布式系统设计时,同时满足“一致性”、“可用性”和 “分区容忍性”三者是几乎不可能的。

CAP有哪些组合方式?

1、CA:放弃分区容忍性,加强一致性和可用性,关系数据库按照CA进行设计。 

2、AP:放弃一致性,加强可用性和分区容忍性,追求最终一致性,很多NoSQL数据库按照AP 进行设计。 

说明:这里放弃一致性是指放弃强一致性,强一致性就是写入成功立刻要查询出最新数据。追求 最终一致性是指允许暂时的数据不一致,只要最终在用户接受的时间内数据 一致即可 

3、CP:放弃可用性,加强一致性和分区容忍性,一些强一致性要求的系统按CP进行设计,比如 跨行转账,一次转账请求要等待双方银行系统都完成整个事务才算完成。 

说明:由于网络问题的存在CP系统可能会出现待等待超时,如果没有处理超时问题则整理系统 会出现阻塞 

总结: 在分布式系统设计中AP的应用较多,即保证分区容忍性和可用性,牺牲数据的强一致性 (写操作后立刻读取到最新数据),保证数据最终一致性。比如:订单退款,今日退款成功,明 日账户到账,只要在预定的用户可以接受的时间内退款事务走完即可。

2、Base理论

Base理论:在保证分区容忍性的前提下一致性和可用性无法兼顾,如果要提高系统的可用性就要 增加多个结点,如果要保证数据的一致性就要实现每个结点的数据一致,结点越多可用性越好, 但是数据一致性越差。所以,在进行分布式系统设计时,同时满足“一致性”、“可用性”和 “分区容忍性”三者是几乎不可能的。 

BASE 是 Basically Available(基本可用)、Soft state(软状态)和 Eventually consistent (最终一致性)三个短语的缩写。BASE 理论是对 CAP 中 AP 的一个扩展,通过牺牲强一致性来获得可用性,当出现故障允许部分不可用但要保证核心功能可用,允许数据在一段时间内是不一致的,但最终达到一致状态。满足BASE理论的事务,我们称之为“柔性事务”。 

基本可用:分布式系统在出现故障时,允许损失部分可用功能,保证核心功能可用。如电商网站交易付款出现问题了,商品依然可以正常浏览。 

软状态:由于不要求强一致性,所以BASE允许系统中存在中间状态(也叫软状态),这个状态不影响系统可用性,如订单的"支付中"、“数据同步中”等状态,待数据最终一致后状态改为“成功”状态。 

最终一致:最终一致是指经过一段时间后,所有节点数据都将会达到一致。如订单的"支付中"状态,最终会变 为“支付成功”或者"支付失败",使订单状态与实际交易结果达成一致,但需要一定时间的延迟、等待。


三、解决方案

1、两阶段提交

2PC(Two-phase commit protocol),中文叫二阶段提交。 二阶段提交是一种强一致性设计,2PC 引入一个事务协调者的角色来协调管理各参与者(也可称之为各本地资源)的提交和回滚,二阶段分别指的是准备(投票) 和提交两个阶段。 

1. 第一阶段:准备阶段(prepare) 协调者通知参与者准备提交订单,参与者开始投票。参与者完成准备工作向协调者回应Yes。 

2. 第二阶段:提交(commit)/回滚(rollback)阶段协调者根据参与者的投票结果发起最终的提交指令。如果有参与者没有准备好则发起回滚指令。 

优点:实现强一致性,部分关系数据库支持(Oracle、MySQL等)。 

缺点:整个事务的执行需要由协调者在多个节点之间去协调,增加了事务的执行时间,性能低下。

2、三阶段提交 

3PC 的出现是为了解决 2PC 的一些问题,相比于 2PC 它在参与者中也引入了超时机制,并且新增了一个阶段使得参与者可以利用这一个阶段统一各自的状态。 

3PC 包含了三个阶段,分别是准备阶段、预提交阶段和提交阶段,对应的英文就是:CanCommit、PreCommit 和 DoCommit。 

看起来是把 2PC 的提交阶段变成了预提交阶段和提交阶段,但是 3PC 的准备阶段协调者只是询问参与者的自身状况。 而预提交阶段就是和 2PC 的准备阶段一样,除了事务的提交该做的都做了。提交阶段和 2PC 的一样

阶段一:CanCommit

 (1)事务询问:协调者向参与者发送一个包含事务内容的 canCommit 请求,询问是否可以执行事务提交操作,并等待响应。

(2)各参与者向协调者反馈事务询问的响应,如果参与者认为自己可以顺利执行事务,就返回 Yes,否则反馈 No 响应。

阶段 二:PreCommit

协调者在得到所有参与者的响应之后,会根据结果执行2种操作:执行事务预提交,或者中断事务。

 (1)执行事务预提交:

首先协调者向所有参与者节点发出 preCommit 的请求,并等待响应。然后参与者受到 preCommit 请求后,会执行事务操作,并将结果返回。最后协调者得到了Ack响应,确定下一阶段是否为提交或者是终止操作。

(2)中断事务也分为2个步骤:

首先协调者向所有参与者节点发出 abort 请求 。然后参与者如果收到 abort 请求或者超时了,都会中断事务。

阶段三:do Commit

1)执行提交

首先协调者发送提交请求,并等待Ack 响应。然后参与者收到 doCommit 请求后,执行事务并反馈事务提交结果,向协调者发送 Ack 消息。最后协调者接收 Ack 消息后,完成事务。

(2)中断事务

中断事务是因为出现了异常,比如协调者一方出现了问题,或者是协调者与参与者之间出现了故障。

首先协调者向所有的参与者发送中断请求。然后参与者接收到中断请求后,会利用其在二阶段记录的 undo 信息来执行事务回滚操作,并释放资源。接下来参与者在完成事务回滚之后,向协调者发送 Ack 消息。最后协调者接收到所有的 Ack 消息后,中断事务。

 3pc协议的优点

3PC对于协调者和参与者都设置了超时时间,而2PC只有协调者才拥有超时机制。这个优化点,避免了参与者在长时间无法与协调者节点通讯(协调者挂掉了)的情况下,无法释放资源的问题。

而且3pc多设置了一个缓冲阶段保证了在最后提交阶段之前各参与节点的状态是一致的。

3、TCC 

TCC事务补偿是基于2PC实现的业务层事务控制方案,它是Try、Confirm和Cancel三个单词的首字母,含义如下: 

1、Try 检查及预留业务资源完成提交事务前的检查,并预留好资源。 

2、Confirm 确定执行业务操作对try阶段预留的资源正式执行。 

3、Cancel 取消执行业务操作对try阶段预留的资源释放。 

优点:最终保证数据的一致性,在业务层实现事务控制,灵活性好。 

缺点:开发成本高,每个事务操作每个参与者都需要实现try/confirm/cancel三个接口。 

注意:TCC的try/confirm/cancel接口都要实现幂等性,在为在try、confirm、cancel失败后要不断重试。 

什么是幂等性? 幂等性是指同一个操作无论请求多少次,其结果都相同。

注意防止悬挂、空提交

4、本地消息表

本地消息表其实就是利用了各系统本地的事务来实现分布式事务。 

本地消息表顾名思义就是会有一张存放本地消息的表,一般都是放在数据库中,然后在执行业务的时候 将业务的执行和将消息放入消息表中的操作放在同一个事务中,这样就能保证消息放入本地表中业务肯定是执行成功的。 

然后再去调用下一个操作,如果下一个操作调用成功了好说,消息表的消息状态可以直接改成已成功。 

如果调用失败也没事,会有后台任务定时去读取本地消息表,筛选出还未成功的消息再调用对应的服务,服务更新成功了再变更消息的状态。 

这时候有可能消息对应的操作不成功,因此也需要重试,重试就得保证对应服务的方法是幂等 的,而且一般重试会有最大次数,超过最大次数可以记录下报警让人工处理。 

可以看到本地消息表其实实现的是最终一致性,容忍了数据暂时不一致的情况。

5、事务消息

RocketMQ 就很好的支持了事务消息,如何通过消息实现事务? 

第一步先给 Broker 发送事务消息即半消息,半消息不是说一半消息,而是这个消息对消费者来说不可见,然后发送成功后发送方再执行本地事务。 

再根据本地事务的结果向 Broker 发送 Commit 或者 RollBack 命令。 

并且 RocketMQ 的发送方会提供一个反查事务状态接口,如果一段时间内半消息没有收到任何操作请求,那么 Broker 会通过反查接口得知发送方事务是否执行成功,然后执行 Commit 或者 RollBack 命令。 

如果是 Commit 那么订阅方就能收到这条消息,然后再做对应的操作,做完了之后再消费这条消息即可。 

如果是 RollBack 那么订阅方收不到这条消息,等于事务就没执行过。 

可以看到通过 RocketMQ 还是比较容易实现的,RocketMQ 提供了事务消息的功能,我们只需要定义好事务反查接口即可。 

总结:事务消息实现的也是最终一致性。

6、最大努力通知

最大努力通知型( Best-effort delivery)是最简单的一种柔性事务,适用于一些最终一致性时间敏感度低的业务,且被动方处理结果不影响主动方的处理结果。典型的使用场景:如银行通知、商户通知等。最大努力通知型的实现方案,一般符合以下特点: 

1、不可靠消息:业务活动主动方,在完成业务处理之后,向业务活动的被动方发送消息,直到通知N次后不再通知,允许消息丢失(不可靠消息)--消息重复通知机制。 

2、定期校对:业务活动的被动方,根据定时策略,向业务活动主动方查询(主动方提供查询接口),恢复丢失的业务消息--消息校对机制。

总结:最大努力通知方案是分布式事务中对一致性要求最低的一种,适用于一些最终一致性时间敏感度低的业务


四、实战 

1、seata

Seata 是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。Seata 将为用户提供了 AT、TCC、SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案。

AT模式:

 AT 模式是一种无侵入的分布式事务解决方案。在 AT 模式下,用户只需关注自己的“业务 SQL”,用户的 “业务 SQL” 作为一阶段,Seata 框架会自动生成事务的二阶段提交和回滚操作。

AT模式一阶段

在一阶段,Seata 会拦截“业务 SQL”,首先解析 SQL 语义,找到“业务 SQL”要更新的业务数据,在业务数据被更新前,将其保存成“before image”,然后执行“业务 SQL”更新业务数据,在业务数据更新之后,再将其保存成“after image”,最后生成行锁。以上操作全部在一个数据库事务内完成,这样保证了一阶段操作的原子性。

 二阶段提交:

二阶段如果是提交的话,因为“业务 SQL”在一阶段已经提交至数据库, 所以 Seata 框架只需将一阶段保存的快照数据和行锁删掉,完成数据清理即可。

 二阶段回滚:

二阶段如果是回滚的话,Seata 就需要回滚一阶段已经执行的“业务 SQL”,还原业务数据。回滚方式便是用“before image”还原业务数据;但在还原前要首先要校验脏写,对比“数据库当前业务数据”和 “after image”,如果两份数据完全一致就说明没有脏写,可以还原业务数据,如果不一致就说明有脏写,出现脏写就需要根据配置的策略来处理。

如何保证写隔离?参考官网

TCC模式:

Overview of a global transaction

 tcc模式对业务的侵入较大,需要开发对应的接口:

  • 一阶段 prepare 行为:调用 自定义 的 prepare 逻辑。
  • 二阶段 commit 行为:调用 自定义 的 commit 逻辑。
  • 二阶段 rollback 行为:调用 自定义 的 rollback 逻辑。

2、RocketMq事务消息

只有云版(付费版本)的rocketmq支持事务消息

交互流程:

事务消息发送步骤如下:

  1. 发送方将半事务消息发送至消息队列RocketMQ版服务端。
  2. 消息队列RocketMQ版服务端将消息持久化成功之后,向发送方返回Ack确认消息已经发送成功,此时消息为半事务消息。
  3. 发送方开始执行本地事务逻辑。
  4. 发送方根据本地事务执行结果向服务端提交二次确认(Commit或是Rollback),服务端收到Commit状态则将半事务消息标记为可投递,订阅方最终将收到该消息;服务端收到Rollback状态则删除半事务消息,订阅方将不会接受该消息。

事务消息回查步骤如下:

  1. 在断网或者是应用重启的特殊情况下,上述步骤4提交的二次确认最终未到达服务端,经过固定时间后服务端将对该消息发起消息回查。
  2. 发送方收到消息回查后,需要检查对应消息的本地事务执行的最终结果。
  3. 发送方根据检查得到的本地事务的最终状态再次提交二次确认,服务端仍按照步骤4对半事务消息进行操作。

3、基于本地消息表

该方案比较简单,在插入业务数据时 插入一条消息数据, 保证两次插入在同一个事务,然后调用库存服务,如果库存服务返回扣减成功,则更新消息表状态。task任务会扫描消息表中扣减库存失败的数据,并发起补偿。

优点:

  • 实现逻辑简单,开发成本比较低

缺点:

  • 本地消息表与业务数据表在同一个库,占用业务系统资源,量大可能会影响数据库性能
  • 与业务场景绑定,高耦合,不可公用

4、基于延时消息+补偿

该方案是本地消息表的一个升级方案,本地消息表由task驱动,task驱动的弊端一是task会有时间间隔,而是task会有空轮训现象产生,如果任务特别多,task表的性能也需要解决。

该方案以下完订单去扣减库存背景来说:

1、订单系统发送延时消息;延时消息的接受者是自己,延时时间为第二步http调用时间+第三步事务锁等待超时时间+5s

2、订单系统发起rest请求,库存系统扣减库存;如果扣减失败,流程终止

3、订单系统执行本地事务

4、订单系统消费第一步发送的延时消息

5、根据本地事务状态,决定是否回滚库存操作 

 ​​​​​​​最后附带下各种解决方案的总结:

 

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值