51CTO首页
AI.x社区
博客
学堂
精品班
软考社区
免费课
企业培训
鸿蒙开发者社区
WOT技术大会
IT证书
公众号矩阵
移动端

实战与原理:如何基于RocketMQ实现分布式事务?

开发 前端
由于此时MQ中的消息一直处于half状态,超过一定的超时时间后,MQ会发现这个half消息有问题,然后回调你的订单系统的接口。此时订单系统需要根据订单状态来决定执行commit请求还是rollback请求。

使用事务消息

在DailyMart系统中,用户发起支付后,订单系统需要调用库存服务执行库存扣减逻辑。图片

由于这是跨服务调用,因此会产生分布式事务。在这里,我们使用RocketMQ的事务消息来实现分布式事务。

1、首先,在订单服务的应用服务层处理支付逻辑,并调用RocketMQ发送事务消息:

@Override
public String payment(String orderSn) {
    // todo 集成支付宝支付
    // 支付流水号
    String outOrderNo = IdUtils.get32UUID();
    TradeOrder tradeOrder = Optional.ofNullable(tradeOrderService.getByOrderSn(orderSn)).orElseThrow(() -> new BusinessException("订单编号不存在"));

    // 如果订单处于待支付状态
    if (Objects.equals(tradeOrder.getStatus(), OrderStatusEnum.WAITING_PAYMENT.getStatus())) {

        OrderPaidEvent orderPaidEvent = new OrderPaidEvent(orderSn, outOrderNo);

        TransactionSendResult sendResult = enhanceTemplate.sendTransaction("TRADE-ORDER", "ORDER-PAID");

        if (SendStatus.SEND_OK == sendResult.getSendStatus() && sendResult.getLocalTransactionState() == LocalTransactionState.COMMIT_MESSAGE) {
            return tradeOrder.getOrderSn();
        } else {
            throw new BusinessException("支付失败...");
        }
    } else {
        throw new BusinessException("订单已支付,请勿重复提交...");
    }
}

2、在订单服务的基础设施层,创建一个类实现 RocketMQLocalTransactionListener 接口:

该接口有两个方法:

  • executeLocalTransaction:用于执行本地事务。
  • checkLocalTransaction:在RocketMQ执行消息回查时检查本地事务执行结果,用于确定消息提交还是回滚。
@Component
@Slf4j
public class OrderPaidTransactionConsumer implements RocketMQLocalTransactionListener {
    
    @Resource
    private TransactionTemplate transactionTemplate;
    @Resource
    private TradeOrderService tradeOrderService;
    
   
  /**
     * 执行本地事务
     * 将订单状态修改成已支付
     */
    @Override
    public RocketMQLocalTransactionState executeLocalTransaction(Message message, Object o) {
        
        final OrderPaidEvent orderPaidEvent = JsonUtils.byte2Obj((byte[]) message.getPayload(), OrderPaidEvent.class);
        try {
            // 放到同一个本地事务中
            this.transactionTemplate.executeWithoutResult(status -> {
                String orderSn = orderPaidEvent.getOrderSn();
                // 修改成待发货
                tradeOrderService.changeOrderStatus(orderSn, OrderStatusEnum.AWAITING_SHIPMENT);
            });
            return RocketMQLocalTransactionState.COMMIT;
        } catch (Exception e) {
            log.error("修改订单状态失败", e);
            // ROLLBACK 则回滚消息,rocketmq将废弃这条消息
            return RocketMQLocalTransactionState.ROLLBACK;
            // 如果是UNKNOWN, 则触发回查
        }

    }

    /**
     * 检查本地事务执行状态
     * 消息回查时,对于正在进行中的事务不要返回Rollback或Commit结果,应继续保持Unknown的状态。
     */
    @Override
    public RocketMQLocalTransactionState checkLocalTransaction(Message message) {
        final OrderPaidEvent orderPaidEvent = JsonUtils.byte2Obj((byte[]) message.getPayload(), OrderPaidEvent.class);

        String orderSn = orderPaidEvent.getOrderSn();
        TradeOrder tradeOrder = tradeOrderService.getByOrderSn(orderSn);
        // 如果已经修改成待发货说明本地事务执行成功,此时消费端可以直接消费
        if (Objects.equals(tradeOrder.getStatus(), OrderStatusEnum.AWAITING_SHIPMENT.getStatus())) {
            return RocketMQLocalTransactionState.COMMIT;
        } else {
            // 这里查不到的时候返回 UNKNOWN在于,有可能事务还没有提交,回查就开始了
            return RocketMQLocalTransactionState.UNKNOWN;
        }
    }
}

3、在库存服务的基础设施层,监听消息以执行库存扣减逻辑:

@Component
@Slf4j
@RocketMQMessageListener(consumerGroup = "dailymart_inventory_group", topic = "TRADE-ORDER", selectorExpression = "ORDER-PAID")
public class InventoryDeductionConsumer extends EnhanceMessageHandler<OrderPaidEvent> implements RocketMQListener<OrderPaidEvent> {
    
    @Resource
    private InventoryDomainService inventoryDomainService;
    
    @Override
    public void onMessage(OrderPaidEvent orderPaidEvent) {
        super.dispatchMessage(orderPaidEvent);
    }
    
    @Override
    protected void handleMessage(OrderPaidEvent orderPaidEvent) throws Exception {
        // 执行库存扣减逻辑
        String orderSn = orderPaidEvent.getOrderSn();
        inventoryDomainService.deductionInventory(orderSn);
    }
}

通过以上步骤,我们完成了RocketMQ事务消息的发送,利用事务消息的特性保证分布式事务的最终一致性。与普通消息相比,事务消息在处理时需要实现 RocketMQLocalTransactionListener 接口,这是事务消息的核心。

介绍完事务消息的使用,接下来我们再来聊聊事务消息的原理。

事务消息的原理

首先,让我们思考一下,如果不使用事务消息会有什么问题。

很容易想到的一个问题就是消息丢失。当保存订单后由于网络问题导致消息丢失,如下图所示:

图片图片

在不使用RocketMQ的情况下,我们往往会通过 本地消息表 + 补偿重试 的机制来保证消息一定会发送出去。其原理可以参考上篇文章 [Dailymart26:微服务中躲不过的坑 - 分布式事务]。

那RocketMQ是如何解决这个问题的呢?

1. 发送half消息,探测MQ是否正常

在基于RocketMQ的事务消息中,我们不是先执行自身的订单支付逻辑,而是先让订单系统发送一条 half消息 到MQ去。这个half消息本质上是一个订单支付成功的消息,只不过此时库存系统是看不见这个half消息的。然后,我们等待接收这个half消息写入成功的响应通知。

图片图片

发送half消息的本质其实是为了探测MQ是否仍然正常运行。但问题来了,如上所述,消息会发生丢失,那么half消息丢失怎么办呢?

2. half消息发送失败

在发送half消息时,由于网络原因或者MQ直接挂了,就会导致half消息发送失败。这个时候订单系统需要执行一系列的回滚操作。在我们的场景中,应该执行退款操作,将钱退还给用户,并告知用户交易失败。

3. half消息成功,订单系统执行自己的业务逻辑

如果成功收到half消息的正常响应,此时订单系统应该执行自己的业务逻辑。在我们这个场景中,就是修改订单数据库状态,将其修改为待发货状态。这部分逻辑就对应上述代码中的executeLocalTransaction()方法。

图片图片

4. 订单本地事务执行失败

如果订单系统执行本地事务失败,则需要发送一个rollback请求给MQ,让其删除这条half消息。

图片图片

5. 订单本地事务执行成功

如果订单系统的本地事务执行正常,此时需要发送一个commit请求给MQ,要求MQ对之前的half消息进行commit操作,这样库存系统就可以消费这条消息了。

图片图片

订单创建消息处于half状态时,库存系统是看不见它的。必须等到订单系统执行commit请求,消息被commit后,库存系统才能看到并获取这条消息进行后续处理。

6. half消息发送成功,但是没收到half的响应

以上就是RocketMQ事务消息的正向流程。

然而,还有一个问题:如果订单系统发送half消息成功后却没有收到half消息的响应,该如何处理呢?

在这种情况下,订单系统可能会误以为是发送half消息到MQ失败了。订单系统就会执行回滚流程,退还支付金额,关闭订单。

图片图片

然而,此时MQ系统中已经存在了一条half消息。这条half消息又该如何处理呢?

在RocketMQ中,有一套补偿流程。RocketMQ会定期扫描处于half状态的消息。如果一直没有对这个消息执行 commit/rollback 操作,超过了一定的时间,RocketMQ就会回调你的订单系统的一个接口,用以确认你本地事务的情况。

当订单系统收到MQ的回查请求时,就需要检索一下数据库,根据订单状态决定执行commit还是rollback。

这部分逻辑就对应上述代码中checkLocalTransaction()方法。

图片图片

7. rollback 或者 commit 失败怎么办?

通过上述说明,可以看到,RocketMQ是根据rollback或commit操作来决定half消息的状态的。如果业务系统执行了commit操作,则将half消息设置为可见,库存系统可以消费;如果业务系统执行了rollback操作,MQ就会删除half消息。那么问题来了:如果订单系统在执行rollback或commit操作时失败又该如何处理呢?

这时候仍然依赖于前文提到的回查机制。

由于此时MQ中的消息一直处于half状态,超过一定的超时时间后,MQ会发现这个half消息有问题,然后回调你的订单系统的接口。此时订单系统需要根据订单状态来决定执行commit请求还是rollback请求。

以上,就是RocketMQ事务消息的原理。结合文章开头的代码,是不是已经很清晰了呢?

责任编辑:武晓燕 来源: JAVA日知录
相关推荐
分布式事务分布式事务核心原理Seata介绍
Seata是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。Seata将为用户提供了AT、TCC、SAGA和XA事务模式,为用户打造一站式的分布式解决方案。

2022-06-21 08:27:22

Seata 分布式 事务
谈谈你对RocketMQ分布式事务原理的理解
随着应用的拆分,从单体架构变成分布式架构,那么每个服务或者模块也会有自己的数据库。一个业务流程的完成需要经过多次的接口调用或者多条MQ消息的发送。

2022-08-26 00:02:03

RocketMQ 单体架构 MQ
分布式事务:项目整合Seata实现分布式事务
目前,我们的项目中是不支持分布式事务的。也就是说,如果我们调用订单微服务的下单接口提交订单,如果扣减库存失败了,订单依然会写入订单数据表,这是一种典型的分布式事务问题。

2022-06-27 08:21:05

Seata 分布式事务 微服务
分布式事务实现方案:一文详解RocketMQ事务消息
RocketMQ事务消息是分布式事务中一种常见的实现方案,只是把发送消息和本地事务放在一个事务中,并且只保证最终一致性,无法保证强一致性。

2024-06-13 09:25:14

基于Seata探寻分布式事务实现方案
随着业务的快速发展、业务复杂度越来越高,几乎每个公司的系统都会从单体走向分布式,特别是转向微服务架构。随之而来就必然遇到分布式事务这个难题,这篇文章通过seata框架总结了分布式事务的几种解决方案。

2023-01-06 09:19:12

Seata 分布式 事务
深度剖析分布式事务,轻松掌握实现原理应用技巧!
二阶段ConfirmCancel方法执行后,将状态改为committed或rollbacked状态。当重复调用二阶段ConfirmCancel方法时,判断事务状态即可解决幂等问题。

2023-05-12 08:02:43

分布式 事务 应用
分布式事务(Seata)原理详解篇
今天带来的这篇,就给大家分析一下Seata的源码是如何一步一步实现的。读源码的时候我们需要俯瞰起全貌,不要去扣一个一个的细节,这样我们学习起来会快捷而且有效率,我们学习源码需要掌握的是整体思路和核心点。

2022-07-10 20:24:48

Seata 分布式事务
数据库 | 分布式事务实现原理详解
事务是数据库系统中非常有趣也非常重要的概念,它是数据库管理系统执行过程中的一个逻辑单元,它能够保证一个事务中的所有操作要么全部执行,要么全不执行;在SOA与微服务架构大行其道的今天,在分布式的多个服务中保证业务的一致性就需要我们实现分布式事务。

2019-08-19 10:24:33

分布式 事务 数据库
分布式实战-基于Etcd的实现很优雅
虽然Kubernetes给云原生时代带来了颠覆性的新气象,但却很少人了解被钦定作为其后端存储的etcd,本篇从分布式锁视角梳理etcd的各种机制,探索基于etcd的锁实现是怎样。

2022-11-06 19:28:02

分布式锁 etcd 云原生
分布式事务原理及解决方案
随着互联网的迅猛发展和大规模分布式系统的普及,分布式事务成为了一个重要的研究领域。在分布式系统中,由于多个节点同时参与的特点,事务的一致性和隔离性成为了挑战。因此,理解分布式事务的原理和采用适当的解决方案显得尤为重要。

2023-09-14 15:44:46

分布式事务 数据存储
Springboot之分布式事务框架Seata实现原理源码分析
SpringBoot是由Pivotal团队提供的全新框架,其设计目的是用来简化新Spring应用的初始搭建以及开发过程。该框架使用了特定的方式来进行配置,从而使开发人员不再需要定义样板化的配置。

2021-08-06 08:33:27

Springboot 分布式 Seata
还不懂分布式事务:带你深入剖析TCC实现原理
TCC模型将事务分为Try、Confirm和Cancel三个阶段,使得事务处理更加灵活和可控,保证了数据的一致性,减少了2PC资源锁定时间过长的问题。

2024-06-28 09:07:19

浅谈分布式事务
现今互联网界,分布式系统和微服务架构盛行。一个简单操作,在服务端非常可能是由多个服务和数据库实例协同完成的。在一致性要求较高的场景下,多个独立操作之间的一致性问题显得格外棘手。基于水平扩容能力和成本考虑,传统的强一致的解决方案(e.g.单机事务)纷纷被抛弃。其理论依据就是响当当的CAP原理。往往为了可用性和分区容错性,忍痛放弃强一致支持...

2017-07-26 15:08:05

大数据 分布式事务
分布式事务框架选择实践
选择分布式事务框架时,需根据业务场景和需求权衡其优缺点。每个框架都有其适用的场景,因此深入理解框架原理和实践步骤是确保分布式事务稳健运行的关键。

2024-01-05 07:28:50

分布式 事务 框架
分布式事务浅析及简单实现
在分布式系统中,为了保证数据的高可用,通常,我们会将数据保留多个副本(replica),这些副本会放置在不同的物理的机器上。为了对用户提供正确的CRUD等语义,我们需要保证这些放置在不同物理机器上的副本是一致的。分布式事务在现在遍地都是分布式部署的系统中几乎是必要的。

2020-03-31 08:05:23

分布式 开发 技术
如何用 RabbitMQ 解决分布式事务
通过消息中间件处理分布式事务,这种方式通过牺牲数据的强一致性换取性能的大幅提升,但是实现这种方式的成本和复杂度是比较高的,使用时还要看实际业务情况。

2022-06-14 10:47:00

分布式 事务 数据
实战!阿里神器 Seata 实现 TCC模式 解决分布式事务,真香!
TCC(TryConfirmCancel)方案是一种应用层面侵入业务的两阶段提交。是目前最火的一种柔性事务方案,其核心思想是:针对每个操作,都要注册一个与其对应的确认和补偿(撤销)操作。

2022-01-12 10:02:02

TCC 模式 Seata
Zookeeper技术:分布式架构详解、分布式技术详解、分布式事务
分布式数据库是数据库拆分后的最后方法,只有在单表规模非常庞大的时候才使用,更常用的数据库拆分手段是业务分库,将不同业务的数据库部署在不同的机器上。

2019-10-10 09:16:34

Zookeeper 架构 分布式
终于有人把“TCC分布式事务实现原理讲明白了!
之前网上看到很多写分布式事务的文章,不过大多都是将分布式事务各种技术方案简单介绍一下。很多朋友看了还是不知道分布式事务到底怎么回事,在项目里到底如何使用。

2018-11-23 09:25:00

TCC 分布式 事务
如何实现分布式架构云原生
分布式架构和云原生都是当前技术领域中的热点话题,两者可以结合起来实现更高效的应用程序部署和运行。

2023-09-14 15:38:55

云原生 分布式架构

玻璃钢生产厂家西红柿玻璃钢雕塑图片云南玻璃钢雕塑定制路人视角商场美陈茂名市玻璃钢雕塑经江苏有玻璃钢雕塑订做成都市郫县玻璃钢雕塑公司实景c4d商场美陈台州玻璃钢仿铜雕塑定制供应玻璃钢雕塑的价格怎样算河北大型商场创意商业美陈道具那曲玻璃钢商场美陈梧州玻璃钢卡通雕塑品牌莆田玻璃钢仿真水果雕塑滁州玻璃钢卡通雕塑周口商场国潮美陈辽宁玻璃钢雕塑报价河北商业商场美陈供应商盘龙区玻璃钢雕塑批发气球商场美陈装饰哪里专业云南商场节庆美陈雕塑定制天水抽象人物玻璃钢雕塑公司惠州玻璃钢雕塑定做达州市玻璃钢雕塑生产厂家溧水元旦商场美陈浙江玻璃钢花盆供应高质量玻璃钢雕塑图片驻马店佛像玻璃钢景观雕塑公司海南玻璃钢海豚雕塑定制主题商场美陈市场玻璃钢花盆质量上乘香港通过《维护国家安全条例》两大学生合买彩票中奖一人不认账让美丽中国“从细节出发”19岁小伙救下5人后溺亡 多方发声单亲妈妈陷入热恋 14岁儿子报警汪小菲曝离婚始末遭遇山火的松茸之乡雅江山火三名扑火人员牺牲系谣言何赛飞追着代拍打萧美琴窜访捷克 外交部回应卫健委通报少年有偿捐血浆16次猝死手机成瘾是影响睡眠质量重要因素高校汽车撞人致3死16伤 司机系学生315晚会后胖东来又人满为患了小米汽车超级工厂正式揭幕中国拥有亿元资产的家庭达13.3万户周杰伦一审败诉网易男孩8年未见母亲被告知被遗忘许家印被限制高消费饲养员用铁锨驱打大熊猫被辞退男子被猫抓伤后确诊“猫抓病”特朗普无法缴纳4.54亿美元罚金倪萍分享减重40斤方法联合利华开始重组张家界的山上“长”满了韩国人?张立群任西安交通大学校长杨倩无缘巴黎奥运“重生之我在北大当嫡校长”黑马情侣提车了专访95后高颜值猪保姆考生莫言也上北大硕士复试名单了网友洛杉矶偶遇贾玲专家建议不必谈骨泥色变沉迷短剧的人就像掉进了杀猪盘奥巴马现身唐宁街 黑色着装引猜测七年后宇文玥被薅头发捞上岸事业单位女子向同事水杯投不明物质凯特王妃现身!外出购物视频曝光河南驻马店通报西平中学跳楼事件王树国卸任西安交大校长 师生送别恒大被罚41.75亿到底怎么缴男子被流浪猫绊倒 投喂者赔24万房客欠租失踪 房东直发愁西双版纳热带植物园回应蜉蝣大爆发钱人豪晒法院裁定实锤抄袭外国人感慨凌晨的中国很安全胖东来员工每周单休无小长假白宫:哈马斯三号人物被杀测试车高速逃费 小米:已补缴老人退休金被冒领16年 金额超20万

玻璃钢生产厂家 XML地图 TXT地图 虚拟主机 SEO 网站制作 网站优化