分布式事务的解决方案总结

一、分布式事物

1.1 数据库事务

原子性(Atomicity )、一致性( Consistency )、隔离性或独立性( Isolation)和持久性(Durabilily),简称就是ACID

 

1.2 分布式事物产生原因

分布式事物产生的原因

分布式事务产生的场景

在分布式系统,都会垂直拆分数据库,分为支付数据库、订单数据库、积分数据库、优惠全数据库等,业务组成,分为多个数据源,会产生分布式事物问题。

spring事务和分布式事务的区别是什么?
spring事务,本地事务
分布式事务是跨服务间的通讯(不同的数据库连接)

 

1.3 分布式理论

CAP定理是由加州大学伯克利分校Eric Brewer教授提出来的,他指出WEB服务无法同时满足一下3个属性:

  • 一致性(Consistency) : 客户端知道一系列的操作都会同时发生(生效)
  • 可用性(Availability) : 每个操作都必须以可预期的响应结束
  • 分区容错性(Partition tolerance) : 即使出现单个组件无法可用,操作依然可以完成

这个定理在迄今为止的分布式系统中都是适用的! 为什么这么说呢?

这个时候有同学可能会把数据库的2PC(两阶段提交)搬出来说话了。OK,我们就来看一下数据库的两阶段提交。

对数据库分布式事务有了解的同学一定知道数据库支持的2PC,又叫做 XA Transactions。

MySQL从5.5版本开始支持,SQL Server 2005 开始支持,Oracle 7 开始支持。

其中,XA 是一个两阶段提交协议,该协议分为以下两个阶段:

  • 第一阶段:事务协调器要求每个涉及到事务的数据库预提交(precommit)此操作,并反映是否可以提交.
  • 第二阶段:事务协调器要求每个数据库提交数据。

其中,如果有任何一个数据库否决此次提交,那么所有数据库都会被要求回滚它们在此事务中的那部分信息。这样做的缺陷是什么呢? 咋看之下我们可以在数据库分区之间获得一致性。

如果CAP 定理是对的,那么它一定会影响到可用性。

如果说系统的可用性代表的是执行某项操作相关所有组件的可用性的和。那么在两阶段提交的过程中,可用性就代表了涉及到的每一个数据库中可用性的和。我们假设两阶段提交的过程中每一个数据库都具有99.9%的可用性,那么如果两阶段提交涉及到两个数据库,这个结果就是99.8%。根据系统可用性计算公式,假设每个月43200分钟,99.9%的可用性就是43157分钟, 99.8%的可用性就是43114分钟,相当于每个月的宕机时间增加了43分钟。

以上,可以验证出来,CAP定理从理论上来讲是正确的,CAP我们先看到这里,等会再接着说。

BASE理论

在分布式系统中,我们往往追求的是可用性,它的重要程序比一致性要高,那么如何实现高可用性呢? 前人已经给我们提出来了另外一个理论,就是BASE理论,它是用来对CAP定理进行进一步扩充的。BASE理论指的是:

  • Basically Available(基本可用)
  • Soft state(软状态)
  • Eventually consistent(最终一致性)

BASE理论是对CAP中的一致性和可用性进行一个权衡的结果,理论的核心思想就是:我们无法做到强一致,但每个应用都可以根据自身的业务特点,采用适当的方式来使系统达到最终一致性(Eventual consistency)。

 

有了以上理论之后,我们来看一下分布式事务的问题。

这里举一个分布式事务的典型例子——用户下单过程。 
当我们的系统采用了微服务架构后,一个电商系统往往被拆分成如下几个子系统:商品系统、订单系统、支付系统、积分系统等。整个下单的过程如下:

用户通过商品系统浏览商品,他看中了某一项商品,便点击下单
此时订单系统会生成一条订单
订单创建成功后,支付系统提供支付功能
当支付完成后,由积分系统为该用户增加积分
上述步骤2、3、4需要在一个事务中完成。对于传统单体应用而言,实现事务非常简单,只需将这三个步骤放在一个方法A中,再用Spring的@Transactional注解标识该方法即可。Spring通过数据库的事务支持,保证这些步骤要么全都执行完成,要么全都不执行。但在这个微服务架构中,这三个步骤涉及三个系统,涉及三个数据库,此时我们必须在数据库和应用系统之间,通过某项黑科技,实现分布式事务的支持。
 

1.4 分布式事务解决方案

在分布式系统中,要实现分布式事务,无外乎那几种解决方案。

一、两阶段提交(2PC)

两阶段提交这种解决方案属

1. 第一阶段(投票阶段):

协调者节点向所有参与者节点询问是否可以执行提交操作(vote),并开始等待各参与者节点的响应。
参与者节点执行询问发起为止的所有事务操作,并将Undo信息和Redo信息写入日志。(注意:若成功这里其实每个参与者已经执行了事务操作)
各参与者节点响应协调者节点发起的询问。如果参与者节点的事务操作实际执行成功,则它返回一个”同意”消息;如果参与者节点的事务操作实际执行失败,则它返回一个”中止”消息。
2. 第二阶段(提交执行阶段):

当协调者节点从所有参与者节点获得的相应消息都为”同意”时:

协调者节点向所有参与者节点发出”正式提交(commit)”的请求。
参与者节点正式完成操作,并释放在整个事务期间内占用的资源。
参与者节点向协调者节点发送”完成”消息。
协调者节点受到所有参与者节点反馈的”完成”消息后,完成事务。
如果任一参与者节点在第一阶段返回的响应消息为”中止”,或者 协调者节点在第一阶段的询问超时之前无法获取所有参与者节点的响应消息时:

协调者节点向所有参与者节点发出”回滚操作(rollback)”的请求。
参与者节点利用之前写入的Undo信息执行回滚,并释放在整个事务期间内占用的资源。
参与者节点向协调者节点发送”回滚完成”消息。
协调者节点受到所有参与者节点反馈的”回滚完成”消息后,取消事务。
不管最后结果如何,第二阶段都会结束当前事务。
 

二、补偿事务(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那样可能出现确认或者回滚不了的情况。

优点: 一种非常经典的实现,避免了分布式事务,实现了最终一致性。在 .NET中 有现成的解决方案。

缺点: 消息表会耦合到业务系统中,如果没有封装好的解决方案,会有很多杂活需要处理。

 

四、MQ 事务消息

有一些第三方的MQ是支持事务消息的,比如RocketMQ,他们支持事务消息的方式也是类似于采用的二阶段提交,但是市面上一些主流的MQ都是不支持事务消息的,比如 RabbitMQ 和 Kafka 都不支持。

以阿里的 RocketMQ 中间件为例,其思路大致为:

第一阶段Prepared消息,会拿到消息的地址。
第二阶段执行本地事务,第三阶段通过第一阶段拿到的地址去访问消息,并修改状态。

也就是说在业务方法内要想消息队列提交两次请求,一次发送消息和一次确认消息。如果确认消息发送失败了RocketMQ会定期扫描消息集群中的事务消息,这时候发现了Prepared消息,它会向消息发送者确认,所以生产方需要实现一个check接口,RocketMQ会根据发送端设置的策略来决定是回滚还是继续发送确认消息。这样就保证了消息发送与本地事务同时成功或同时失败。

遗憾的是,RocketMQ并没有 .NET 客户端。有关 RocketMQ的更多消息,大家可以查看这篇博客

优点: 实现了最终一致性,不需要依赖本地数据库事务。

缺点: 实现难度大,主流MQ不支持,没有.NET客户端,RocketMQ事务消息部分代码也未开源。

 

五、Sagas 事务模型

Saga事务模型又叫做长时间运行的事务(Long-running-transaction), 它是由普林斯顿大学的H.Garcia-Molina等人提出,它描述的是另外一种在没有两阶段提交的的情况下解决分布式系统中复杂的业务事务问题。你可以在这里看到 Sagas 相关论文。

我们这里说的是一种基于 Sagas 机制的工作流事务模型,这个模型的相关理论目前来说还是比较新的,以至于百度上几乎没有什么相关资料。

该模型其核心思想就是拆分分布式系统中的长事务为多个短事务,或者叫多个本地事务,然后由 Sagas 工作流引擎负责协调,如果整个流程正常结束,那么就算是业务成功完成,如果在这过程中实现失败,那么Sagas工作流引擎就会以相反的顺序调用补偿操作,重新进行业务回滚。

比如我们一次关于购买旅游套餐业务操作涉及到三个操作,他们分别是预定车辆,预定宾馆,预定机票,他们分别属于三个不同的远程接口。可能从我们程序的角度来说他们不属于一个事务,但是从业务角度来说是属于同一个事务的。

他们的执行顺序如上图所示,所以当发生失败时,会依次进行取消的补偿操作。

因为长事务被拆分了很多个业务流,所以 Sagas 事务模型最重要的一个部件就是工作流或者你也可以叫流程管理器(Process Manager),工作流引擎和Process Manager虽然不是同一个东西,但是在这里,他们的职责是相同的

 

分布式事务的解决方案

https://blog.csdn.net/jxpxlinkui/article/details/79688326

自我总结

分布式事务是指涉及多个数据库或服务的事务操作,确保这些操作要么全部成功,要么全部失败。以下为你介绍几种常见的分布式事务解决方案,并举例说明:

1. 两阶段提交协议(2PC,Two - Phase Commit)

原理

两阶段提交协议将事务的提交过程分为两个阶段:准备阶段和提交阶段。有一个协调者(Coordinator)负责管理整个事务的执行,多个参与者(Participant)负责执行具体的事务操作。

  • 准备阶段:协调者向所有参与者发送准备请求,参与者执行事务操作并将执行结果(成功或失败)反馈给协调者。
  • 提交阶段:如果所有参与者都反馈成功,协调者向所有参与者发送提交请求,参与者提交事务;如果有任何一个参与者反馈失败,协调者向所有参与者发送回滚请求,参与者回滚事务。

举例

假设一个电商系统中,用户下单时需要同时扣减库存和更新订单状态。库存服务和订单服务是两个独立的服务,使用 2PC 来保证分布式事务。

  • 准备阶段:协调者向库存服务和订单服务发送准备请求。库存服务尝试扣减库存,订单服务尝试更新订单状态,并将执行结果反馈给协调者。
  • 提交阶段:如果库存服务和订单服务都反馈成功,协调者向它们发送提交请求,完成事务;如果有一个服务反馈失败,协调者向它们发送回滚请求,恢复到事务前的状态。

缺点

  • 同步阻塞:在整个事务执行过程中,参与者需要一直等待协调者的指令,会导致资源长时间被占用。
  • 单点故障:协调者是整个事务的关键,如果协调者出现故障,会影响整个事务的执行。
  • 数据不一致:在提交阶段,如果协调者发送提交请求后部分参与者崩溃,可能会导致数据不一致。

2. 三阶段提交协议(3PC,Three - Phase Commit)

原理

三阶段提交协议是对两阶段提交协议的改进,将两阶段提交的准备阶段进一步拆分为询问阶段和准备阶段,增加了一个预提交阶段,从而减少了参与者的阻塞时间,提高了系统的可用性。

  • 询问阶段:协调者向所有参与者发送询问请求,询问参与者是否可以执行事务,参与者反馈是否可以执行。
  • 准备阶段:如果所有参与者都反馈可以执行,协调者向所有参与者发送准备请求,参与者执行事务操作并将执行结果反馈给协调者。
  • 提交阶段:与 2PC 类似,如果所有参与者都反馈成功,协调者向所有参与者发送提交请求,参与者提交事务;如果有任何一个参与者反馈失败,协调者向所有参与者发送回滚请求,参与者回滚事务。

举例

还是以电商系统的下单为例,在询问阶段,协调者先询问库存服务和订单服务是否可以执行事务,得到肯定答复后进入准备阶段,后续流程与 2PC 类似。

缺点

  • 仍然存在数据不一致的问题,虽然概率比 2PC 小,但在某些极端情况下,如网络分区,还是可能出现数据不一致。
  • 实现复杂度较高,增加了系统的开发和维护成本。

3. 补偿事务(TCC,Try - Confirm - Cancel)

原理

TCC 是一种补偿型的分布式事务解决方案,将一个业务操作分为三个阶段:Try、Confirm 和 Cancel。

  • Try 阶段:尝试执行业务操作,完成所有业务检查,预留必要的业务资源。
  • Confirm 阶段:如果 Try 阶段所有参与者都成功,则执行 Confirm 操作,确认执行业务操作,使用 Try 阶段预留的资源。
  • Cancel 阶段:如果 Try 阶段有任何一个参与者失败,则执行 Cancel 操作,撤销 Try 阶段预留的资源。

举例

在电商系统的下单场景中:

  • Try 阶段:库存服务预留相应的商品库存,订单服务创建订单并标记为待确认状态。
  • Confirm 阶段:如果库存服务和订单服务的 Try 阶段都成功,库存服务扣减预留的库存,订单服务将订单状态更新为已确认。
  • Cancel 阶段:如果有任何一个服务的 Try 阶段失败,库存服务释放预留的库存,订单服务删除待确认的订单。

优点

  • 性能较高,因为在 Try 阶段完成了资源的预留,后续的 Confirm 和 Cancel 操作相对较快。
  • 可以实现柔性事务,适用于对性能要求较高的场景。

缺点

  • 开发成本较高,需要开发者手动实现 Try、Confirm 和 Cancel 三个阶段的逻辑。
  • 一致性较弱,属于最终一致性,在某些情况下可能会出现短暂的数据不一致。

4. 消息事务(基于消息队列)

原理

使用消息队列来实现分布式事务,将业务操作和消息发送绑定在一起。通常采用本地事务和消息队列的结合,确保消息的发送和业务操作的原子性。当业务操作成功时,发送消息到消息队列,其他服务监听消息队列,根据消息执行相应的操作。

举例

在电商系统中,用户下单后,订单服务在本地事务中创建订单,并将订单消息发送到消息队列。库存服务监听消息队列,接收到订单消息后扣减库存。为了保证消息的可靠性,通常会采用消息确认机制和重试机制。

优点

  • 实现简单,通过消息队列解耦了不同服务之间的依赖关系。
  • 可以实现最终一致性,保证数据在一定时间内达到一致。

缺点

  • 消息队列可能成为系统的瓶颈,如果消息队列出现故障,会影响整个事务的执行。
  • 消息的顺序性和幂等性需要开发者自行处理,增加了开发难度。

总结

不同的分布式事务解决方案适用于不同的场景。2PC 和 3PC 适用于对一致性要求较高、对性能要求相对较低的场景;TCC 适用于对性能要求较高、可以接受一定时间内数据不一致的场景;消息事务适用于对系统解耦和最终一致性有要求的场景。在实际应用中,需要根据具体的业务需求和系统特点选择合适的解决方案。

posted @   皇问天  阅读(186)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!
点击右上角即可分享
微信分享提示