Java面试题:RabbitMQ如何防止重复消费?

         Java面试题:RabbitMQ如何防止重复消费?最近有很多小伙伴开始找工作,在面试时,面试官经常问到一个题目:RabbitMQ如何防止重复消费?

有很多小伙伴这个时候都在想,消息怎么就会重复消费呢???.......

二. 面试题考点

如果面试官是健哥的话,那么我想考察的,其实就是候选人除了对技术的基本使用之外,再就是在各种实际应用场景中对可能发生问题的实际处理能力。

所以这道题的考点,最起码有两点:

  • 第一是RabbitMQ中消息的重复消费是如何产生的,我们首先要发现问题,,知道问题产生原因;
  • 第二是针对这个重复消费问题的处理方案及机制。

三. 解题分析

接下来健哥就根据上述考点,带大家来一起分析这个问题的解题思路。

3.1RabbitMQ消息重复消费的产生原因

Java面试题:RabbitMQ如何防止重复消费?_第1张图片

Java面试题icon-default.png?t=M85Bhttp://www.mobiletrain.org/interview/

根据上图,给大家梳理总结出了消息重复消费的产生过程,如下:

  1. 消费方的业务项目从MQ队列中接收数据;
  2. 接着处理业务;
  3. 业务处理成功后,消费方项目给MQ返回ack进行手动确认;
  4. 返回回调执行结果的过程中,因为网络抖动等原因,回调数据时,MQ没有返回成功,所以MQ队列中的数据会再次发给业务项目,造成重复消费。

3.2. RabbitMQ消息重复消费的处理方案

Java面试题:RabbitMQ如何防止重复消费?_第2张图片

针对消息的重复消费问题,健哥根据上图总结的解决思路如下:

  1. 监听器接收MQ队列中的数据;
  2. 利用redis的setnx命令,以消息唯一id为key,以消息内容为value,超时时间设置为10秒,存入redis中;
  3. 如果能够成功存入,说明没有重复消费,则处理业务,处理完业务后返回ack或者nack确认;
  4. 如果存不进去,则说明重复消费,直接返回ack确认的回调信息就可以了。

3.3解决重复消费的案例代码

  • 发送方测试代码
    /**
    * 测试发送
    * @author XXXX
    */
    @SpringBootTest(classes = ProducerApplication.class)
    @RunWith(SpringRunner.class)
    public class TestProducer {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Test
    public void contextLoads() throws IOException {
    //给消息封装一个唯一id对象
    CorrelationData messageId = new CorrelationData(UUID.randomUUID().toString());
    //第四个参数: 设置消息唯一id
    rabbitTemplate.convertAndSend("交换器名字","路由键","千锋健哥测试MQ重复消费处理!!",messageId);
    }
    }
    package com.qf.rabbitmq.topic;

    import com.rabbitmq.client.Channel;
    import org.springframework.amqp.core.Message;
    import org.springframework.amqp.rabbit.annotation.RabbitListener;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.data.redis.core.StringRedisTemplate;
    import org.springframework.stereotype.Component;
    IT应聘面试题_Java面试题_前端面试题_千锋教育千锋教育IT培训机构为大家提供Java面试题,前端面试题,包括python面试题,云计算面试题,软件测试面试题,大数据面试题,UI面试题,网络安全面试题,物联网面试题,unity面试题,影视剪辑面试题,系统整理了IT企业面试题目及IT技术笔试题目,千锋教育应聘面试题库将持续为大家提供IT面试问题大全及答案大全.http://www.mobiletrain.org/interview/
    import java.io.IOException;
    import java.util.concurrent.TimeUnit;

    /**
    * @author 千锋健哥
    */
    @Component
    public class Consumer {

    @Autowired
    private StringRedisTemplate redisTemplate;

    @RabbitListener(queues = "队列名字")
    public void getMessage(String msg, Channel channel, Message message) throws IOException {
    //0. 获取MessageId, 消息唯一id
    String messageId = (String) message.getMessageProperties().getHeaders().get("spring_returned_message_correlation");
    //1. 设置key到Redis
    if(redisTemplate.opsForValue().setIfAbsent(messageId,"0", 10, TimeUnit.SECONDS)) {

    //2. 消费消息
    System.out.println("接收到消息:" + msg);

    //3. 设置key的value为1
    redisTemplate.opsForValue().set(messageId,"1",10,TimeUnit.SECONDS);

    //4. 手动ack
    channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);

    }else {
    //5. 获取Redis中的value即可 如果是1,手动ack
    if("1".equalsIgnoreCase(redisTemplate.opsForValue().get(messageId))){
    channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
    }
    }

    }
    }
    • 接收方测试代码

四. 总结

经过上面的分析,最后健哥再给大家总结一下这个问题的完整答案。

  • 问题产生原因:

因为消费方和MQ服务器网络闪断等原因,造成了接收方消费后,返回给MQ服务器一个ack确认消息,结果MQ没有接收到,造成了重复消费。

  • 解决过程:

利用redis的setnx命令,将消费的消息id存入到redis,超时时间设置为10秒,然后再给mq返回ack。消费前要判断redis中是否存在这个消息id,如果不存在说明没有消费过,则正常消费;如果redis中存在这个消息id,则说明重复消费,直接返回ack,不重复执行业务。

你可能感兴趣的:(java,java-rabbitmq,rabbitmq)