java使用rocketMQ实现幂等性

今天记录下java使用rocketMQ实现幂等性
1、标识请求唯一性:orderFlag 作为请求的唯一标识符,确保该标识符在每次请求中都是唯一的,这样可以避免重复请求。

2、提前检查幂等性:在发送消息之前,可以先检查该请求是否已经被处理,如果已经处理,就不再发送消息。这样可以减轻消息队列和数据库的压力。

3、消费端的幂等性处理:在消息队列的消费端,也需要考虑幂等性。即使请求在生产者端保持幂等,消费端也可能多次处理相同的消息。你可以在消费端根据请求标识符来检查消息是否已经处理,如果已经处理,就不再执行订单生成和保存操作。

4、消息队列的事务性:你已经在发送消息时使用了事务消息,这是一种确保消息不会被重复消费的方式。确保消息队列的消费者端也可以正确处理这些事务消息。

发送消息

 		/**
         * 生成一个标识,当消息队列的消息没有处理完成后
         * 前端根据这个查询订单是否生成
         */
        //当前时间戳
        String orderFlag = UUID.randomUUID().toString();
        /**
         * 发送信息到消息队列
         */
        JSONObject jsonObject = new JSONObject();
        //解决幂等性问题
        jsonObject.put("txNo", orderFlag);
        jsonObject.put("orderFlag", orderFlag);
        String str = jsonObject.toJSONString();
        //生成消息
        Message<String> message = MessageBuilder.withPayload(str).build();
        try {
            //发送一条事务消息,消息是不可消费的
            log.info("提交发送MQ开始参数{}", message);
            rocketMQTemplate.sendMessageInTransaction("commit_virtual_order_producer_group", RocketmqConstants.COMMIT_VIRTUAL_ORDER_TOPIC_T, message, null);
        } catch (Exception e) {
            LogUtil.error("发送事务消息失败", e);
            redisUtil.del(cacheName);
            throw new ServiceException(BizExceptionEnum.SERVER_ERROR);
        }

消费者监听

@Component
@RocketMQMessageListener(consumerGroup = "commit_virtual_order_consumer_group", topic = RocketmqConstants.COMMIT_VIRTUAL_ORDER_TOPIC_T)
public class CommitVirtualOrderConsumerListener implements RocketMQListener<String> {
    @Autowired
    private RedisUtil redisUtil;
    @Autowired
    private OrderVirtualFormService orderVirtualFormService;

    @Override
    public void onMessage(String s) {
        //解析消息
        JSONObject jsonObject = JSONObject.parseObject(s);
        //事务号
        String txNo = jsonObject.getString("txNo");

        //幂等性判断,防止重复消费
        String cacheKey = CacheConstants.CACHE_KEY_COMMIT_VIRTUAL_ORDER_NO + txNo;
        if (redisUtil.hasKey(cacheKey)) {
            //调用下单逻辑
            orderVirtualFormService.commitVirtualOrder(jsonObject);
        }
    }
}

生产者监听

@RocketMQTransactionListener(txProducerGroup = "commit_virtual_order_producer_group")
public class CommitVirtualOrderProducerListener implements RocketMQLocalTransactionListener {
    @Autowired
    private RedisUtil redisUtil;

    /**
     * 事务消息发送后的回调方法,当消息发送给mq成功,此方法被回调
     * @param message
     * @param o
     * @return
     */
    @Override
    public RocketMQLocalTransactionState executeLocalTransaction(Message message, Object o) {
        try {
            JSONObject jsonObject = messageToObj(message);
            //事务号
            String txNo = jsonObject.getString("txNo");
            //幂等判断
            String cacheKey = CacheConstants.CACHE_KEY_COMMIT_VIRTUAL_ORDER_NO + txNo;
            if (redisUtil.hasKey(cacheKey)) {
                //自动向mq发送commit请求,将mq中的该消息改为可消费
                return RocketMQLocalTransactionState.COMMIT;
            }
            //redis中添加幂等操作标识
            redisUtil.set(cacheKey, cacheKey, 86400L);
            //自动向mq发送commit请求,将mq中的该消息改为可消费
            return RocketMQLocalTransactionState.COMMIT;
        } catch (Exception e) {
            e.printStackTrace();
            LogUtil.info("提交虚拟订单消息回滚");
            return RocketMQLocalTransactionState.ROLLBACK;
        }
    }

    /**
     * 事务状态回查
     * @param message
     * @return
     */
    @Override
    public RocketMQLocalTransactionState checkLocalTransaction(Message message) {
        JSONObject jsonObject = messageToObj(message);
        //事务号
        String txNo = jsonObject.getString("txNo");
        //幂等判断
        String cacheKey = CacheConstants.CACHE_KEY_COMMIT_VIRTUAL_ORDER_NO + txNo;
        if (redisUtil.hasKey(cacheKey)) {
            //自动向mq发送commit请求,将mq中的该消息改为可消费
            return RocketMQLocalTransactionState.COMMIT;
        }else {
            return RocketMQLocalTransactionState.UNKNOWN;
        }
    }

    /**
     * message转成对象
     * @param message
     * @return
     */
    private JSONObject messageToObj(Message message){
        String str = new String((byte[]) message.getPayload());
        JSONObject jsonObject = JSONObject.parseObject(str);
        return jsonObject;
    }
}

消费者逻辑主要代码

 //事务号
        String txNoStr = jsonObjectParam.getString("txNo");
        //消息队列的处理参数
        String orderFlag = jsonObjectParam.getString("orderFlag");
        //提交订单传参
        VirtualOrderCommitParam virtualOrderCommitParam = JSON.parseObject(jsonObjectParam.getString("virtualOrderCommitParam"), VirtualOrderCommitParam.class);

        //幂等性判断
        TxNo txNo = txNoMapper.selectById(txNoStr);
        if (txNo != null) {
            return ErrorResponseData.error("已经处理过了");
        }
        //添加事务记录,用于幂等(在这里保存是防止库存多减)
        TxNo result = new TxNo();
        result.setTxNo(txNoStr);
        txNoMapper.insert(result);
        //删除掉幂等性标识
        String cacheKey = CacheConstants.CACHE_KEY_COMMIT_VIRTUAL_ORDER_NO + txNoStr;
        redisUtil.del(cacheKey);

消费者消费后删除幂等性标识,

最后,这只是通过mq消息处理,后续可优化在发消息之前判断改标识是否存在,存在可不要再发,减轻服务器压力,
还有更多方案选择,例如使用分布式锁,前端传入唯一标识,redis的setNx,如果前端传入的数据有唯一标识字段,可以通过插入数据库唯一处理

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值