【RocketMQ】事务消息实践

官方文档:特性设计 第五段主要介绍了事务消息的设计与处理

整体设计思想是2pc实现,外加补偿逻辑处理二阶段的超时或者失败的消息。
原理图如下:
在这里插入图片描述

详细设计思路请参考上面链接,主要实现简单说明:
生产者
TransactionMQProducer:sendMessageInTransaction方法生产发送事务消息,此时的消息是不可见的对于消费者。broker收到消息后需要到事务监听实现类中进行事务状态的确认,是回滚还是提交。

事务监听
实现TransactionListener接口,其中两个方法:

  • public LocalTransactionState executeLocalTransaction(Message message, Object o)
    执行本地事务,发送half消息到rocketmq的固定队列,返回值决定消息是否回滚

  • public LocalTransactionState checkLocalTransaction(MessageExt messageExt)
    提供回调,当broker服务器收不到消息是回滚还是提交的回应时,会回调这个方法询问,可以在这里处理消息事务状态,当依然无法得知确认消息状态时还是会回滚消息。

下述案例使用rocketmq client,未直接使用Springboot提供。
与Spring集成 注入TransactionMQProducer :

@Configuration
@Slf4j
public class ProduceConfig {

    @Value("${rocketmq.name-server}")
    private String nameServer;

    @Bean
    public AccountRocketMQTransactionLister accountRocketMQTransactionLister(ApplicationContext context) {
        return new AccountRocketMQTransactionLister(context.getBean(IAccountService.class));
    }

    @Bean(name = "accountRocketMQTemplate")
    @Primary
    public TransactionMQProducer accountRocketMQTemplate(ApplicationContext context) throws MQClientException {
        TransactionMQProducer producer = new TransactionMQProducer();
        producer.setTransactionListener(accountRocketMQTransactionLister(context));
        producer.setNamesrvAddr(nameServer);
        producer.setProducerGroup("account-gp");
        producer.start();
        System.out.println("transaction producer start...");
        return producer;
    }

    @Bean(name = "traceProducer")
    public DefaultMQProducer traceProducer() throws MQClientException {
        DefaultMQProducer producer = new DefaultMQProducer("trace-producer-group", true, "trace-topic");
        producer.setNamesrvAddr(nameServer);
        producer.start();
        return producer;
    }
}

生产者:

@PostMapping("/another-trancase1")
public ResponseResult tranA1(@RequestBody AccountVo accountVo) {

    try {
        Message message = new Message();
        message.setBody(accountVo.getUserId().getBytes());
        message.setTopic("account1");
        message.setTags("debit");
        TransactionSendResult sendResult = null;
        try {
            sendResult = accountRocketMQTemplate.sendMessageInTransaction(message, accountVo);
        } catch (MQClientException e) {
            log.error("{}", e);
            throw new BaseException("生产消息失败");
        }
        return ResultUtil.success("下单成功", sendResult);
    } catch (Exception e) {
        e.printStackTrace();
    }

    return ResultUtil.error("下单失败", null);
}

事务监听

@Slf4j
public class AccountRocketMQTransactionLister implements TransactionListener {

    private IAccountService accountService;

    public AccountRocketMQTransactionLister(IAccountService accountService){
        this.accountService = accountService;
    }

    @Override
    public LocalTransactionState executeLocalTransaction(Message message, Object o) {
        String transactionId = message.getTransactionId();
        log.info("AccountTransactionLister事务Id:{}", transactionId);
        return check(message, o);
    }

    @Override
    public LocalTransactionState checkLocalTransaction(MessageExt messageExt) {
        String transactionId = messageExt.getTransactionId();
        log.info("AccountTransactionLister事务Id:{}", transactionId);
        return check(messageExt);
    }

    private LocalTransactionState check(Message message, Object o) {
        String userId = new String(message.getBody());
        AccountTbl accountTbl = new AccountTbl();
        accountTbl.setUserId(userId);
        AccountTbl result = accountService.get(accountTbl);
        if (result == null) {
            log.error("无账户信息");
            return LocalTransactionState.ROLLBACK_MESSAGE;
        }
        if (result.getMoney() < 0) {
            log.error("账户余额不足");
            return LocalTransactionState.ROLLBACK_MESSAGE;
        }
        return LocalTransactionState.COMMIT_MESSAGE;
    }

    private LocalTransactionState check(MessageExt message) {
        String userId = new String(message.getBody());
        AccountTbl accountTbl = new AccountTbl();
        accountTbl.setUserId(userId);
        AccountTbl result = accountService.get(accountTbl);
        if (result == null) {
            log.error("无账户信息");
            return LocalTransactionState.ROLLBACK_MESSAGE;
        }
        if (result.getMoney() < 0) {
            log.error("账户余额不足");
            return LocalTransactionState.ROLLBACK_MESSAGE;
        }
        return LocalTransactionState.COMMIT_MESSAGE;
    }

}

消费者:

@Service
@Slf4j
public class RocketAccountConsumerService implements MessageListenerConcurrently {

    @Autowired
    private IAccountService accountService;

    @Override
    public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> list, ConsumeConcurrentlyContext consumeConcurrentlyContext) {
        if (!CollectionUtils.isEmpty(list)) {
            try {
                for (MessageExt messageExt : list) {
                    String userId = new String(messageExt.getBody());
                    log.info("获取到扣费通知:{}", userId);
                    AccountVo accountVo = new AccountVo();
                    accountVo.setUserId(userId);
                    accountVo.setMoney(10);
                    accountService.debit(accountVo.getUserId(), accountVo.getMoney());

                }
            } catch (Exception e) {
                log.error("处理异常:{}", e);
                return ConsumeConcurrentlyStatus.RECONSUME_LATER;
            }


        }
        return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
    }
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值