如何保证RabbitMQ消息不被重复消费(幂等性)

幂等性:就是用户对于同一操作发起的一次请求或者多次请求的结果是一致的,不会因为多次点击而产生了副作用

消息重复出现在两个阶段

1.生产者重复发送消息,导致消息重复发送到消息队列。

2.MQ的一条消息被消费者消费了多次。

消费者重复消费原因

正常情况下,消费者在消费消息的时候,消费完毕后,会发送一个确认消息(ack)给消息队列,消息队列知道该消息被消费了,就会将该消息从消息队列中删除。

在保证MQ消息不重复的情况下,消费者消费消息成功后,在给MQ发送消息确认的时候出现了网络异常(或者是服务中断),MQ没有接收到确认(ack),此时MQ不会将发送的消息删除,
为了保证消息被消费,当消费者网络稳定后,MQ就会继续给消费者投递之前的消息。这时候消费者就接收到了两条一样的消息。

如何解决消息重复消费的问题

1.消息发送者发送消息时携带一个全局唯一的消息id

2.消费者监听到消息后,根据id在redis或者db中查询是否存在消费记录

3.如果没有消费就正常消费,消费完毕后,写入redis或者db

4.如果消息消费过则直接丢弃

注释:为了确保即使服务挂了也能保持幂等性,你可以在处理消息的过程中引入一些持久化的机制。比如,你可以将已经处理过的消息的唯一标识符存储在数据库中,这样即使服务挂了,重新启动后仍然可以从数据库中检查已处理的消息。这样即使服务挂了,也能保证消息的幂等性。 

编码示例

生产者服务:

    /**
     * @Description:  发送消息 模拟消息重复消费
     *      消息重复消费情景:消息生产者已把消息发送到mq,消息消费者在消息消费的过程中
     *      突然因为网络原因或者其他原因导致消息消费中断
     *      消费者消费成功后,在给MQ确认的时候出现了网络波动,MQ没有接收到确认,
     *      为了保证消息被消费,MQ就会继续给消费者投递之前的消息。这时候消费者就接收到了两条一样的消息
     */
    @GetMapping("/rabbitmq/sendMsgNoRepeat")
    public String sendMsgNoRepeat() {
        String message = "server message sendMsgNoRepeat";
        for (int i = 0; i <10000 ; i++) {
            Message msg = MessageBuilder.withBody((message+"--"+i).getBytes()).setMessageId(UUID.randomUUID()+"").build();
            amqpTemplate.convertAndSend("queueName4",msg);
        }
        return message;
    }

消费者服务:

 方案1:将id存入string中(单消费者场景):

这样一个队列,redis数据只有一条,每次消息过来都覆盖之前的消息,但是消费者多的情况不适用,可能会存在问题,一个消息被多个消费者消费。

    @RabbitListener(queues = "queueName4")//发送的队列名称     @RabbitListener注解到类和方法都可以
    @RabbitHandler
    public void receiveMessage(Message message) throws UnsupportedEncodingException {
        String messageId = message.getMessageProperties().getMessageId();
        String msg = new String(message.getBody(),"utf-8");
 
        String messageRedisValue = redisUtil.get("queueName4","");
        if (messageRedisValue.equals(messageId)) {
            return;
        }
        System.out.println("消息:"+msg+", id:"+messageId);
 
        redisUtil.set("queueName4",messageId);//以队列为key,id为value
    }

方案2:将id存入list中(多消费者场景)

这个方案可以解决多消费者的问题,但是随着mq的消息增加,redis数据越来越多,需要去清除redis数据。

    @RabbitListener(queues = "queueName4")//发送的队列名称     @RabbitListener注解到类和方法都可以
    @RabbitHandler
    public void receiveMessage1(Message message) throws UnsupportedEncodingException {
        String messageId = message.getMessageProperties().getMessageId();
        String msg = new String(message.getBody(),"utf-8");
 
        List<String> messageRedisValue = redisUtil.lrange("queueName4");
        if (messageRedisValue.contains(messageId)) {
            return;
        }
        System.out.println("消息:"+msg+", id:"+messageId);
 
        redisUtil.lpush("queueName4",messageId);//存入list
    }

方案3:将id以key值增量存入string中并设置过期时间:

以消息id为key,消息内容为value存入string中,设置过期时间(可承受的redis服务器异常时间,比如设置过期时间为10分钟,如果redis服务器断了20分钟,那么未消费的数据都会丢了)

    @RabbitListener(queues = "queueName4")//发送的队列名称     @RabbitListener注解到类和方法都可以
    @RabbitHandler
    public void receiveMessage2(Message message) throws UnsupportedEncodingException {
        String messageId = message.getMessageProperties().getMessageId();
        String msg = new String(message.getBody(),"utf-8");
 
        String messageRedisValue = redisUtil.get(messageId,"");
        if (msg.equals(messageRedisValue)) {
            return;
        }
        System.out.println("消息:"+msg+", id:"+messageId);
 
        redisUtil.set(messageId,msg,10L);//以id为key,消息内容为value,过期时间10分钟
    }

### 回答1: RabbitMQ可以通过以下几种方式来保证消息不被重复消费: 1. 消费者手动确认消息消费者在消费消息后,需要手动确认消息已经被消费,这样RabbitMQ才会将该消息从队列中删除。如果消费者没有确认消息RabbitMQ会将该消息重新发送给其他消费者。 2. 消息去重:消费者可以通过记录已经消费过的消息的ID或者其他唯一标识符,来避免重复消费同一条消息。 3. 消息幂等性消费者可以通过设计幂等性的业务逻辑,来保证同一条消息重复消费时不会产生副作用。例如,如果消费者需要向数据库中插入一条记录,可以通过在数据库中设置唯一索引来避免重复插入同一条记录。 4. 消息过期时间:消费者可以设置消息的过期时间,如果消息在指定时间内没有被消费RabbitMQ会将该消息从队列中删除,避免重复消费。 总之,保证消息不被重复消费需要消费者和生产者共同努力,通过合理的设计和配置来避免重复消费的问题。 ### 回答2: Rabbitmq中,确保消息不被重复消费主要有以下两种方式: 1.消息幂等性设计 在RabbitMQ中,在处理消息时通常需要对消息进行一些操作。消息幂等性是指无论接收多少次同一条消息,都能确保在后续处理中只产生一次影响或结果。为了实现消息幂等性设计,我们通常需要在适当的位置对消息进行编号、状态标记等处理,以便在不影响消息处理逻辑的前提下确保消息不被重复消费。 2.手动ACK确认 手动ACK确认意味着当消费者从队列中消费消息后,需要发送一个确认消息RabbitMQ,告诉RabbitMQ这条消息已经被消费。当RabbitMQ收到这个确认时,会将这条消息从队列中移除。如果消息消费失败,消费者可以选择不发确认消息,这样该条消息将被重新放回队列。这种方式可以确保只有当消费者已经正确消费消息时,才能把它从队列中移除,从而保证不会重复消费。此外,RabbitMQ还支持消费者的ACK超时时间设定,如果消费者在设定的时间内未发送确认消息,则RabbitMQ将再次将该消息推送给消费者,以便处理未被确认的消息。 ### 回答3: RabbitMQ 是一个消息队列软件,可以用来存储和转发消息,并且可以用于构建可靠的、异步的分布式系统。在分布式系统中,保证消息的可靠性很重要,尤其是在消息消费后,需要保证消息不被重复消费RabbitMQ 提供了一些机制来保证消息不被重复消费。下面分别介绍这些机制: 第一,消息的确认机制。当消费者从队列中获取消息后,需要向 RabbitMQ 发送确认信息,告诉 RabbitMQ 已经成功接收到了消息RabbitMQ 会在收到确认信息后将消息删除,以确保消息不会被重复消费。 第二,消息的持久化机制。在生产者发送消息时,可以设置消息的持久化属性,使消息RabbitMQ 持久化存储。在消费者从队列中获取消息时,如果消息已经被消费过,但是还没有被删除,那么消息不会被重新分发给其他消费者,因为 RabbitMQ 会保存消息的“投递标记”,以保证消息不被重复消费。 第三,消息幂等性机制。幂等性指的是对同一操作的多次调用所产生的影响相同,即多次调用具有相同的副作用。在 RabbitMQ 中,可以通过消息的唯一标识符来实现消息幂等性。对于接收到的每个消息消费者都需要对消息进行唯一标识符的去重操作,并且判断该消息是否已经被消费过。如果已经被消费过,那么消费者可以选择忽略该消息或者重新消费。 综上所述,RabbitMQ 通过消息确认、持久化和幂等性机制,保证消息不被重复消费。同时,RabbitMQ 还提供了其他一些高级机制,例如消息的过期时间、消息的优先级以及消息的死信队列等,进一步提升了消息队列的可靠性和稳定性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值