分布式事务

发布时间 2023-07-10 11:40:36作者: 毛会懂

一、分布式基础

               1、分布式事务:

               2、CAP理论:

               3、BASE理论:

      使用分布式事务的原则:不用分布式事务最好。如何无法不得不用,则考虑业务出错的频率,频率低,可以走人工补偿,频率高则引入分布式事务。

      最想说的话:很多时候,没有十全十美的方案,只能理论+实际,作出权衡。

二、常见的分布式解决方案

 

分布式事务分两大类,一类是XA类型的,一类是基于消息通知的事务方案。

常见的分布式事务解决方案:

https://view.inews.qq.com/a/20221103A00TLH00

 

1、最大努力通知

2、本地消息表

3、事务消息

4、TCC

5、XA

6、AT

7、SAGA

1、最大努力通知

https://cloud.tencent.com/developer/article/1953959

https://www.cnblogs.com/blogtech/p/14513002.html

 

最大努力通知型:通常是解决跨网络、跨服务之间分布式事务的一种方式,适用于一些最终一致性时间敏感度低的业务,且被动方处理结果不影响主动方的处理结果。

使用场景:如银行通知、商户通知,对账通知等。

特点:

            不可靠消息:业务活动主动方,在完成业务处理之后,向业务活动的被动方发送消息,直到通知N次后不再通知,允许消息丢失(不可靠消息)。
            定期校对:业务活动的被动方,根据定时策略,向业务活动主动方查询(主动方提供查询接口),恢复丢失的业务消息,对账等。

实现方式:

  • 定时任务,使用定时作业轮询发起直接通知,通知数据要做持久化操作。几乎无外部依赖。
  • 定时消息队列,基于定时消息发起通知,同样需要对通知请求数据做持久化操作,对消息队列有高可用需求。

适用范围:本方案适用于对时间敏感性较低的业务,比如充值、转账业务,在内部系统间就不适合该方式。多适用于跨系统、跨业务、跨网络的服务间数据一致性保证场景。

注意事项:业务被动方暴露给主动方的通知接收接口 以及 业务主动方提供给被动方进行查询操作的接口均需要实现幂等,这样才能保证数据完整性不会被破坏,从而实现最终一致性。

 

2、本地消息表

核心思想:将分布式事务拆分成本地事务进行处理,然后通过定时轮询待发送的消息,直到发送成功。

目的:避免”业务处理成功 + 事务消息发送失败",或"业务处理失败 + 事务消息发送成功"的棘手情况出现,保证 2 个系统事务的数据一致性。

 

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

缺点: 耦合度高,注意重试时的幂等性操作。

对于消息的生产者,屡次失败的消息,可以设置最大失败次数,超过最大失败次数的消息,不进行接口调用,等待人工处理

对于消息的消费者,如果是业务上面的失败,可以给生产方发送一个业务补偿消息,通知生产方进行回滚等操作。

 

 

3、事务消息

参考:

https://blog.51cto.com/u_13626762/4990938

https://blog.csdn.net/Weixiaohuai/article/details/123733518

https://zhuanlan.zhihu.com/p/115553176?utm_source=wechat_timeline

事务消息:事务消息发送成功后,处于 prepared 状态,不能被订阅者消费,等到事务消息的状态更改为可消费状态后,下游订阅者才可以监听到次消息。

Apache RocketMQ 4.3之后的版本正式支持事务消息,为分布式事务实现提供了便利性支持。在RocketMQ 4.3后实现了完整的事务消息,

实际上其实是对本地消息表的一个封装,将本地消息表移动到了MQ内部,解决 Producer 端的消息发送与本地事务执行的原子性问题。

 

1)事务发起方发送Half事务消息
2)RocketMq回复Half发送成功
3)事务发起方执行本地事务
4)事务发起方执行本地事务成功,发送commit到RocketMq,mq投递消息到事务参与方;

     事务发起方执行本地事务失败,发送rollback到RocketMq,mq删除消息。
5)当RocketMq一定时间内未收到来自事务发起方的确认信息,会对事务发起方进行事务回查
6)事务发起方查询本地事务状态。
7)事务发起方根据查询到的事务状态发送commint/rollback到RocketMq。
8,9,10)当RocketMq发起commit后,收到失败或一定时间未收到成功ack,则会发起重试。

优点

消息数据独立存储,降低业务系统与消息系统之间的耦合。
吞吐量优于本地消息表方案。

缺点

一次消息发送需要两次网络请求(half消息 + commit/rollback)。
需要实现消息回查接口。

注意的问题:

有可能会消息重复投递。消费者需要保证幂等。

解决方式:消费端增加消息应用状态表,用于记录消息的消费情况,每次来一个消息,在真正执行之前,先去消息应用状态表中查询一遍,如果找到说明是重复消息,丢弃即可,如果没找到才执行,同时插入到消息应用状态表(同一事务)。

rocketMQ可视化服务:https://github.com/apache/rocketmq-dashboard

springBoot集成:https://blog.csdn.net/Juzipi1234/article/details/121155769    https://blog.csdn.net/weixin_39643007/article/details/126620127

解决办法:https://blog.csdn.net/shuiyuyue/article/details/124299620   https://blog.csdn.net/sinat_34349564/article/details/116301966

start mqnamesrv.cmd

start mqbroker.cmd -n 10.1.100.68:9876 autoCreateTopicEnable=true -c ../conf/broker.conf

 

 

 

4、TCC

参与:

https://www.bilibili.com/video/BV1pK411s7MS/?spm_id_from=333.337.search-card.all.click

https://blog.csdn.net/zjj2006/article/details/125380752

https://www.sdk.cn/details/EmGNy6EYwpyl6WewAX

https://www.cnblogs.com/xxbiao/p/11679577.html

4.1 TCC原理

 

 

4.2 Seata TCC原理

 

一阶段:Try 阶段: 调用 Try 接口,尝试执行业务,完成所有业务检查,预留业务资源。

二阶段:Confirm 或 Cancel 阶段: 两者是互斥的,只能进入其中一个,并且都满足幂等性,允许失败重试。

               Confirm 操作: 对业务系统做确认提交,确认执行业务操作,不做其他业务检查,只使用 Try 阶段预留的业务资源。
               Cancel 操作: 在业务执行错误,需要回滚的状态下执行业务取消,释放预留资源。

               TCC 中会添加事务日志,如果 Confirm 或者 Cancel 阶段出错,则会进行重试,所以这两个阶段需要支持幂等;如果重试失败,则需要人工介入进行恢复和处理等

以转账为例:

 

优点:
       效率高:TCC 模式在 try 阶段的锁定资源并不是真正意义上的锁定,而是真实提交了本地事务,将资源预留到中间态,并不需要阻塞等待,因此效率比其他模式要高。

      异步提交:try 阶段成功后,不立即进入 confirm/cancel 阶段,而是认为全局事务已经结束了,启动定时任务来异步执行 confirm/cancel,扣减或释放资源,这样会有很大的性能提升。

TCC 模式中需要解决的三个问题:

  (1)幂等:在 commit/cancel 阶段,因为 TC 没有收到分支事务的响应,需要进行重试,这就要分支事务支持幂等。

  (2)空回滚:因为网络问题,try还未执行,先执行了rollback阶段。

  (3)悬挂:因为网络问题,RM 开始没有收到 try 指令,但是执行了 Rollback 后 RM 又收到了 try 指令并且预留资源成功,这时全局事务已经结束,最终导致预留的资源不能释放。

 

 

5、XA

参考:

https://blog.csdn.net/qq_45594962/article/details/126796903

https://blog.csdn.net/web__404/article/details/127971994

5.1 什么是XA

XA是一个分布式事务协议,由Tuxedo提出。XA中大致分为两部分:事务管理器和本地资源管理器。其中本地资源管理器往往由数据库实现,比如Oracle、DB2这些商业数据库都实现了XA接口,而事务管理器作为全局的调度者,负责各个本地资源的提交和回滚。XA实现分布式事务的原理如下:

 

优点:

  • 事务的强一致性,满足ACID原则。

  • 常用数据库都支持,实现简单,并且没有代码侵入

缺点:

  • 因为一阶段需要锁定数据库资源,等待二阶段结束才释放,性能较差

  • 依赖关系型数据库实现事务

5.2XA事务语法

以Mysql为例:xa 可以分为外部XA 和内部XA,外部XA 就是分布式事务,而内部XA,主要是保证同一个MySQL Server 下的各个数据库实例的全局事务一致性。

 

# 在mysql实例中开启一个XA事务,指定一个全局唯一标识;
mysql> XA START 'any_unique_id';

# XA事务的操作结束;
mysql> XA END 'any_unique_id';

# 告知mysql准备提交这个xa事务;
mysql> XA PREPARE 'any_unique_id';

# 告知mysql提交这个xa事务;
mysql> XA COMMIT 'any_unique_id';

# 告知mysql回滚这个xa事务;
mysql> XA ROLLBACK 'any_unique_id';

# 查看本机mysql目前有哪些xa事务处于prepare状态;
mysql> XA RECOVER;

分别在两个库中执行以下操作:

 

5.3 Seata XA

 

 

6、AT

参考:

AT一阶段:

 

Seata会拦截业务SQL,首先解析SQL语义,找到要更新的业务数据,在数据更新前,保存undo log,然后执行业务SQL,更新数据。

更新数据后,保存redo log,最后生成锁,这些操作都是在本地数据库事务内完成,这样保证了异阶段的原子性。

AT二阶段:

如果TC 收到全局事务提交指令后,则TC通知多个RM异步清理本地的事务日志。

TC 收到全局事务提交/回滚指令后,则TC通知每个RM回滚数据。

如果一阶段中有本地事务没有通过,name就执行全局回滚,否则全部提交。回滚用到的是一阶段的undo log,通过回滚记录生成返乡更新

SQL并执行,已完成分支事务的回滚,释放所有的资源和删除所有的日志。

 

问题1:脏读

问题2:脏写

 

7、SAGA

参考:

http://seata.io/zh-cn/docs/user/saga.html

https://www.bilibili.com/video/BV1zU4y1M7HH/?spm_id_from=333.337.search-card.all.click

https://blog.csdn.net/w1014074794/article/details/116997363

https://blog.csdn.net/zh2508/article/details/115441217

https://help.aliyun.com/document_detail/172550.htm?spm=a2c4g.11186623.0.0.163567d6v9QoNH#topic-2027609

7.1 什么是saga

SAGA其核心思想是将长事务拆分为多个本地短事务,由Saga事务协调器协调,如果正常结束那就正常完成,如果某个步骤失败,则根据相反顺序一次调用补偿操作。

 

Saga包含两类:协同式和编排式。

协同式把流程的走向与协调全盘由事务的参考者来完成,比如最简单的场景:下单同时对库存进行扣减,订单服务本地事务完成后就把事件消息发送给库存服务,库存服务如果本地事务处理失败则由它将回滚的消息发送给订单服务。

   虽然整个流程当中订单服务与库存服务并没有产生耦合,但由于没 有一个总的事务协调者,一旦服务参与者多起来那业务流程的可理解性就非常差,出了问题也不好定位。

编排式通过把Saga的执行顺序交由一个集中的Saga编排器,由它指挥并决策业务的流向,相对于协同式整个流程要清晰很多。

适用场景:

  • 业务流程长、业务流程多 ,实现上对业务侵入低,所以非常适合微服务架构的场景。同时 Saga 采用的是一阶段提交模式,不会对资源长时间加锁,不存在“木桶效应”,所以采用这种模式架构的系统性能高、吞吐高。
  • 参与者包含其它公司或遗留系统服务,无法提供 TCC 模式要求的三个接口
  • 松耦合

优势:

  • 一阶段提交本地事务,无锁,高性能
  • 事件驱动架构,参与者可异步执行,高吞吐
  • 补偿服务易于实现
  • 难理解

缺点:

  • 不保证隔离性

需要解决的问题:

          (1)允许空补偿:务设计时需要允许空补偿, 即没有找到要补偿的业务主键时返回补偿成功并将原业务主键记录下来

    • 空补偿:原服务未执行,补偿服务执行了
    • 出现原因:
      • 原服务 超时(丢包)
      • Saga 事务触发 回滚
      • 未收到 原服务请求,先收到 补偿请求

    (2)防悬挂控制:检查当前业务主键是否已经在空补偿记录下来的业务主键中存在,如果存在则要拒绝服务的执行

    • 悬挂:补偿服务 比 原服务 先执行
    • 出现原因:
      • 原服务 超时(拥堵)
      • Saga 事务回滚,触发 回滚
      • 拥堵的 原服务 到达

    (3)幂等控制

    • 原服务与补偿服务都需要保证幂等性, 由于网络可能超时, 可以设置重试策略,重试发生时要通过幂等控制避免业务数据重复更新

7.2 Seata saga

seata提供的Saga模式是基于状态机引擎来实现的,机制是:

1)通过状态图来定义服务调用的流程并生成 json 状态语言定义文件

2)状态图中一个节点可以是调用一个服务,节点可以配置它的补偿节点

3)状态图 json 由状态机引擎驱动执行,当出现异常时状态引擎反向执行已成功节点对应的补偿节点将事务回滚

4)可以实现服务编排需求,支持单项选择、并发、子流程、参数转换、参数映射、服务执行状态判断、异常捕获等功能

seata saga 状态机设计:http://seata.io/saga_designer/index.html#/

实现步骤:

1)数据库表设计:seata_state_machine_def:存储状态机定义文件

seata_state_machine_inst:存储状态机运行实例

seata_state_inst:存储状态图中单个节点

2)代码: