RabbitMQ如何保证消息幂等?

2023-10-30 14:44:42 浏览数 (1)

RabbitMQ如何保证消息幂等?

1、生产端做消息幂等 (即不重复投递)

在生产端的话,其实消费端做好幂等,生产端就算投递多次,也无所谓了。 如果实在想在生产者做幂等的话,可以参考消费端的思路,比如通过redis的 setnx (key可以设计成 producer:具体业务:具有唯一性的某几个或者某一个业务字段 作为key) ,添加防重表等等。但是我个人觉得没必要。把消费端做好幂等就可以了。

2、消费端做消息幂等 (即不重复消费)

A、方案

代码语言:javascript复制
 /** 
  * 是否能消费,用于防止重复消费
  * false 代表未消费过 ,true代表消费过
  * 
  * @param  content 
  * @param  queueName 
  * @return  
 */
private Boolean checkConsumedFlag(T content, String queueName) {
    String messageKey = queueName   ":"   content.getId();
    if (StringUtils.isBlank(redisTemplate.opsForValue().get(messageKey))) {
        //从redis中没获取到value,说明未消费过该消息,返回true
        return false;
    } else {
        //获取到了value说明消费过,然后将该消息标记为已消费并直接响应ack,不进行下边的业务处理,防止消费n次(保证幂等)
        redisTemplate.opsForValue().set(messageKey, "lock", 60, TimeUnit.SECONDS);
        //事实上,set操作应该放在业务执行完后,确保真正消费成功后执行。这里偷个懒。写在业务执行前了。
        return true;
    }
}

B、方案(防重表)

并发高情况下可能会有IO瓶颈 (先读在写) 该方式需要在发送消息时候,指定一个业务上唯一的字段。 如 xzll:order:10001 (10001代表订单id) 然后,在消费端获取该字段,并插入到防重表中(插入代码写在哪?) 如果你声明了事务,那么插入防重这段代码位置无需关注(因为出现异常肯定会回滚), 如果没实现事务,那么最好在执行完业务逻辑后,再插入防重表,保证防重表中的数据肯定是消费成功的。实现步骤: 接收到消息后,select count(0) from 防重表 where biz_unique_id=message.getBizUniqueId(); 如果大于0,那么说明以及消费过,将直接ack,告知mq删除该消息。如果=0说明没消费过。进行正常的业务逻辑。

C、方案(唯一键 : 真正保证了幂等)

直接写) 如果消费端业务是新增操作,我们可以为某几个或者某一个字段设置业务上的唯一键约束, 如果重复消费将会插入两条相同的记录,数据库会报错从而可以保证数据不会插入两条。

D、方案(乐观锁)

并发高下也可能会产生IO瓶颈 (先读再写) 如果消费端业务是更新操作(例如扣减库存), 可以给业务表加一个 version 字段,每次更新把 version 作为条件,更新之后 version 1。 由于 MySQL的innoDB是行锁,当其中一个请求成功更新之后,另一个请求才能进来(注意此时该请求拿到的version还是1), 由于版本号version已经变成 2,所以更新操作不会执行,从而保证幂等。

0 人点赞