RocketMQ多事务消息发送

由于业务需要,需要Spring Boot集成RocketMq,进行事务消息的发送。相信很多道友在网上搜的话,应该有很多案例。但是大部分只是说到了单条事务消息的发送。而应用中,不同业务。可能会有多个事务消息的发送。查看网上很多资料,说的都不太正确。因此记录一下,我在业务中的正确处理方式。希望能帮到更多的道友来尽快摆脱多事务消息的困扰。

版本说明:

SpringBoot  2.2.2.RELEASE

rocketmq maven:

<dependency>
    <groupId>org.apache.rocketmq</groupId>
    <artifactId>rocketmq-spring-boot-starter</artifactId>
    <version>2.2.0</version>
</dependency>

一、首先定义controller

@RestController
@RequestMapping("/zhmy/mqTrans/")
@Slf4j
public class BookController {
    private final BookServiceImpl bookService;

    @Resource
    private OrderMessageSender orderMessageSender;

    @Resource
    private ReduceStockMsgSender reduceStockMsgSender;

   
    @PostMapping("/bookTransSave")
    public ResultModel addBook(@RequestBody BookVO vo){
        bookService.addBookTrans(vo);
        orderMessageSender.sendCreateOrderMsg(vo);
        return ResultModel.success();
    }
    @PostMapping("/bookTransSave2")
    public ResultModel addBook2(@RequestBody BookVO vo){
        bookService.addBookTrans(vo);
        reduceStockMsgSender.sendCreateOrderMsg(vo);
        return ResultModel.success();
    }
  

}

二、从controller中,大家可看到我定义了两个mq生产者。具体代码如下

@Slf4j
@Component
public class OrderMessageSender {
    @Resource(name ="rocketMQTemplate" )
    private RocketMQTemplate rocketMQTemplate;
    private String TAG = "cancelOrder";
    private String TXTAG = "trans";
    private String ORDERTAG = "create-order";
    /**
     * 使用事务消息机制发送订单
     * @return
     */
    public boolean sendCreateOrderMsg(BookVO bookVO){
        String destination = RocketMqConstant.ORDER_ASYNC_TOPIC+":"+ORDERTAG;
        Message<BookVO> build = MessageBuilder.withPayload(bookVO).build();

        TransactionSendResult sendResult = rocketMQTemplate.sendMessageInTransaction(destination,build,bookVO.getId());

        log.info("发送的rocketMQTemplate事务消息信息:{}", JSONUtil.toJsonStr(sendResult));
        return SendStatus.SEND_OK == sendResult.getSendStatus();
    }
}
@Component
@Slf4j
public class ReduceStockMsgSender {
    @Autowired
    private ZhmyRocketMQTemplate zhmyRocketMQTemplate;
    private String TAG = "cancelOrder";
    private String TXTAG = "trans";
    private String ORDERTAG = "create-order2";
    /**
     *
     * @param bookVO
     * @return
     */
    public boolean sendCreateOrderMsg(BookVO bookVO){
        String destination = RocketMqConstant.ORDER_ASYNC_TOPIC2+":"+ORDERTAG;
        Message<BookVO> build = MessageBuilder.withPayload(bookVO).build();
        TransactionSendResult sendResult = zhmyRocketMQTemplate.sendMessageInTransaction(destination,build,bookVO.getId());
        log.info("发送的extRocketMQTemplate事务消息信息:{}", JSONUtil.toJsonStr(sendResult));
        return SendStatus.SEND_OK == sendResult.getSendStatus();
    }


  
}

三、从生产者中,可以看到定义了两个RocketMQTemplate,一个是默认的,一个是咱们自定义的。

默认的无需处理了,看下自定义的是如何定义的。且生产组一定要和其他生产组区分出来

//一定要加上生产组,否则多事务消息的监听器都会监听到
@ExtRocketMQTemplateConfiguration(group = "zhmy-Test2")
public class ZhmyRocketMQTemplate extends RocketMQTemplate {
}

四、在定义两个监听器

@Slf4j
@RocketMQTransactionListener
public class TransactionListenerImpl2 implements RocketMQLocalTransactionListener {
    private static int maxTryMums = 5;
    @Resource
    private RedisTemplate<String, Object> redisTemplate;
    @Resource
    private BookMapper bookMapper;
    @Resource
    private BookServiceImpl bookService;
    private String ORDERTAG = "create-order";
    @Override
    public RocketMQLocalTransactionState executeLocalTransaction(Message msg, Object object) {
        try{
            String transId = (String)msg.getHeaders().get(RocketMQHeaders.PREFIX + RocketMQHeaders.TRANSACTION_ID);
            log.info("------------RocketMQ执行本地订单创建 transId: "+transId+"-------------");
            /**
             * 订单已提前生成,这里就不用记录本地订单了。
             * */
            log.info("object信息为:{}", JSONUtil.toJsonStr(object));
            log.info("msg信息为:{}", JSONUtil.toJsonStr(msg));
            //RocketMQUtil.convertToRocketMessage(new StringMessageConverter(), "UTF-8", destination, msg);
            String destination = RocketMqConstant.ORDER_ASYNC_TOPIC2+":"+ORDERTAG;
            org.apache.rocketmq.common.message.Message message =
                    RocketMQUtil.convertToRocketMessage(new StringMessageConverter(), "UTF-8", destination, msg);
            log.info("输出msg信息:{}",JSONUtil.toJsonStr(msg));
            BookVO vo = getBusinessMessage(msg);
            log.info("vo信息为:{}", JSONUtil.toJsonStr(vo));
            redisTemplate.opsForHash().put(RocketMqConstant.REDIS_CREATE_ORDER,vo.getId().toString(),0);
            return RocketMQLocalTransactionState.UNKNOWN;
        }catch (Exception e){
            log.warn("-----创建RocketMQ下单事务消息失败, ---",e);
            return RocketMQLocalTransactionState.ROLLBACK;
        }
    }

    @Override
    public RocketMQLocalTransactionState checkLocalTransaction(Message msg) {
        try{
            String transId = (String)msg.getHeaders().get(RocketMQHeaders.PREFIX + RocketMQHeaders.TRANSACTION_ID);
            log.info("------------RocketMQ回查订单状态 transId: "+transId+"-------------");
            BookVO vo = getBusinessMessage(msg);
//            String message = new String((byte[]) msg.getPayload());
            if(ObjectUtil.isNotNull(vo) && vo.getAuthor().contains("author") ){

                Object redisRecord = redisTemplate.opsForHash().get(RocketMqConstant.REDIS_CREATE_ORDER, vo.getId());
                if(null == redisRecord){
                    log.warn("-----RocketMQ订单事务回查失败, 没有Redis记录");
                    return RocketMQLocalTransactionState.ROLLBACK;
                }
                int retryTimes = (int)redisRecord;
                //1、超过最大检查次数,表示订单超时未支付,关闭订单
                if(retryTimes >= maxTryMums){

                    Book book = bookService.queryBook(vo.getId());
                    book.setTitle("超出最大次数,关闭了");
                    bookMapper.updateById(book);
                    redisTemplate.opsForHash().delete(RocketMqConstant.REDIS_CREATE_ORDER,vo.getId());
                    log.info("--- 订单下单事务消息 transID: "+transId+"支付超时,发送消息,释放锁定的库存");
                    return RocketMQLocalTransactionState.COMMIT;
                }else{
                    //2、查询支付宝订单支付状态
                    //2.1 如果支付宝支付成功,这个方法中会更新订单paytype和status,并完成扣减库存
                    Book book = bookService.queryBook(vo.getId());
                    if(book.getPayStatus() == 1){
//                        msg.getHeaders().remove("CHECK_TIME");
//                    localTrans.remove(transId);
                        log.info("--- 订单下单事务消息 transID: "+transId+";订单号:"+vo.getId()+"已经完成支付,回滚消息");
                        return RocketMQLocalTransactionState.ROLLBACK;
                    }else{
                        log.info("--- 订单下单事务消息 transID: "+transId+";订单号:"+vo.getId()+"未支付,已回查"+retryTimes+"次,等待下次回查");
                        //记录回查次数,等待下次回查。
                        redisTemplate.opsForHash().increment(RocketMqConstant.REDIS_CREATE_ORDER,vo.getId().toString(),1);
//                msg.getHeaders().put("CHECK_TIME",retryTimes+1);
                        return RocketMQLocalTransactionState.UNKNOWN;
                    }
                }
            }else{
                log.info("----RocketMQ订单消息格式不对,丢弃消息。");
                return RocketMQLocalTransactionState.ROLLBACK;
            }
        }catch (Exception e){
            log.warn("-----RcoketMQ下单事务消息状态回查失败, ---",e);
            return RocketMQLocalTransactionState.ROLLBACK;
        }
    }

    /**
     * Get event instance from message payload.
     */
    private BookVO getBusinessMessage(Message msg) throws JsonProcessingException {

        final String json = new String((byte[]) msg.getPayload(), StandardCharsets.UTF_8);
        BookVO bookVO = JSONUtil.toBean(json, BookVO.class);

        return bookVO;
    }

}
@Slf4j
@RocketMQTransactionListener(rocketMQTemplateBeanName="zhmyRocketMQTemplate") //一个事物监听器对应一个事物流程
public class ReduceStockMsgListener implements RocketMQLocalTransactionListener {

    private static int maxTryMums = 5;
    @Resource
    private RedisTemplate<String, Object> redisTemplate;
    @Resource
    private BookMapper bookMapper;
    @Resource
    private BookServiceImpl bookService;
    private String ORDERTAG = "create-order";
    @Override
    public RocketMQLocalTransactionState executeLocalTransaction(Message msg, Object object) {
        try{
            String destination = RocketMqConstant.ORDER_ASYNC_TOPIC2+":"+ORDERTAG;
            org.apache.rocketmq.common.message.Message message =
                    RocketMQUtil.convertToRocketMessage(new StringMessageConverter(), "UTF-8", destination, msg);
            log.info("输出msg信息:{}",JSONUtil.toJsonStr(msg));
            Object rocketmqTags = msg.getHeaders().get("rocketmq_TAGS");
            log.info("tags信息为{}",rocketmqTags.toString());
            String transId = (String)msg.getHeaders().get(RocketMQHeaders.PREFIX + RocketMQHeaders.TRANSACTION_ID);
            log.info("-----extRocketMQTemplate-------RocketMQ执行本地订单创建 transId: "+transId+"-------------");
            /**
             * 订单已提前生成,这里就不用记录本地订单了。
             * */
            log.info("extRocketMQTemplate object信息为:{}", JSONUtil.toJsonStr(object));
            log.info("extRocketMQTemplate msg信息为:{}", JSONUtil.toJsonStr(msg));
            BookVO vo = getBusinessMessage(msg);
            log.info("extRocketMQTemplate vo信息为:{}", JSONUtil.toJsonStr(vo));
            redisTemplate.opsForHash().put(RocketMqConstant.REDIS_CREATE_ORDER,vo.getId().toString(),0);
            return RocketMQLocalTransactionState.UNKNOWN;
        }catch (Exception e){
            log.warn("--extRocketMQTemplate---创建RocketMQ下单事务消息失败, ---",e);
            return RocketMQLocalTransactionState.ROLLBACK;
        }
    }

    @Override
    public RocketMQLocalTransactionState checkLocalTransaction(Message msg) {
        try{
            String transId = (String)msg.getHeaders().get(RocketMQHeaders.PREFIX + RocketMQHeaders.TRANSACTION_ID);
            log.info("-----extRocketMQTemplate-------RocketMQ回查订单状态 transId: "+transId+"-------------");
            BookVO vo = getBusinessMessage(msg);
//            String message = new String((byte[]) msg.getPayload());
            if(ObjectUtil.isNotNull(vo) && vo.getAuthor().contains("author") ){

                Object redisRecord = redisTemplate.opsForHash().get(RocketMqConstant.REDIS_CREATE_ORDER, vo.getId());
                if(null == redisRecord){
                    log.warn("-extRocketMQTemplate----RocketMQ订单事务回查失败, 没有Redis记录");
                    return RocketMQLocalTransactionState.ROLLBACK;
                }
                int retryTimes = (int)redisRecord;
                //1、超过最大检查次数,表示订单超时未支付,关闭订单
                if(retryTimes >= maxTryMums){

                    Book book = bookService.queryBook(vo.getId());
                    book.setTitle("extRocketMQTemplate超出最大次数,关闭了");
                    bookMapper.updateById(book);
                    redisTemplate.opsForHash().delete(RocketMqConstant.REDIS_CREATE_ORDER,vo.getId());
                    log.info("--extRocketMQTemplate- 订单下单事务消息 transID: "+transId+"支付超时,发送消息,释放锁定的库存");
                    return RocketMQLocalTransactionState.COMMIT;
                }else{
                    //2、查询支付宝订单支付状态
                    //2.1 如果支付宝支付成功,这个方法中会更新订单paytype和status,并完成扣减库存
                    Book book = bookService.queryBook(vo.getId());
                    if(book.getPayStatus() == 1){
//                        msg.getHeaders().remove("CHECK_TIME");
//                    localTrans.remove(transId);
                        log.info("--- 订单extRocketMQTemplate下单事务消息 transID: "+transId+";订单号:"+vo.getId()+"已经完成支付,回滚消息");
                        return RocketMQLocalTransactionState.ROLLBACK;
                    }else{
                        log.info("--- 订单extRocketMQTemplate下单事务消息 transID: "+transId+";订单号:"+vo.getId()+"未支付,已回查"+retryTimes+"次,等待下次回查");
                        //2.2 记录回查次数,等待下次回查。
                        redisTemplate.opsForHash().increment(RocketMqConstant.REDIS_CREATE_ORDER,vo.getId().toString(),1);
                        //消息的header不可修改。可以思考下为什么?
//                msg.getHeaders().put("CHECK_TIME",retryTimes+1);
                        return RocketMQLocalTransactionState.UNKNOWN;
                    }
                }
            }else{
                log.info("--extRocketMQTemplate--RocketMQ订单消息格式不对,丢弃消息。");
                return RocketMQLocalTransactionState.ROLLBACK;
            }
        }catch (Exception e){
            log.warn("--extRocketMQTemplate---RcoketMQ下单事务消息状态回查失败, ---",e);
            return RocketMQLocalTransactionState.ROLLBACK;
        }
    }

    /**
     * Get event instance from message payload.
     */
    private BookVO getBusinessMessage(Message msg) throws JsonProcessingException {

        final String json = new String((byte[]) msg.getPayload(), StandardCharsets.UTF_8);
        BookVO bookVO = JSONUtil.toBean(json, BookVO.class);

        return bookVO;
    }
}

notes:

@RocketMQTransactionListener(rocketMQTemplateBeanName="zhmyRocketMQTemplate") //一个事物监听器对应一个事物流程

五、定义两个消费者:

@Slf4j
@Component
@RocketMQMessageListener(consumerGroup = "CancelOrderGroup2", topic = RocketMqConstant.ORDER_ASYNC_TOPIC2)
public class RocketMqCancelOrderReceiver implements RocketMQListener<BookVO> {
    @Autowired
    private BookServiceImpl bookService;
    @Override
    public void onMessage(BookVO bookVO) {
        log.info("监控到订单支付超时事件,取消超时订单:BookVO:{} ", JSONUtil.toJsonStr(bookVO));
        if(ObjectUtil.isNull(bookVO)){
            return;
        }

        try {
            //取消的订单,释放锁定的库存

            bookService.delBook(bookVO);
        } catch (Exception e) {
            log.error("订单取消异常 : 还原库存失败,please check:{}",e.getMessage(),e.getCause());
            throw new RuntimeException();//抛异常出去,rocketmq会重新投递
        }
    }
}
@Slf4j
@Component
@RocketMQMessageListener(consumerGroup = "CancelOrderGroup", topic = RocketMqConstant.ORDER_ASYNC_TOPIC)
public class RocketMqCancelOrderReceiver2 implements RocketMQListener<BookVO> {
    @Autowired
    private BookServiceImpl bookService;
    @Override
    public void onMessage(BookVO bookVO) {
        log.info("监控到订单支付超时事件,取消超时订单:BookVO:{} ", JSONUtil.toJsonStr(bookVO));
        if(ObjectUtil.isNull(bookVO)){
            return;
        }

        try {

            bookService.delBook(bookVO);
        } catch (Exception e) {
            log.error("订单取消异常 : 还原库存失败,please check:{}",e.getMessage(),e.getCause());
            throw new RuntimeException();//抛异常出去,rocketmq会重新投递
        }
    }
}

到此基本结束,开始进行实验:

controller调用:

服务端查看日志:

 

至此大家即可看到,事务多消息只被其对应的监听器监听处理了。 

  • 4
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
SpringBoot中整合RocketMQ发送事务消息的步骤如下: 首先,定义一个生产者类,使用@Component注解将其标记为一个组件,并使用@Autowired注解注入RocketMQTemplate实例。在该类中,可以编写一个sendMsg方法来发送消息。该方法接受两个参数,分别是topic和msg,使用MessageBuilder构建一个消息对象,并通过rocketMQTemplate的sendMessageInTransaction方法发送消息。需要注意的是,该方法的第一个参数要与@RocketMQTransactionListener注解中的txProducerGroup属性保持一致。\[1\] 其次,定义一个消费者类,使用@Component和@RocketMQMessageListener注解将其标记为一个组件,并指定topic和consumerGroup。该类需要实现RocketMQListener接口,并实现其中的onMessage方法,用于处理接收到的消息。\[2\] 最后,在引导类中使用@SpringBootApplication注解标记该类为Spring Boot应用程序的入口,并在main方法中调用SpringApplication的run方法启动应用程序。\[3\] 通过以上步骤,就可以在SpringBoot中整合RocketMQ发送事务消息了。 #### 引用[.reference_title] - *1* *2* *3* [springboot 整合 rocketmq 发送事务消息](https://blog.csdn.net/weixin_42494845/article/details/109362030)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值