分布式事务:解决方案之可靠消息最终一致性实战

《分布式事务:解决方案之可靠性消息最终一致性理论》中我们说到可靠消息最终一致性主要有两个解决方案:一个是本地消息表+MQ;一个是RocketMQ事务消息。今天我们重点演示一下第二个方案的实现细节。

1. 业务说明

下面我们通过消息中间件(RocketMQ)实现分布式事务来模拟两个账户的转账交易过程。交易过程是:张三给李四转账指定金额。

2. 开发环境

  • 数据库:MySQL-5.6
  • 微服务框架:spring-boot-2.2.2.RELEASEspring-cloud-Hoxton.SR1rocketmq-spring-boot-2.0.2

2.1 启动程序

源码地址:https://gitee.com/anbang713/distributed-transaction-study

启动程序之前,我们需要使用源码工程中对应的sql脚本创建相对应的数据库和表,以及插入测试数据。

(1)首先我们要启动registry-server服务注册中心;

(2)分别启动mq-demo-bank1mq-demo-bank2微服务;

(3)启动rocketmq服务(可参考:《RocketMQ:快速入门》)。

3. 技术架构

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qXzSumis-1598274339518)(./assets/08_技术架构.png)]

交互流程如下:

(1)Bank1向MQ Server发送转账消息。

(2)Bank1执行本地事务,扣减金额。

(3)Bank2接收转账消息,执行本地事务,添加金额。

4. 数据结构

这里除了在bank1bank2数据库分别创建account_info表外,我们还需要再新增de_duplication交易记录表,用于交易幂等控制。

CREATE TABLE de_duplication (
   tx_no VARCHAR(64) NOT NULL COMMENT '事务id',
   create_time DATETIME,
   PRIMARY KEY (`tx_no`)
) ENGINE=INNODB DEFAULT CHARSET=utf8;

5. 核心代码介绍

5.1 mq-demo-bank1的核心代码

(1)向mq发送转账的事务信息

public void sendUpdateAccountBalance(AccountChangeEvent accountChangeEvent) {
    //将accountChangeEvent转成json
    JSONObject jsonObject =new JSONObject();
    jsonObject.put("accountChange",accountChangeEvent);
    String jsonString = jsonObject.toJSONString();
    //生成message类型
    Message<String> message = MessageBuilder.withPayload(jsonString).build();
    //发送一条事务消息
    rocketMQTemplate.sendMessageInTransaction("producer_group_txmsg_bank1","topic_txmsg", message,null);
}

(2)mq的事务监听器

@Component
@Slf4j
@RocketMQTransactionListener(txProducerGroup = "producer_group_txmsg_bank1")
public class ProducerTxMsgListener  implements RocketMQLocalTransactionListener {

    @Autowired
    private AccountInfoService accountInfoService;
    @Autowired
    private AccountInfoDao accountInfoDao;
    @Autowired
    private DeDuplicationDao deDuplicationDao;

    //事务消息发送后的回调方法,当消息发送给mq成功,此方法被回调
    @Override
    @Transactional
    public RocketMQLocalTransactionState executeLocalTransaction(Message message, Object o) {
        try {
            //解析message,转成AccountChangeEvent
            String messageString = new String((byte[]) message.getPayload());
            JSONObject jsonObject = JSONObject.parseObject(messageString);
            String accountChangeString = jsonObject.getString("accountChange");
            //将accountChange(json)转成AccountChangeEvent
            AccountChangeEvent accountChangeEvent = JSONObject.parseObject(accountChangeString, AccountChangeEvent.class);
            //执行本地事务,扣减金额
            accountInfoService.doUpdateAccountBalance(accountChangeEvent);
            //当返回RocketMQLocalTransactionState.COMMIT,自动向mq发送commit消息,mq将消息的状态改为可消费
            return RocketMQLocalTransactionState.COMMIT;
        } catch (Exception e) {
            e.printStackTrace();
            return RocketMQLocalTransactionState.ROLLBACK;
        }


    }

    //事务状态回查,查询是否扣减金额
    @Override
    public RocketMQLocalTransactionState checkLocalTransaction(Message message) {
        //解析message,转成AccountChangeEvent
        String messageString = new String((byte[]) message.getPayload());
        JSONObject jsonObject = JSONObject.parseObject(messageString);
        String accountChangeString = jsonObject.getString("accountChange");
        //将accountChange(json)转成AccountChangeEvent
        AccountChangeEvent accountChangeEvent = JSONObject.parseObject(accountChangeString, AccountChangeEvent.class);
        //事务id
        String txNo = accountChangeEvent.getTxNo();
        if(deDuplicationDao.existsByTxNo(txNo)){
            return RocketMQLocalTransactionState.COMMIT;
        }else{
            return RocketMQLocalTransactionState.UNKNOWN;
        }
    }
}

5.2 mq-demo-bank2的核心代码

(1)转账事务消息消费者

@Component
@Slf4j
@RocketMQMessageListener(consumerGroup = "consumer_group_txmsg_bank2",topic = "topic_txmsg")
public class TxMsgConsumer implements RocketMQListener<String> {

    @Autowired
    AccountInfoService accountInfoService;

    //接收消息
    @Override
    public void onMessage(String message) {
        log.info("开始消费消息:{}",message);
        //解析消息
        JSONObject jsonObject = JSONObject.parseObject(message);
        String accountChangeString = jsonObject.getString("accountChange");
        //转成AccountChangeEvent
        AccountChangeEvent accountChangeEvent = JSONObject.parseObject(accountChangeString, AccountChangeEvent.class);
        //设置账号为李四的
        accountChangeEvent.setAccountNo("2");
        //更新本地账户,增加金额
        accountInfoService.addAccountInfoBalance(accountChangeEvent);

    }
}

(2)更新账户,实现转账功能

//更新账户,增加金额
@Override
@Transactional
public void addAccountInfoBalance(AccountChangeEvent accountChangeEvent) {
    log.info("bank2更新本地账号,账号:{},金额:{}",accountChangeEvent.getAccountNo(),accountChangeEvent.getAmount());
    if(deDuplicationDao.existsByTxNo(accountChangeEvent.getTxNo())){
        return ;
    }
    //增加金额
    accountInfoDao.updateAccountBalance(accountChangeEvent.getAccountNo(),accountChangeEvent.getAmount());
    //添加事务记录,用于幂等
    DeDuplication deDuplication = new DeDuplication();
    deDuplication.setTxNo(accountChangeEvent.getTxNo());
    deDuplication.setCreateTime(new Date());
    deDuplicationDao.save(deDuplication);
    if(accountChangeEvent.getAmount() == 2){
        throw new RuntimeException("人为制造异常");
    }
}

6. 测试场景

(1)张三向李四转账成功

http://localhost:9011/bank1/transfer?amount=1

(2)李四增加金额过程失败,mq将再一定时间内进行消息重复推送,消费者尝试消息实现最终一致性

http://localhost:9011/bank1/transfer?amount=2

(3)张三减少金额过程失败,本地事务回滚(此时李四还无法消费增加金额的转账消息)

http://localhost:9011/bank1/transfer?amount=3
——End——
更多精彩分享,可扫码关注微信公众号哦。

在这里插入图片描述

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
1、课程简介Spring Cloud是一系列框架的有序集合。它利用Spring Boot的开发便利性巧妙地简化了分布式系统基础设施的开发,如服务发现注册、配置中心、消息总线、负载均衡、断路器、数据监控等,都可以用Spring Boot的开发风格做到一键启动和部署。       在本套课程中,我们将全面的讲解Spring Cloud技术栈, 从环境的部署到技术的应用,再到项目实战,让我们不仅是学习框架技术的使用,而且可以学习到使用Spring Cloud如何解决实际的问题。Spring Cloud各个组件相互配合,合作支持了一套完整的微服务架构。- 注册中心负责服务的注册与发现,很好将各服务连接起来- 断路器负责监控服务之间的调用情况,连续多次失败进行熔断保护。- API网关负责转发所有对外的请求和服务- 配置中心提供了统一的配置信息管理服务,可以实时的通知各个服务获取最新的配置信息- 链路追踪技术可以将所有的请求数据记录下来,方便我们进行后续分析- 各个组件又提供了功能完善的dashboard监控平台,可以方便的监控各组件的运行状况2、适应人群有一定的Java基础,并且要有一定的web开发基础。3、课程亮点       系统的学习Spring Cloud技术栈,由浅入深的讲解微服务技术。涵盖了基础知识,原理剖析,组件使用,源码分析,优劣分析,替换方案等,以案例的形式讲解微服务中的种种问题和解决方案l  微服务的基础知识n  软件架构的发展史n  微服务的核心知识(CAP,RPC等)l  注册中心n  Eureka搭建配置服务注册n  Eureka服务端高可用集群n  Eureka的原理和源码导读n  Eureka替换方案Consuln  Consul下载安装&服务注册&高可用l  服务发现与服务调用n  Ribbon负载均衡基本使用&源码分析n  Feign的使用与源码分析n  Hystrix熔断(雪崩效应,Hystrix使用与原理分析)n  Hystrix替换方案Sentinell  微服务网关n  Zuul网关使用&原理分析&源码分析n  Zuul 1.x 版本的不足与替换方案n  SpringCloud Gateway深入剖析l  链路追踪n  链路追踪的基础知识n  Sleuth的介绍与使用n  Sleuth与Zipkin的整合开发l  配置中心n  SpringClond Config与bus 开发配置中心n  开源配置中心Apollo4、主讲内容章节一:1.     微服务基础知识2.     SpringCloud概述3.     服务注册中心Eureka4.     Eureka的替换方案Consul章节二:1.     Ribbon实现客户端负载均衡2.     基于Feign的微服务调用3.     微服务熔断技术Hystrix4.     Hystrix的替换方案Sentinel章节三:1.     微服务网关Zuul的基本使用2.     Zuul1.x 版本的不足和替换方案3.     深入SpringCloud Gateway4.     链路追踪Sleuth与Zipkin章节四:1.     SpringCloud Config的使用2.     SpringCloud Config结合SpringCloud Bus完成动态配置更新3.     开源配置中心Apollo

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值