分布式事务-SAGA与消息

简介: 分布式事务分两大类,一类是XA类型的,一类是基于消息通知的事务方案。前些日子写了[分布式事务-2PC与TCC](https://mp.weixin.qq.com/s?__biz=MzUzNzAzMTc3MA==&mid=2247484814&idx=1&sn=e3467cbc3d7ae2149e8ad5c00ede9772&scene=21#wechat_redirect),这次聊一下Saga和基于消息的的事务方案。

分布式事务分两大类,一类是XA类型的,一类是基于消息通知的事务方案。前些日子写了分布式事务-2PC与TCC,这次聊一下Saga和基于消息的的事务方案。

Saga

SAGA最初出现在1987年Hector Garcaa-Molrna & Kenneth Salem发表的论文SAGAS里。其核心思想是将长事务拆分为多个本地短事务,由Saga事务协调器协调,如果正常结束那就正常完成,如果某个步骤失败,则根据相反顺序一次调用补偿操作。

流程

执行成功

如果我们要进行一个类似于银行跨行转账的业务,转出(TransOut)和转入(TransIn)分别在不同的微服务里,一个成功完成的SAGA事务典型的时序图如下:

图片

执行失败

如果有正向操作失败,则需要调用各分支的补偿操作,进行回滚,最后事务成功回滚。所以对每个事务分支而言,要包括action和compensate两个操作。

图片

补偿失败

按照Saga模式的协议,补偿操作是不允许失败的,遇见失败的情况,都是由于临时故障或者程序bug。补偿操作遇见失败时,会不断进行重试,直到成功。为了避免程序bug导致补偿操作一直无法成功,建议开发者对全局事务表进行监控,发现重试超过3次的事务,发出报警,由运维人员找开发手动处理。

设计要点

空补偿

同TCC模式,当遇到网络问题时,事务参与者可能会只收到补偿操作的请求,所以补偿操作要求能「空补偿」

防悬挂

同TCC模式,当遇到网络问题时,事务参与者可能会先于执行操作收到补偿操作的请求,所以执行操作要求能「防悬挂」

幂等性

执行操作和补偿操作的实现必须是幂等的。

状态机引擎

Saga模式通常会通过状态机引擎来实现,状态机引擎可以编排服务的调用流程和补偿服务的拓扑关系。这种状态机可以基于MQ的异步事件驱动,可以极大的提高吞吐量,其实基于事件通知的分布式事务可以看成是Saga模式的一种特例。

伪代码

我们以下单流程,需要使用优惠券和库存为例,写一下使用Saga生成订单的伪代码。

  1. 生成订单号
  2. 计算使用哪张优惠券、使用哪个库存,确定订单真正的金额等信息
  3. 向事务管理器注册使用优惠券、使用库存、生成订单的执行、补偿行为,设置先执行使用优惠券和库存,后执行生成订单
  4. 事务管理器记录主事务和6个分支事务(使用优惠券、使用库存、生成点单的执行、补偿行为)
  5. 事务管理器调用执行使用优惠券、使用库存、生成订单操作
  6. 如果正向全部成功,事务管理器返回成功,如果有失败,事务管理器进行补偿(补偿失败事务管理器会不断重试),并返回失败
  7. 主程序根据返回结果,显示订单创建成功与失败

Saga总结

因为正常情况下每个事务参与者都会直接提交,而不需要等待其他参与者的状态,所以Saga模式的并发性能非常好。但隔离性比较差,它的隔离级别是读未提交,这就意味着一个事务可以读到其他事务还没未提交的数据状态。

对于业务的接入成本,只需要为Saga单独实现一个补偿操作,而对于补偿操作的实现要求也不是很高,所以Saga的接入成本相对来说比较低。

综上,Saga模式比较适合于对并发性能和业务接入成本要求比较高,但是对于有隔离性要求不高的场景,如果有遗留系统要接入,考虑到Saga的接入成本比较低,所以这时也适合Saga模式。

基于消息的分布式事务

基于消息的分布式事务与上面的方案很不同,适合执行周期长且实时性要求不高的场景。基于消息的分布式事务是指当事务发起方执行完成本地事务后并发出一条消息,事务参与方(消息消费者)一定能够接收消息并处理事务成功,此方案强调的是只要消息发给事务参与方最终事务要达到一致。一般使用消息中间件完成。

事务发起方(消息生产方)将消息发给消息中间件,事务参与方从消息中间件接收消息,事务发起方和消息中间件之间,事务参与方(消息消费方)和消息中间件之间都是通过网络通信。

问题

要使用消息实现分布式事务,需要解决三个问题:

  1. 本地事务与消息发送的原子性问题
  • 保证数据库操作与发送消息的一致性,不会出现只有一个成功,另一个不成功的情况
  1. 事务参与方接收消息的可靠性
  • 事务参与方必须能够从消息队列接收到消息,如果接收消息失败可以重复接收消息
  1. 消息重复消费的问题
  • 由于网络的存在,若某一个消费节点超时但是消费成功,此时消息中间件会重复投递此消息,就导致了消息的重复消费

流程

使用RocketMQ事务消息,能够解决以上问题。

Apache RocketMQ 4.3之后的版本正式支持事务消息,为分布式事务实现提供了便利性支持。RocketMQ 事务消息设计则主要是为了解决 Producer 端的消息发送与本地事务执行的原子性问题,RocketMQ 的设计中 broker 与 producer 端的双向通信能力,使得 broker 天生可以作为一个事务协调者存在;而 RocketMQ 本身提供的存储机制为事务消息提供了持久化能力;RocketMQ 的高可用机制以及可靠消息设计则为事务消息在系统发生异常时依然能够保证达成事务的最终一致性。

图片

  1. Producer 发送事务消息

    本例中,Producer 即MQ发送方。

    Producer (MQ发送方)发送事务消息至MQ Server,MQ Server将消息状态标记为Prepared(预备状态),注意此时这条消息消费者(MQ订阅方)是无法消费到的。

  2. MQ Server回应消息发送成功

    MQ Server接收到Producer 发送给的消息则回应发送成功表示MQ已接收到消息。

  3. Producer 执行本地事务

    Producer 端执行业务代码逻辑,通过本地数据库事务控制。

  4. 消息投递

    若Producer 本地事务执行成功则自动向MQServer发送commit消息,MQ Server接收到commit消息后将”状态标记为可消费,此时MQ订阅方即正常消费消息;

    若Producer 本地事务执行失败则自动向MQServer发送rollback消息,MQ Server接收到rollback消息后将删除消息 。

    MQ订阅方消费消息,消费成功则向MQ回应ack,否则将重复接收消息。这里ack默认自动回应,即程序执行正常则自动回应ack。

  5. 事务回查

    如果执行Producer端本地事务过程中,执行端挂掉,或者超时,MQ Server将会不停的询问同组的其他 Producer来获取事务执行状态,这个过程叫事务回查。MQ Server会根据事务回查结果来决定是否投递消息。

以上主干流程已由RocketMQ实现,对用户侧来说,用户需要分别实现本地事务执行以及本地事务回查方法,因此只需关注本地事务的执行状态即可。

基于消息的分布式事务总结

基于消息的分布式事务适合执行周期长且实时性要求不高的场景。引入消息机制后,同步的事务操作变为基于消息执行的异步操作, 避免了分布式事务中的同步阻塞操作的影响,并实现了两个服务的解耦。

总结

分布式事务的几种主要类型都讲完了,每种类型有不同的特性,大家根据需要选择。但讲的这些都是理论,大家有时间可实现个Demo或者看一下源码,毕竟动手才能真正掌握知识。

如果是Go语言,可以看一下DTM,github地址为https://github.com/yedf/dtm ,执行go run app/main.go qs便能查看效果。

另外使用事务管理器后,性能会受TM影响,可考虑单元化方案进行解决。

资料

  1. TCC Demo 代码实现
  2. Hmily实现TCC事务
  3. https://github.com/yedf/dtm
  4. https://dtm.pub/
  5. RocketMQ实现可靠消息最终一致性

最后

大家如果喜欢我的文章,可以关注我的公众号(程序员麻辣烫)

我的个人博客为:https://shidawuhen.github.io/

相关实践学习
快速体验阿里云云消息队列RocketMQ版
本实验将带您快速体验使用云消息队列RocketMQ版Serverless系列实例进行获取接入点、创建Topic、创建订阅组、收发消息、查看消息轨迹和仪表盘。
消息队列 MNS 入门课程
1、消息队列MNS简介 本节课介绍消息队列的MNS的基础概念 2、消息队列MNS特性 本节课介绍消息队列的MNS的主要特性 3、MNS的最佳实践及场景应用 本节课介绍消息队列的MNS的最佳实践及场景应用案例 4、手把手系列:消息队列MNS实操讲 本节课介绍消息队列的MNS的实际操作演示 5、动手实验:基于MNS,0基础轻松构建 Web Client 本节课带您一起基于MNS,0基础轻松构建 Web Client
相关文章
|
7月前
|
缓存 NoSQL 关系型数据库
Redis缓存和分布式锁
Redis 是一种高性能的键值存储系统,广泛用于缓存、消息队列和内存数据库。其典型应用包括缓解关系型数据库压力,通过缓存热点数据提高查询效率,支持高并发访问。此外,Redis 还可用于实现分布式锁,解决分布式系统中的资源竞争问题。文章还探讨了缓存的更新策略、缓存穿透与雪崩的解决方案,以及 Redlock 算法等关键技术。
|
7月前
|
NoSQL Java 调度
分布式锁与分布式锁使用 Redis 和 Spring Boot 进行调度锁(不带 ShedLock)
分布式锁是分布式系统中用于同步多节点访问共享资源的机制,防止并发操作带来的冲突。本文介绍了基于Spring Boot和Redis实现分布式锁的技术方案,涵盖锁的获取与释放、Redis配置、服务调度及多实例运行等内容,通过Docker Compose搭建环境,验证了锁的有效性与互斥特性。
610 0
分布式锁与分布式锁使用 Redis 和 Spring Boot 进行调度锁(不带 ShedLock)
|
8月前
|
存储 负载均衡 NoSQL
【赵渝强老师】Redis Cluster分布式集群
Redis Cluster是Redis的分布式存储解决方案,通过哈希槽(slot)实现数据分片,支持水平扩展,具备高可用性和负载均衡能力,适用于大规模数据场景。
537 2
|
8月前
|
存储 缓存 NoSQL
【📕分布式锁通关指南 12】源码剖析redisson如何利用Redis数据结构实现Semaphore和CountDownLatch
本文解析 Redisson 如何通过 Redis 实现分布式信号量(RSemaphore)与倒数闩(RCountDownLatch),利用 Lua 脚本与原子操作保障分布式环境下的同步控制,帮助开发者更好地理解其原理与应用。
508 6
|
9月前
|
存储 缓存 NoSQL
Redis核心数据结构与分布式锁实现详解
Redis 是高性能键值数据库,支持多种数据结构,如字符串、列表、集合、哈希、有序集合等,广泛用于缓存、消息队列和实时数据处理。本文详解其核心数据结构及分布式锁实现,帮助开发者提升系统性能与并发控制能力。
|
9月前
|
NoSQL Redis
Lua脚本协助Redis分布式锁实现命令的原子性
利用Lua脚本确保Redis操作的原子性是分布式锁安全性的关键所在,可以大幅减少由于网络分区、客户端故障等导致的锁无法正确释放的情况,从而在分布式系统中保证数据操作的安全性和一致性。在将这些概念应用于生产环境前,建议深入理解Redis事务与Lua脚本的工作原理以及分布式锁的可能问题和解决方案。
327 8
|
10月前
|
缓存 NoSQL 算法
高并发秒杀系统实战(Redis+Lua分布式锁防超卖与库存扣减优化)
秒杀系统面临瞬时高并发、资源竞争和数据一致性挑战。传统方案如数据库锁或应用层锁存在性能瓶颈或分布式问题,而基于Redis的分布式锁与Lua脚本原子操作成为高效解决方案。通过Redis的`SETNX`实现分布式锁,结合Lua脚本完成库存扣减,确保操作原子性并大幅提升性能(QPS从120提升至8,200)。此外,分段库存策略、多级限流及服务降级机制进一步优化系统稳定性。最佳实践包括分层防控、黄金扣减法则与容灾设计,强调根据业务特性灵活组合技术手段以应对高并发场景。
2728 7
|
11月前
|
NoSQL 算法 安全
redis分布式锁在高并发场景下的方案设计与性能提升
本文探讨了Redis分布式锁在主从架构下失效的问题及其解决方案。首先通过CAP理论分析,Redis遵循AP原则,导致锁可能失效。针对此问题,提出两种解决方案:Zookeeper分布式锁(追求CP一致性)和Redlock算法(基于多个Redis实例提升可靠性)。文章还讨论了可能遇到的“坑”,如加从节点引发超卖问题、建议Redis节点数为奇数以及持久化策略对锁的影响。最后,从性能优化角度出发,介绍了减少锁粒度和分段锁的策略,并结合实际场景(如下单重复提交、支付与取消订单冲突)展示了分布式锁的应用方法。
858 3
|
11月前
|
存储 NoSQL Java
从扣减库存场景来讲讲redis分布式锁中的那些“坑”
本文从一个简单的库存扣减场景出发,深入分析了高并发下的超卖问题,并逐步优化解决方案。首先通过本地锁解决单机并发问题,但集群环境下失效;接着引入Redis分布式锁,利用SETNX命令实现加锁,但仍存在死锁、锁过期等隐患。文章详细探讨了通过设置唯一标识、续命机制等方法完善锁的可靠性,并最终引出Redisson工具,其内置的锁续命和原子性操作极大简化了分布式锁的实现。最后,作者剖析了Redisson源码,揭示其实现原理,并预告后续关于主从架构下分布式锁的应用与性能优化内容。
491 0
|
11月前
|
缓存 监控 NoSQL
Redis设计与实现——分布式Redis
Redis Sentinel 和 Cluster 是 Redis 高可用与分布式架构的核心组件。Sentinel 提供主从故障检测与自动切换,通过主观/客观下线判断及 Raft 算法选举领导者完成故障转移,但存在数据一致性和复杂度问题。Cluster 支持数据分片和水平扩展,基于哈希槽分配数据,具备自动故障转移和节点发现机制,适合大规模高并发场景。复制机制包括全量同步和部分同步,通过复制积压缓冲区优化同步效率,但仍面临延迟和资源消耗挑战。两者各有优劣,需根据业务需求选择合适方案。
772 14