RabbitMQ高级特性第一篇——消息确认机制

本文详细介绍了RabbitMQ的消息确认机制,包括生产端的消息发送确认(Confirm机制和Return机制)和消费端的消息消费确认(ACK机制),并提供了Java代码示例。通过消息确认机制,可以确保消息的可靠性和成功消费,防止消息丢失。
摘要由CSDN通过智能技术生成

本文代码在RabbitMQ基础篇的基础上进行配置

一、为什么需要消息确认机制

​ 在RabbitMQ基础篇中,我们介绍了使用MQ的优势,但是MQ的引入同时也会给整个项目项目带来一些其他的问题,其中一个问题就是:

如何保证消息的可靠性以及如何确保消息被成功消费了?(常见面试题)。

为了解决这个问题,RabbitMQ提供了消息确认机制。

二、RabbitMQ消息确认机制概述

注意:这是在生产端实现的

RabbitMQ的消息确认机制分为两个部分:

  1. 消息发送确认
  2. 消息消费确认

至于为什么会分成这两个部分,我们可以通过分析RabbitMQ消息传递流程得出答案

RabbitMQ组成

在这里插入图片描述

各组成部分介绍:

  • Producer:消息生产者
  • Channel:消息通道
  • Broker:消息队列服务进程,此进程包括两个部分:Exchange和Queue。
  • Exchange:交换机,按照一定规则叫不同的消息分发给不同的消息队列,过滤消息
  • Queue:消息队列,存储消息的队列,消息到达队列并转发给指定的消费方。(未被处理的消息都放在这)
  • Consumer:消费者,负责处理消息。

RabbitMQ工作流程

  1. 生产者发送消息到Channel
  2. Channel将消息发送给Exchange
  3. Exchange按照指定的匹配规则分发消息
  4. Queue收到消息后存储起来,慢慢发给Consumer进行处理

在这个过程中,Queue是实际上的消息中转站和消息储存者。那么在这个过程中造成消息丢失的原因就有两个:

  1. 消息没有成功发送给Queue
  2. Queue将消息转发给Consumer后,Consumer发生异常没有处理消息,但Queue已经将消息出队了

针对这两个原因,所以RabbitMQ就将消息确认机制分为两部分了

  1. 消息发送确认:确认消息发送给Queue了,即Queue反馈成功获取了消息
  2. 消息消费确认:消息发送给Consumer后,由Consumer反馈的消息确认是否出队该消息

通过这两步,就可以实现:确保消息可靠性以及确保消息被成功消费的要求。

三、消息发送确认

消息发送确认通过两步实现

  1. 确认消息发送到了Exchange(Confirm机制)
  2. 确认消息发送到了Queue(Return机制)

1. Confirm机制

1. 实现原理

生产者将信道设置成confirm模式,一旦信道进入confirm模式,
所有在该信道上面发布的消息都会被指派一个唯一的ID(从1开始),
一旦消息被投递到所有匹配的队列之后,broker就会发送一个确认给生产者(包含消息的唯一ID),
这就使得生产者知道消息已经正确到达目的队列了,如果消息和队列是可持久化的,
那么确认消息会将消息写入磁盘之后发出,broker回传给生产者的确认消息中deliver-tag域包含了确认消息的序列号,
此外broker也可以设置basic.ack的multiple域,表示到这个序列号之前的所有消息都已经得到了处理。

2. 代码实现

创建ConfirmCallback处理器,实现RabbitTemplate.ConfirmCallback接口

/**
 * 消息发送到exchange时会触发这个方法
 *
 * @author 叶子
 * @Description ConfirmCallback实现类
 * @DevelopmentTools IntelliJ IDEA
 * @PackageName icu.yezi.producer.handel
 * @Data 2020/11/21 星期六 15:34
 */
public class ConfirmCallbackHandel implements RabbitTemplate.ConfirmCallback {
    @Override
    public void confirm(CorrelationData correlationData, boolean b, String s) {
        System.out.println("消息唯一标识:"+correlationData);
        System.out.println("确认结果:"+b);
        System.out.println("失败原因:"+s+"(如果没有失败则此项为null)");
    }
}

创建ReturnCallBack处理类,实现RabbitTemplate.ReturnsCallback接口

/**
 * 当消息从交换器发送到对应队列失败时触发
 * (比如根据发送消息时指定的routingKey找不到队列时会触发)
 *
 * @author 叶子
 * @Description ReturnsCallback实现类
 * @DevelopmentTools IntelliJ IDEA
 * @PackageName icu.yezi.producer.handel
 * @Data 2020/11/21 星期六 15:38
 */
public class ReturnCallbackHandel implements RabbitTemplate.ReturnsCallback {
    @Override
    public void returnedMessage(ReturnedMessage returnedMessage) {
        System.out.println("消息内容 body:"+new String(returnedMessage.getMessage().getBody()));
        System.out.println("应答码 replyCode: :"+returnedMessage.getReplyCode());
        System.out.println("原因描述 replyText:"+returnedMessage.getReplyText());
        System.out.println("交换机 exchange:"+returnedMessage.getExchange());
        System.out.println("消息使用的路由键 routingKey:"+returnedMessage.getRoutingKey());
    }
}
3. 在配置类中进行配置
//初始化加载方法,对RabbitTemplate进行配置
    @PostConstruct
    void rabbitTemplate(){
        //消息发送确认,发送到交换器Exchange后触发回调
        rabbitTemplate.setConfirmCallback(new ConfirmCallbackHandel());
        //消息发送确认,如果消息从交换器发送到对应队列失败时触发(比如根据发送消息时指定的routingKey找不到队列时会触发)
        rabbitTemplate.setReturnCallback(new ReturnCallbackHandel());
    }
4. 在yml文件中进行配置
spring:
  rabbitmq:
    host: localhost
    port: 5672
    username: 叶子
    password: 123456789
    virtual-host: /test
    publisher-returns: true # 消息发送交换机确认
    publisher-confirm-type: correlated # 消息发送队列回调 none代表不开启

四、消息消费确认

注意:这个是在消费端进行配置

1. ACK确认机制

有两种确认模式:

  1. none 自动确认
  2. manual 手动确认

注意:一般不采用自动确认模式,
原因:自动确认模式下,当消费者接受消息后,消息队列会自动删除该消息。
此时,如果消费者消费失败,那么该条消息就丢失了

2. 在yml文件中配置ACK为手动模式

spring:
  rabbitmq:
    host: localhost
    port: 5672
    username: 叶子
    password: 123456789
    virtual-host: /test
    listener:
      simple:
        acknowledge-mode: manual  # 手动

3. 在配置类中进行配置

/**
 * @author 叶子
 * @Description RabbitMQ配置类(消费者)
 * @DevelopmentTools IntelliJ IDEA
 * @PackageName icu.yezi.consumer.config
 * @Data 2020/11/25 星期三 11:20
 */
@Configuration
public class RabbitMQConfig {

    //RabbitMQ监听容器
    @Bean
    public SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory(ConnectionFactory connectionFactory){
        SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
        factory.setConnectionFactory(connectionFactory);
        //设置并发
        factory.setConcurrentConsumers(1);
        SimpleMessageListenerContainer s=new SimpleMessageListenerContainer();
        //最大并发
        factory.setMaxConcurrentConsumers(1);
        //消息接收——手动确认
        factory.setAcknowledgeMode(AcknowledgeMode.MANUAL);
        //设置超时
        factory.setReceiveTimeout(2000L);
        //设置重试间隔
        factory.setFailedDeclarationRetryInterval(3000L);
        //监听自定义格式转换
        //factory.setMessageConverter(jsonMessageConverter);
        return factory;
    }

}

4. 消息确认常用方法

1. 确认消费的方法

void basicAck(long deliveryTag, boolean multiple) throws IOException

参数说明:

  1. deliveryTag:消息ID,从1开始
  2. multiple:是否批量,将一次性ack所有小于deliveryTag的消息。
2. 反馈消息消费失败的方法

void basicNack(long deliveryTag, boolean multiple, boolean requeue) throws IOException

basicReject(long deliveryTag, boolean requeue) throws IOException

参数说明:

  1. deliveryTag:消息ID,从1开始
  2. multiple:是否批量,将一次性拒绝所有小于deliveryTag的消息。
  3. requeue:被拒绝的是否重新入队列。

5. 代码编写

/**
 * @author 叶子
 * @Description RabbitMQ - 消息队列监听类
 * @DevelopmentTools IntelliJ IDEA
 * @PackageName icu.yezi.consumer.listener
 * @Data 2020/11/20 星期五 16:20
 */
@Component
public class RabbitMQListener {

    /**
     *
     * 确认消费成功的方法
     *  void basicAck(long deliveryTag, boolean multiple) throws IOException
     *
     * 参数说明:index
     *      1. deliveryTag:消息ID,从1开始
     *      2. multiple:是否批量,将一次性ack所有小于deliveryTag的消息。
     *
     * @param message
     * @param channel
     */
    @RabbitListener(queues = "boot.queue.user")
    public void listenerQueueUser(Message message, Channel channel){
        System.out.println("普通用户阅读了"+new String(message.getBody()));
        System.out.println(message.getMessageProperties().getDeliveryTag());
        try {
            channel.basicAck(message.getMessageProperties().getDeliveryTag(),true);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 失败确认
     *  方法一:void basicNack(long deliveryTag, boolean multiple, boolean requeue) throws IOException
     *  方法二:basicReject(long deliveryTag, boolean requeue) throws IOException
     * 参数说明:
     *      1. deliveryTag:消息ID,从1开始
     *      2. multiple:是否批量,将一次性拒绝所有小于deliveryTag的消息。
     *      3. requeue:被拒绝的是否重新入队列。
     *
     * @param message
     * @param channel
     */
    @RabbitListener(queues = "boot.queue.user.vip")
    public void listenerQueueUserVip(Message message,Channel channel){
        System.out.println("VIP用户阅读了"+new String(message.getBody()));
        try {
            channel.basicNack(message.getMessageProperties().getDeliveryTag(),true,true);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值