rocketmq事务消息使用

RocketMQ系列(七)事务消息(数据库|最终一致性)

事务消息

SpringBoot整合RocketMQ发送事务消息

mq发送消息之后,业务代码回滚,导致发了一条中奖消息给用户!!

业务背景

需求是在扣减本地库存的情况下,还得通知多个第三方系统进行相关业务的操作。

问题是如果本地业务出现失败回滚的同时,通知操作也发送出去,破坏了本次请求事务的原子性。

引用rocketmq官图

事务消息诉求

相关概念

事务消息交互流程

  1. 生产者将消息发送至Apache RocketMQ服务端
  2. Apache RocketMQ 服务器端将消息持久化成功之后,向生产者返回ack确认消息已经发送成功,此时消息被标记为 “暂不能投递”,这种状态下的消息即为半事务消息
  3. 生产者开始执行本地事务逻辑
  4. 生产者根据本地事务执行结果向服务端提交二次确认结果 (Commit 或 Rollback),服务端收到确认结果后处理逻辑如下:
    • 二次确认结果为 Commit:服务端将半事务消息标记为可投递,并投递给消费者
    • 二次确认结果为 Rollback:服务端将回滚事务,不会将半事务消息投递给消费者
  5. 在断网或者生产者应用重启的特殊情况下,若服务端未收到发送者提交的二次确认结果,或服务端收到的二次确认结果为 Unknown 未知状态,经过固定时间后,服务端对消息生产者即生产者集群中任一生产者实例发起消息回查(checkLocalTransaction)
  6. 生产者收到消息回查后,需检查对应消息的本地事务执行的最终结果
  7. 生产者根据检查到的本地事务的最终状态再次提交二次确认,服务端仍按照步骤4对半事务消息进行处理

事务消息生命周期

  • 初始化:半事务消息被生产者构建并完成初始化,待发送到服务端的状态
  • 事务待提交:半事务消息被发送到服务端,和普通消息同,并不会直接被服务端持久化,而是会被单独存储到事务存储系统中,等待第二阶段本地事务返回执行结果后再提交。此时消息对下游消费者不可见
  • 消息回滚:第二阶段如果事务执行结果明确为回滚,服务端会将半事务消息回滚,该事务消息流程终止
  • 提交待消费:第二阶段如果事务执行结果明确为提交,服务端会将半事务消息重新存储到普通存储系统中,此时消息对下游消费者可见,等待被消费者获取并消费
  • 消费中:消息呗消费者获取,并按照消费者本地的业务逻辑进行处理的过程。此时服务端会等待消费者完成消费并提交消费结果,如果一定时间后没有收到消费者的响应,RocketMQ会对消息进行重试处理
  • 消费提交:消费者完成消费处理,并向服务端提交消费结果,服务端标记当前消息已经被处理(包括消费成功和失败)。RocketMQ默认支持保留所有消息,此时消息数据并不会立即被删除,只是逻辑标记已消费。消息在保存时间到期或存储空间不足被删除前,消费者仍然可以回溯消息重新消费。
  • 消息删除:RocketMQ 按照消息保存机制滚动清理最早的消息数据,将消息从物理文件中删除。

技术实现

框架:SpringBoot

中间件:RocketMQ

消息类型:事务消息

业务方法

@Service
public class MqService{
		@Autowired
  	private MqProducer mqProducer;
  
  	public void test(){
      //业务逻辑处理
      //args为需保存到数据库的参数
      mqProducer.sendMsg(args)
    }
}

消息发送者

@Component
@Slf4j
public class MqProducer {

    @Autowired
    private MqConfig mqConfig;

    @Autowired
    private RocketMQTemplate rocketMQTemplate;

    public void sendMsg(Object args) {

        Message<Object> message = MessageBuilder.withPayload(args).build();
        String topic = mqConfig.getTopic();
        TransactionSendResult sendResult = rocketMQTemplate.sendMessageInTransaction(topic, message, args);
        if (SendStatus.SEND_OK.equals(sendResult.getSendStatus())) {
            log.info("【sendMsg】 发送[{}]驳回消息[{}]成功,当前事务状态[{}]", topic, args, sendResult.getLocalTransactionState().name());
        } else {
            log.warn("【sendMsg】 发送[{}]驳回消息[{}]失败", topic, args);
        }
    }
}

事务消息监听器

@RocketMQTransactionListener
@Component
@Slf4j
public class TransactionMqListener implements RocketMQLocalTransactionListener {

    /**
     * 执行本地事务
     *
     * @param message
     * @param o
     * @return
     */
    @Override
    public RocketMQLocalTransactionState executeLocalTransaction(Message message, Object o) {
      	//业务逻辑处理,例如提交本地事务
        return RocketMQLocalTransactionState.COMMIT;
    }

    /**
     * 检验本地事务,提供给broker当发生网络问题的时候进行重试处理
     *
     * @param message
     * @return
     */
    @Override
    public RocketMQLocalTransactionState checkLocalTransaction(Message message) {
        //回查操作,校验本地事务是否提交成功
        return RocketMQLocalTransactionState.COMMIT;
    }
}

消息消费者

@Component
@RocketMQMessageListener(topic = "自定义topic", consumerGroup = "自定义消费者组")
@Slf4j
public class MqConsumer implements RocketMQListener<Object> {

    @Override
    public void onMessage(String message) {
        logger.info("接收到事务消息:{}", message);
    }
}

在业务方法执行组装将提交数据库的参数,等到executeLocalTransaction方法里再将事务和相关数据进行提交。如果出现异常则进行回滚。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值