rabbitmq高级特性(1):消息确认,持久性,发送方确认和重试机制

目录

1.消息确认机制

1.1.消息确认机制介绍

2.1.Spring-AMQP的三种消息确认(重点)

2.持久性

2.1.交换机持久性

2.2.队列持久性

2.3.消息持久性

3.发送方确认

3.1.confirm确认模式

3.2.return退回模式

4.重试机制

4.1.重试机制定义

4.2.重试机制代码的准备工作

4.3.重试机制演示


1.消息确认机制
1.1.消息确认机制介绍

这里的消息确认机制,指的是消费者对消息的确认,而不是生产者。

(1)背景缘由

当消费者把消息发送出去后,就会把消息删除。如果消费者这边处理消息成功,则相安无事;但是如果处理异常,消息也就会丢失。所以就需要设置消费者的消息确认模式

(2)消息确认的机制

消息确认机制分为两个大类:自动确认和手动确认

手动确认又分为三种模式:肯定确认、否定确认、否定批量确认

对于rabbitmq的操作有两种,我们主要介绍第二种

  1. 直接使用amqb提供的包(RabbitMQ Java Client库)
  2. 使用spring集成进来的

第一种介绍:

对于第一种主要把autoAck参数设置成false即为手动确认,改成true即为自动确认。收到确认需要调用Channel.basicAck函数

java 复制代码
//自动确认,第二个参数为true
channel.basicConsume(Constants.RPC_RESPONSE_QUEUE,true,consumer);
//收到确认,第二个参数为false
DefaultConsumer consumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                String request = new String(body,"UTF-8");
                System.out.println("接收到请求:"+ request);
                String response = "针对request:"+ request +", 响应成功";
                AMQP.BasicProperties basicProperties = new AMQP.BasicProperties().builder()
                        .correlationId(properties.getCorrelationId())
                        .build();
                channel.basicPublish("", Constants.RPC_RESPONSE_QUEUE, basicProperties, response.getBytes());//返回响应
                channel.basicAck(envelope.getDeliveryTag(), false);//手动确认
            }
        };
channel.basicConsume(Constants.RPC_REQUEST_QUEUE,false,consumer);//false
  • 自动确认:当autoAck为true时,RabbitMQ会自动把发出去的消息设置为确认,然后从内存(硬盘)删除,而不会去管消费者是否真正的消费到该消息。适用对消息可靠性要求不高的场景
  • 收到确认:当autoAck为false时,RabbiMQ会等待消费者显示地调用Basic.Ack命令,回复确认信号之后才会删除该消息。适用对消息可靠性要求比较高的场景
2.1.Spring-AMQP的三种消息确认(重点)

关于消息确认机制的问题:none自动确认(不管消息是否收到)、auto成功收到删除,异常收到不确认、manual手动确认

none:

**(1)成功处理:**不考虑

(2)消费异常:队列也会直接将消息删除

auto:

(1)成功处理--不考虑

(2)消费异常:队列不会把消息删除

  • 没有进行异常捕获 --- 会一直尝试消费消息并一直报异常,队列不会把该消息删除,也就是不会确认消息
  • 进行了异常捕获 --- 消费逻辑只会执行一次并,队列会删除消息

**manual:**手动确认模式(肯定确认和否定确认)

(1)成功处理

  • 进行了肯定确认,队列将消息进行删除
  • 没有进行肯定确认,队列会将消息从ready至为unacked

(2)消费异常

下面的情况是进行了异常的捕获

  • 进行否定确认,但不重新入队 --- 队列会直接将消息删除
  • 进行了否定确认,并且重新入队 --- 消息会重新入队,也就是队列不会将消息删除

以上是消息确认机制的情况总结。

下面展示代码:

配置文件:除了manual模式,其他模式只需要配置了就会生效

mamual模式的确认机制:

java 复制代码
@RabbitListener(queues = Constant.ACK_QUEUE)
public void ackQueue2(Message message, Channel channel) throws IOException {
    long deliveryTag = message.getMessageProperties().getDeliveryTag();
    System.out.println("消息是:"+new String(message.getBody()));
    System.out.println("deliveryTag下标是:"+deliveryTag);
    try {
        System.out.println("消费异常前");
        int a=10/0;
        System.out.println("消费异常后");
        channel.basicAck(deliveryTag, false);
    }catch (Exception e) {
        System.out.println("消费异常");
        channel.basicNack(deliveryTag, false, true);
    }
}

下面这一段写的无需观看,都是一些学习时写的混乱思路


Spring-AMQP提供了三种消息确认机制

java 复制代码
public enum AcknowledgeMode {
    NONE ,
    MANUAL ,
    AUTO ;
}
  1. AcknowledgeMode.NONE:这种也就是对应自动确认模式。消息一旦投递给消费者,不关消费者是否成功处理,RabbitMQ都会自动确认消息,就会把消息删除。
  2. AcknowledgeMode.AUTO(默认):这种模式下,消费者处理成功会自动确认消息,但是如果处理失败就会抛出异常,不会确认消息。
  3. AcknowlegeMode.MANUAL:手动确认模式。消费者必须在成处理消息后显示的调用basicAck方法来确认消息。如果消息未被确认,RabbitMQ会认为消息尚未被成功处理,并且会重新投递消息。

(1)收到确认的两个方法

(2)先准备好下面的代码

1)常量类

java 复制代码
public class Constant {

    //消息确认机制
    public static final String ACK_QUEUE = "ack_queue";
    public static final String ACK_EXCHANGE = "ack_exchange";
    
}

2)配置类和配置文件

java 复制代码
@Configuration
public class AckConfig {

    //消息确认机制
    @Bean("ackQueue")
    public Queue ackQueue() {
        return QueueBuilder.durable(Constant.ACK_QUEUE).build();
    }

    @Bean("ackDirectExchange")
    public DirectExchange ackDirectExchange() {
        return ExchangeBuilder.directExchange(Constant.ACK_EXCHANGE).build();
    }

    @Bean("ackBinding")
    public Binding ackBinding(@Qualifier("ackDirectExchange") DirectExchange ackDirectExchange,
                              @Qualifier("ackQueue") Queue ackQueue) {
        return BindingBuilder.bind(ackQueue).to(ackDirectExchange).with("ack");
    }


}
XML 复制代码
spring:
  rabbitmq:
    addresses: amqp://study:study@8.138.121.41:5672/extension1
    listener:
      simple:
        acknowledge-mode: none #消息确认模式

3)生产者

java 复制代码
@RequestMapping("/produce")
@RestController
public class ProduceController {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @RequestMapping("/ack")
    public String ack() {
        rabbitTemplate.convertAndSend(Constant.ACK_EXCHANGE,"ack","这是一条ack消息");
        return "消息发送成功";
    }
}

4)消费者

java 复制代码
@Component
public class AckListener {


    @RabbitListener(queues = Constant.ACK_QUEUE)
    public void ackQueue(Message message, Channel channel) {
        System.out.println("接受到消息:"+ new String(message.getBody()));
    }
}

程序正常运行:

(3)消息确认模式

1)第一种:none

正常运行:会直接确认消息并且从队列中删除

程序抛出异常:也会自动确认消息并且从队列中删除

小结:只要消费者把消息成功发送出去,不管消费者如何处理,都会自动确认并且删除消息

2)第二种:auto

正常运行:会直接确认消息并且从队列中删除

异常处理:消息不会确认,并且消息会不停地尝试重新入队

小结: 消费者成功处理消息就会确认消息,发生异常不会确认并尝试重新发送消息。

3)第三种:manual

正常情况并确认:需要手动确认消息

java 复制代码
@Component
public class AckListener {


    @RabbitListener(queues = Constant.ACK_QUEUE)
    public void ackQueue(Message message, Channel channel) {
        long deliveryTag = message.getMessageProperties().getDeliveryTag();//消息的序号
        try {
            System.out.println("异常情况前");
            int a=10/0;
            System.out.println("异常情况后");
            System.out.println("接受到消息:"+ new String(message.getBody()));
            channel.basicAck(deliveryTag, false);//收到确认消息
        }catch (Exception e) {
            System.out.println("尝试重新入队列");
        }
    }
}

这种就是正常的情况,不演示

正确情况不确认:会在队列中一直存在,不确认就会一直存在,消费者也会一直拿到该消息。如果下一个消费者进行了确认才会删除

异常情况并确认:在捕获异常中进行否定确认(一样也会确认消息)

java 复制代码
@Component
public class AckListener {


    @RabbitListener(queues = Constant.ACK_QUEUE)
    public void ackQueue(Message message, Channel channel) throws IOException {
        long deliveryTag = message.getMessageProperties().getDeliveryTag();//消息的序号
        try {
            System.out.println("异常情况前");
            int a=10/0;
            System.out.println("异常情况后");
            System.out.println("接受到消息:"+ new String(message.getBody()));
            channel.basicAck(deliveryTag, false);//收到确认消息
        }catch (Exception e) {
            channel.basicNack(deliveryTag,false,false);//b:非批量确认,b1:不进行重新入队
            //System.out.println("尝试重新入队列");
        }
    }
}

异常情况确认并重新入队:消息重新入队,并且一直尝试重新消费

2.持久性

背景:RabbitMQ的持久性也是保证可靠性的一种机制。RabbitMQ持久性分为交换机持久性、队列持久性和消息持久性。

2.1.交换机持久性

(1)持久性定义

这里交换机的持久性是指在RabbitMQ重启前后,交换机是否还存在。如果设置为持久性,则会一直存在,反之重启后交换机就会被删除。

RabbitMQ重启的定义:在服务器上进行重启启动。比如在linux上使用systemctl restart rabbitmq-server.service进行重新启动

(2)Spring设置交换机持久化

spring集成进来的交换机在创建的时候是默认为持久化的

java 复制代码
ExchangeBuilder. topicExchange (Constant. ACK_EXCHANGE_NAME ).durable(true).build()

如果要设置成非持久化,只需要设置成false即可。

java 复制代码
ExchangeBuilder.directExchange(Constant.PRE_EXCHANGE).durable(false).build();

(3)持久化与非持久化演示

持久化:交换机仍然存在

非持久化:重启后框框中的队列已经不见了

2.2.队列持久性

(1)持久性定义

定义:队列持久化和交换机一样,指的都是RabbitMQ服务器重启前后是否还存在。持久化,服务器重启后仍然存在,队列中的消息也会存在;非持久化,当服务器重启后队列就会被删除,队列中的消息也会被删除。

(2)设置持久化

创建的队列默认是持久化的

设置的方法:

java 复制代码
QueueBuilder.durable(Constant.ACK_QUEUE).build();//持久化(默认)
QueueBuilder.nonDurable(Constant.PRE_QUEUE).build();//非持久化队列

(3)持久化与非持久化演示

持久化:重启前后消息队列存在,如果里面有消息,也仍然存在

非持久化:重启后队列消失,消息也消失

(4)可靠性的保证

如果只是队列持久性,但是消息不持久性,MQ重启之后,消息仍然会消失,也就是不可靠。所以要想保证消息可靠性,就需要同时设置消息持久性和队列持久化。

2.3.消息持久性

(1)定义

消息的持久化,放入队列中如果是持久化,那个mq服务器重启后消息也仍然会存在(队列也要是持久化);反之如果消息是非持久化,那么重启服务后消息将会被删除。

(2)设置消息持久性

对于消息的持久性属性,需要额外设置

消息默认是持久化的。如果队列是非持久化,消息也会被修改成非持久化。

rabbitmq-client设置消息持久化:设置第三个属性

java 复制代码
//⾮持久化信息
channel.basicPublish("",QUEUE_NAME,null,msg.getBytes());

//持久化信息
channel.basicPublish("",QUEUE_NAME,MessageProperties.PERSISTENT_TEXT_PLAIN ,msg.getBytes());

spring内置:需要设置new MessageProperties()中的MessageProperties()属性

java 复制代码
Message message = new Message("".getBytes(),new MessageProperties());

//1.消息非持久化
message.getMessageProperties().setDeliveryMode(MessageDeliveryMode.NON_PERSISTENT);
//2.消息持久化
message.getMessageProperties().setDeliveryMode(MessageDeliveryMode.PERSISTENT);

小结:想要消息做到持久化,必须要求队列和消息都为持久化才可以,其他情况都不可以。

3.发送方确认

要想保证消息持久化,前提是消息已经到达了服务器,那如果保证消息可以发送到服务器呢?RabbitMQ就给我们提供了两种方式:事务机制和发送方确认机制,我们介绍后者。

发送方确认机制也就是:publisher confirm 机制,该机制也分为两种:confirm确认模式和return退回模式

confirm确认模式运用在生产者和交换机之间;而return退回模式运用在交换机和队列之间。

3.1.confirm确认模式

(1)介绍confirm

1)工作的地方

2)定义

生产者发送消息后,会对发送端设置一个监听,无论是否到达Exchange,这个监听都会被执行。如果Exchange成功收到,ACK为true,如果没收到,为false

(2)使用confirm模式

1)第一步:配置确认模式

java 复制代码
spring:
  rabbitmq:
    addresses: amqp://study:study@8.138.121.41:5672/extension1
    listener:
      simple:
        #acknowledge-mode: none #消息确认模式
        #acknowledge-mode: auto #消息确认模式
        acknowledge-mode: manual #消息确认模式
    publisher-confirm-type: correlated #消息发送确认

2)编写准备代码

已准备好的队列和交换机:

java 复制代码
//发送方确认机制
@Bean("confirmQueue")
public Queue confirmQueue() {
    return QueueBuilder.durable(Constant.CONFIRM_QUEUE).build();
}
@Bean("confirmDirectExchange")
public DirectExchange confirmDirectExchange() {
    return ExchangeBuilder.directExchange(Constant.CONFIRM_EXCHANGE).build();
}

@Bean("confirmBinding")
public Binding confirmBinding(@Qualifier("confirmDirectExchange")Exchange exchange,@Qualifier("confirmQueue")Queue queue ) {
    return BindingBuilder.bind(queue).to(exchange).with("comfrim").noargs();
}

3)编写生产者代码

因为需要在交换机收到消息时回复确认,所以就需要回调方法,这个需要在rabbitmq客户端对象上进行设置。

设置客户端的属性:

java 复制代码
@Configuration
public class RabbitTemplateConfig {

    //1.返回原装的rabbitmqTemplate
    @Bean
    public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
        RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
        return rabbitTemplate;
    }

    //2.返回confirm模式的
    @Bean
    public RabbitTemplate confirmRabbitTemplate(ConnectionFactory connectionFactory) {
        RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
        //设置confirm回调方法
        rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
            @Override
            public void confirm(CorrelationData correlationData, boolean b, String s) {
                /**
                 *  b:是否收到ack
                 *  correlationData: 消息id
                 *  s: 原因
                 */
                if(b) {
                    System.out.printf("接收到消息,消息id: %s \n",correlationData==null?"null":correlationData.getId());
                }else {
                    System.out.printf("未接收到消息,消息id: %s,原因: %s \n",correlationData==null?"null":correlationData.getId(),s);
                    //下面可以做相应的消息重发
                }
            }
        });
        return rabbitTemplate;
    }

}

一个客户端修改了属性,后续的所有操作都会生效,并且只能设置一次,多次设置会报错。

在生产者代码中注入属性并发送消息:

java 复制代码
@Resource(name = "confirmRabbitTemplate")
private RabbitTemplate confirmRabbitTemplate;

 //confirm确认模式
    @RequestMapping("/confirm")
    public String confirm() {
        CorrelationData correlationData  =new CorrelationData("1");//设置消息的id
        confirmRabbitTemplate.convertAndSend(Constant.CONFIRM_EXCHANGE,"confirm11","我是一条confirm test",correlationData);
        return "confirm";
    }

如果消息没有成功到达指定的交换机,ack就为false。这里不管理会消息是否到达队列。

(3)confirm模式小结

  1. 需要配置为确认模式
  2. 设置rabbitmq客户端的属性
  3. 消息只要成功发送到指定的交换机,ack就为true,也就是说明成功收到消息。
3.2.return退回模式

confirm模式和return模式可以一起使用并不互斥

(1)模式定义

定义:消息到达Exchange后,就会根据路由规则将消息放入指定的队列中。在这个过程中开启了return模式,只要存在一条消息无法到达队列(比如路由规则不匹配或者队列不存在),就可以选择把消息退回给发送者。选择是否发送回给发送者,就需要设置一个回调方法(和confirm模式一样)

(2)return退回模式代码编写

1)配置文件

这里上述的confirm已经配置好,这里无需再进行配置。

java 复制代码
spring:
  rabbitmq:
    addresses: amqp://study:study@8.138.121.41:5672/extension1
    listener:
      simple:
        #acknowledge-mode: none #消息确认模式
        #acknowledge-mode: auto #消息确认模式
        acknowledge-mode: manual #消息确认模式
    publisher-confirm-type: correlated #消息发送确认(包含confirm和return)

2)编写准备代码

这里的队列和交换机使用上面的。

3)设置return回调方法

java 复制代码
//2.返回confirm模式的
    @Bean
    public RabbitTemplate confirmRabbitTemplate(ConnectionFactory connectionFactory) {
        RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
        //1.设置confirm回调方法
        rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
            @Override
            public void confirm(CorrelationData correlationData, boolean b, String s) {
                /**
                 *  b:是否收到ack
                 *  correlationData: 消息id
                 *  s: 原因
                 */
                if(b) {
                    System.out.printf("接收到消息,消息id: %s \n",correlationData==null?"null":correlationData.getId());
                }else {
                    System.out.printf("未接收到消息,消息id: %s,原因: %s \n",correlationData==null?"null":correlationData.getId(),s);
                    //下面可以做相应的消息重发
                }
            }
        });
        //2.设置return回调
        rabbitTemplate.setMandatory(true);//开启
        rabbitTemplate.setReturnsCallback(new RabbitTemplate.ReturnsCallback() {

            @Override
            public void returnedMessage(ReturnedMessage returnedMessage) {
                System.out.println("消息已被退回:"+returnedMessage);
                //具体退回逻辑省略
            }
        });
        return rabbitTemplate;
    }

4.重试机制

4.1.重试机制定义

重试机制指的是在消费消息时发送异常情况,进行对消息重新入队的机制。如果入队成功,就相当于没有消费;如果入队失败,队列就把消息删除。

(1)在消息发送时,可能会存在很多的问题导致消息发送失败,RabbitMQ的重试机制就运行消息可以重新发送

(2)一些网络问题比如:网络故障、服务器不可以、资源等问题,是有可能重新发送成功的;但是如果是由于代码的问题导致,就无法重新发送成功

(3)为了防止无休止的重试,就可以设置重试的次数

(4)重试机制需要配合消息确认机制。如果消息为自动确认,消息到达就会被自动删除,这个时候重试机制也就没有了用武之地。

4.2.重试机制代码的准备工作

(1)配置重试机制

java 复制代码
spring:
  rabbitmq:
    listener:
      simple:
        retry:
          enabled: true #开启消费者失败重试
          initial-interval: 5000ms #初始失败等待时长为5秒
          max-attempts: 5 # 最⼤重试次数(包括⾃⾝消费的⼀次)   

(2)队列和交换机的编写

生产者:

java 复制代码
//retry模式
@RequestMapping("/retry")
public String retry() {
    //System.out.println("我进来了");
    rabbitTemplate.convertAndSend(Constant.RETRY_EXCHANGE,"retry","我是retry ......");
    return "retry";
}

队列和交换机:

java 复制代码
//重试机制
@Bean("retryQueue")
public Queue retryQueue() {
    return QueueBuilder.durable(Constant.RETRY_QUEUE).build();
}
@Bean("retryDirectExchange")
public DirectExchange retryDirectExchange() {
    return ExchangeBuilder.directExchange(Constant.RETRY_EXCHANGE).build();
}

public Binding retryBinding(@Qualifier("retryDirectExchange")Exchange exchange,@Qualifier("retryQueue")Queue queue) {
    return BindingBuilder.bind(queue).to(exchange).with("retry").noargs();
}

(3)消费者代码编写

java 复制代码
@Configuration
public class RetryListener {
    
    //自动确认的消费者
    //@RabbitListener(queues = Constant.RETRY_QUEUE)
    public void handleMessage(Message message) throws Exception {
        System.out.println("消费消息:"+new String(message.getBody()));
        System.out.println("异常前");
        int a=9/0;
        System.out.println("异常后");
    }
    
    //自动确认的消费者
    @RabbitListener(queues = Constant.RETRY_QUEUE)
    public void handlerMessage(Message message, Channel channel) throws Exception {
        long deliveryTag = message.getMessageProperties().getDeliveryTag();
        System.out.printf("["+Constant.RETRY_QUEUE+"]接收到消息: %s, deliveryTag: %s \n", new String(message.getBody(), "UTF-8"), deliveryTag);
        try {
            int num = 3/0;
            System.out.println("业务处理完成");
            channel.basicAck(deliveryTag, false);
        }catch (Exception e){
            channel.basicNack(deliveryTag, false, true);
        }
    }
    
}

(4)回忆消息确认机制

1)NONE:消息一旦投递给消费者,不关消费者是否成功处理,RabbitMQ都会自动确认消息,就会把消息删除。

2)MANUAL:消费者必须在成处理消息后显示的调用basicAck方法来确认消息。如果消息未被确认,RabbitMQ会认为消息尚未被成功处理,并且会重新投递消息。

3)AUTO:消费者处理成功会自动确认消息,但是如果处理失败就会抛出异常,不会确认消息。

4.3.重试机制演示

演示三种消息确认模式下的重试机制(正常消费情况无法触发重试,所以都是在异常情况下)

(1)none模式下重试

代码发送异常并进行重试:

重试次数完成还是无法入队,就进行了报错。

(2)auto模式下重试

重试次数完成还是无法入队,就进行了报错。

以上两种统称为自动确认,都是可以搭配重试机制

(3)manual模式下重试

  • 发送异常否定确认并重新入队

使用的是否定确认的重复入队机制,并不会采取重试机制,最后队列不会把消息删除

  • 发送异常否定确认,不进行重新入队

队列删除消息,不会进行重试


相关推荐
茶杯梦轩4 天前
从零起步学习RabbitMQ || 第三章:RabbitMQ的生产者、Broker、消费者如何保证消息不丢失(可靠性)详解
分布式·后端·面试
回家路上绕了弯6 天前
深入解析Agent Subagent架构:原理、协同逻辑与实战落地指南
分布式·后端
用户8307196840826 天前
Spring Boot 集成 RabbitMQ :8 个最佳实践,杜绝消息丢失与队列阻塞
spring boot·后端·rabbitmq
用户8307196840828 天前
RabbitMQ vs RocketMQ 事务大对决:一个在“裸奔”,一个在“开挂”?
后端·rabbitmq·rocketmq
初次攀爬者9 天前
RabbitMQ的消息模式和高级特性
后端·消息队列·rabbitmq
初次攀爬者11 天前
ZooKeeper 实现分布式锁的两种方式
分布式·后端·zookeeper
让我上个超影吧12 天前
消息队列——RabbitMQ(高级)
java·rabbitmq
塔中妖12 天前
Windows 安装 RabbitMQ 详细教程(含 Erlang 环境配置)
windows·rabbitmq·erlang
断手当码农12 天前
Redis 实现分布式锁的三种方式
数据库·redis·分布式
初次攀爬者12 天前
Redis分布式锁实现的三种方式-基于setnx,lua脚本和Redisson
redis·分布式·后端