RabbitMQ消息可靠性保证机制3--消费端ACK机制

消费端ACK机制

​ 在这之前已经完成了发送端的确认机制。可以保证数据成功的发送到RabbitMQ,以及持久化机制,然尔这依然无法完全保证整个过程的可靠性,因为如果消息被消费过程中业务处理失败了,但是消息却已经被标记为消费了,如果又没有任何重度机制,那结果基本等于丢消息。在消费端如何保证消息不丢呢?

在rabbitMQ的消费端会有ACK机制。即消费端消费消息后需要发送ACK确认报文给Broker端,告知自己是否已经消费完成,否则可能会一直重发消息直到消息过期(AUTO模式)。同时这个也是最终一致性、可恢复性的基础。一般有如下手段:
  1. 采用NONE模式,消费的过程中自行捕捉异常,引发异常后直接记录日志并落到异常处理表,再通过后台定时任务扫描异常恢复表做重度动作。如果业务不自行处理则有丢失数据的风险。
  2. 采用AUTO(自动ACK)模式,不主动捕获异常,当消费过程中出现异常时,会将消息放回Queue中,然后消息会被重新分配到其他消费节点(如果没有则还是选择当前节点)重新被消费,默认会一直重发消息并直到消费完成返回ACK或者一直到过期。
  3. 采用MANUAL(手动ACK)模式,消费者自行控制流程并手动调用channel相关的方法返回ACK。
7.6.1 手动ACK机制-Reject

maven导入

            <dependency>
                <groupId>com.rabbitmq</groupId>
                <artifactId>amqp-client</artifactId>
                <version>5.9.0</version>
            </dependency>

生产者

import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;

import java.nio.charset.StandardCharsets;

public class Product {
  public static void main(String[] args) throws Exception {

    ConnectionFactory factory = new ConnectionFactory();

    factory.setUri("amqp://root:123456@node1:5672/%2f");

    try (Connection connection = factory.newConnection();
        Channel channel = connection.createChannel(); ) {

      // 定义交换器队列
      channel.exchangeDeclare("ack.ex", BuiltinExchangeType.DIRECT, false, false, false, null);
      // 定义队列
      channel.queueDeclare("ack.qu", false, false, false, null);
      // 队列绑定
      channel.queueBind("ack.qu", "ack.ex", "ack.rk");

      for (int i = 0; i < 5; i++) {
        byte[] sendBytes = ("hello-" + i).getBytes(StandardCharsets.UTF_8);
        channel.basicPublish("ack.ex", "ack.rk", null, sendBytes);
      }
    } catch (Exception e) {
      e.printStackTrace();
    }
  }
}

消费者

import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.Envelope;
import java.io.IOException;

public class Consumer {
  public static void main(String[] args) throws Exception {
    ConnectionFactory factory = new ConnectionFactory();
    factory.setUri("amqp://root:123456@node1:5672/%2f");
    Connection connection = factory.newConnection();
    Channel channel = connection.createChannel();
    channel.queueDeclare("ack.qu", false, false, false, null);
    DefaultConsumer consumer =
        new DefaultConsumer(channel) {
          @Override
          public void handleDelivery(
              // 消费者标签
              String consumerTag,
              // 消费者的封装
              Envelope envelope,
              // 消息属性
              AMQP.BasicProperties properties,
              // 消息体
              byte[] body)
              throws IOException {

            System.out.println("确认的消息内容:" + new String(body));
            // 找收消息
            // Nack与Reject的区别在于,nack可以对多条消息进行拒收,而reject只能拒收一条。
            // requeue为true表示不确认的消息会重新放回队列。
            channel.basicReject(envelope.getDeliveryTag(), true);
          }
        };

    channel.basicConsume(
        "ack.qu",
        // 非自动确认
        false,
        // 消费者的标签
        "ack.consumer",
        // 回调函数
        consumer);
  }
}

发送测试

首先执行生产者向队列中发送数据。然后执行消费者,检查拒收的处理。

在消费者的控制台,将持续不断的输出消息信息:

确认的消息内容:hello-0
确认的消息内容:hello-1
确认的消息内容:hello-2
确认的消息内容:hello-3
确认的消息内容:hello-4
确认的消息内容:hello-0
确认的消息内容:hello-1
......
确认的消息内容:hello-0

按照发送的顺序将不断的被打印。

那此时消息是什么状态呢?查看下消息队列中的信息

[root@nullnull-os rabbitmq]# rabbitmqctl list_queues name,messages_ready,messages_unacknowledged,messages,consumers  --formatter pretty_table
Timeout: 60.0 seconds ...
Listing queues for vhost / ...
┌───────────────┬────────────────┬─────────────────────────┬──────────┬───────────┐
│ name          │ messages_ready │ messages_unacknowledged │ messages │ consumers │
├───────────────┼────────────────┼─────────────────────────┼──────────┼───────────┤
│ ack.qu        │ 0551         │
├───────────────┼────────────────┼─────────────────────────┼──────────┼───────────┤
│ persistent.qu │ 1010         │
└───────────────┴────────────────┴─────────────────────────┴──────────┴───────────┘
[root@nullnull-os rabbitmq]# 

可以看到当前的消息处于unack的状态。由于消息被不断的重新放回队列,而消费者又只有当前这一个,所以,在不断拒收中被放回。

那如果将消息拒绝改为不重新放回队列,会如何呢?来验证下。

首先修改消费者的代码:

import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.Envelope;
import java.io.IOException;

public class Consumer {

  public static void main(String[] args) throws Exception {
    ConnectionFactory factory = new ConnectionFactory();

    factory.setUri("amqp://root:123456@node1:5672/%2f");

    Connection connection = factory.newConnection();
    Channel channel = connection.createChannel();

    channel.queueDeclare("ack.qu", false, false, false, null);

    DefaultConsumer consumer =
        new DefaultConsumer(channel) {
          @Override
          public void handleDelivery(
              // 消费者标签
              String consumerTag,
              // 消费者的封装
              Envelope envelope,
              // 消息属性
              AMQP.BasicProperties properties,
              // 消息体
              byte[] body)
              throws IOException {

            System.out.println("确认的消息内容:" + new String(body));


            // 找收消息
            // Nack与Reject的区别在于,nack可以对多条消息进行拒收,而reject只能拒收一条。
            // requeue为false表示不确认的消息不会重新放回队列。
            //channel.basicReject(envelope.getDeliveryTag(), true);
            channel.basicReject(envelope.getDeliveryTag(), false);
          }
        };

    channel.basicConsume(
        "ack.qu",
        // 非自动确认
        false,
        // 消费者的标签
        "ack.consumer",
        // 回调函数
        consumer);
  }
}

再次执行消费者。

确认的消息内容:hello-0
确认的消息内容:hello-1
确认的消息内容:hello-2
确认的消息内容:hello-3
确认的消息内容:hello-4

而这一次消息没有再循环打印。只输出一遍,再检查下消息在队列中的状态:

[root@nullnull-os rabbitmq]# rabbitmqctl list_queues name,messages_ready,messages_unacknowledged,messages,consumers  --formatter pretty_table
Timeout: 60.0 seconds ...
Listing queues for vhost / ...
┌───────────────┬────────────────┬─────────────────────────┬──────────┬───────────┐
│ name          │ messages_ready │ messages_unacknowledged │ messages │ consumers │
├───────────────┼────────────────┼─────────────────────────┼──────────┼───────────┤
│ ack.qu        │ 0001         │
├───────────────┼────────────────┼─────────────────────────┼──────────┼───────────┤
│ persistent.qu │ 1010         │
└───────────────┴────────────────┴─────────────────────────┴──────────┴───────────┘
[root@nullnull-os rabbitmq]# 

通过观察发现,消息已经没有在队列中了,那就是消息已经被丢弃了。

7.6.2 手动ACK机制-ack

消费者修改为ACK确认处理

import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.Envelope;

import java.io.IOException;

public class Consumer {

  public static void main(String[] args) throws Exception {
    ConnectionFactory factory = new ConnectionFactory();

    factory.setUri("amqp://root:123456@node1:5672/%2f");

    Connection connection = factory.newConnection();
    Channel channel = connection.createChannel();

    channel.queueDeclare("ack.qu", false, false, false, null);

    DefaultConsumer consumer =
        new DefaultConsumer(channel) {
          @Override
          public void handleDelivery(
              // 消费者标签
              String consumerTag,
              // 消费者的封装
              Envelope envelope,
              // 消息属性
              AMQP.BasicProperties properties,
              // 消息体
              byte[] body)
              throws IOException {

            System.out.println("确认的消息内容:" + new String(body));

            // 消息确认,并且非批量确认,multiple为false,表示只确认了单条
            channel.basicAck(envelope.getDeliveryTag(), false);
          }
        };

    channel.basicConsume(
        "ack.qu",
        // 非自动确认
        false,
        // 消费者的标签
        "ack.consumer",
        // 回调函数
        consumer);
  }
}

此时可以先运行消息者。等待消息推送。然后运行生产者将消息推送,此时便可以看到消费者的控制台输出:

确认的消息内容:hello-0
确认的消息内容:hello-1
确认的消息内容:hello-2
确认的消息内容:hello-3
确认的消息内容:hello-4

观察队列中的信息

[root@nullnull-os rabbitmq]# rabbitmqctl list_queues name,messages_ready,messages_unacknowledged,messages,consumers  --formatter pretty_table
Timeout: 60.0 seconds ...
Listing queues for vhost / ...
┌───────────────┬────────────────┬─────────────────────────┬──────────┬───────────┐
│ name          │ messages_ready │ messages_unacknowledged │ messages │ consumers │
├───────────────┼────────────────┼─────────────────────────┼──────────┼───────────┤
│ ack.qu        │ 0001         │
├───────────────┼────────────────┼─────────────────────────┼──────────┼───────────┤
│ persistent.qu │ 1010         │
└───────────────┴────────────────┴─────────────────────────┴──────────┴───────────┘
[root@nullnull-os rabbitmq]# 

在队列中,消息已经被成功的消费了。

7.6.3 手动ACK机制-nack
import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.Envelope;
import java.io.IOException;

public class Consumer {
  public static void main(String[] args) throws Exception {
    ConnectionFactory factory = new ConnectionFactory();

    factory.setUri("amqp://root:123456@node1:5672/%2f");

    Connection connection = factory.newConnection();
    Channel channel = connection.createChannel();

    channel.queueDeclare("ack.qu", false, false, false, null);

    DefaultConsumer consumer =
        new DefaultConsumer(channel) {
          @Override
          public void handleDelivery(
              // 消费者标签
              String consumerTag,
              // 消费者的封装
              Envelope envelope,
              // 消息属性
              AMQP.BasicProperties properties,
              // 消息体
              byte[] body)
              throws IOException {

            System.out.println("确认的消息内容:" + new String(body));
            // 消息批量不确认,即批量丢弃,每5个做一次批量消费
            // 参数1,消息的标签
            // multiple为false 表示不确认当前是一个消息。true就是多个消息。
            // requeue为true表示不确认的消息会重新放回队列。
            // 每5条做一次批量确认,_deliveryTag从1开始
            if (envelope.getDeliveryTag() % 5 == 0) {
                System.out.println("批量确认执行");
                channel.basicNack(envelope.getDeliveryTag(), true, false);
            }
          }
        };

    channel.basicConsume(
        "ack.qu",
        // 非自动确认
        false,
        // 消费者的标签
        "ack.consumer",
        // 回调函数
        consumer);
  }
}

执行消费者程序,然后再执行生产者。查看消费端的控制台:

确认的消息内容:hello-0
确认的消息内容:hello-1
确认的消息内容:hello-2
确认的消息内容:hello-3
确认的消息内容:hello-4
批量确认执行

由于此处采用的是不重新放回队列,所以,数据接收到之后被丢弃了。

[root@nullnull-os rabbitmq]# rabbitmqctl list_queues name,messages_ready,messages_unacknowledged,messages,consumers  --formatter pretty_table
Timeout: 60.0 seconds ...
Listing queues for vhost / ...
┌───────────────┬────────────────┬─────────────────────────┬──────────┬───────────┐
│ name          │ messages_ready │ messages_unacknowledged │ messages │ consumers │
├───────────────┼────────────────┼─────────────────────────┼──────────┼───────────┤
│ ack.qu        │ 0000         │
├───────────────┼────────────────┼─────────────────────────┼──────────┼───────────┤
│ persistent.qu │ 1010         │
└───────────────┴────────────────┴─────────────────────────┴──────────┴───────────┘

队列中的数据也已经被处理掉了。

7.6.4 手动ACK机制-SpringBoot

首先是Maven导入

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
            <version>2.2.8.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <version>2.2.8.RELEASE</version>
        </dependency>

配制文件application.yml

spring:
  application:
    name: consumer-ack
  rabbitmq:
    host: node1
    port: 5672
    virtual-host: /
    username: root
    password: 123456

    # 配制消费端ack信息。
    listener:
      simple:
        acknowledge-mode: manual
        # 重试超过最大次数后是否拒绝
        default-requeue-rejected: false
        retry:
          # 开启消费者重度(false时关闭消费者重试,false不是不重试,而是一直收到消息直到ack确认或者一直到超时)
          enable: true
          # 最大重度次数
          max-attempts: 5
          # 重试间隔时间(单位毫秒)
          initial-interval: 1000

主启动类

import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;

import java.nio.charset.StandardCharsets;


@SpringBootApplication
public class Main {

  @Autowired private RabbitTemplate rabbitTemplate;

  public static void main(String[] args) {
    SpringApplication.run(Main.class, args);
  }

  /**
   * 在启动后就开始向MQ中发送消息
   *
   * @return
   */
  @Bean
  public ApplicationRunner runner() {
    return args -> {
      Thread.sleep(5000);
      for (int i = 0; i < 10; i++) {
        MessageProperties props = new MessageProperties();
        props.setDeliveryTag(i);
        Message message = new Message(("消息:" + i).getBytes(StandardCharsets.UTF_8), props);
        rabbitTemplate.convertAndSend("ack.ex", "ack.rk", message);
      }
    };
  }
}

当主类启动后,会延迟5秒,向MQ中发送10条记录。

队列配制

import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.DirectExchange;
import org.springframework.amqp.core.Exchange;
import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class RabbitConfig {

  @Bean
  public Queue queue() {
    return new Queue("ack.qu", false, false, false, null);
  }

  @Bean
  public Exchange exchange()
  {
    return new DirectExchange("ack.ex",false,false,null);
  }

  @Bean
  public Binding binding()
  {
    return BindingBuilder.bind(queue()).to(exchange()).with("ack.rk").noargs();
  }
}

使用推送模式来查确认消息

监听器,MQ队列推送消息至listener

import com.rabbitmq.client.Channel;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.support.AmqpHeaders;
import org.springframework.messaging.handler.annotation.Header;
import org.springframework.messaging.handler.annotation.Payload;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.util.concurrent.ThreadLocalRandom;

@Component
public class MessageListener {

  /**
   * NONE模式,则只要收到消息后就立即确认(消息出列,标识已消费),有丢数据风险
   *
   * <p>AUTO模式,看情况确认,如果此时消费者抛出异常则消息会返回队列中
   *
   * <p>WANUAL模式,需要显示的调用当前channel的basicAck方法
   *
   * @param channel
   * @param deliveryTag
   * @param msg
   */
  // @RabbitListener(queues = "ack.qu", ackMode = "AUTO")
  // @RabbitListener(queues = "ack.qu", ackMode = "NONE")
  @RabbitListener(queues = "ack.qu", ackMode = "MANUAL")
  public void handMessageTopic(
      Channel channel, @Header(AmqpHeaders.DELIVERY_TAG) long deliveryTag, @Payload String msg) {

    System.out.println("消息内容:" + msg);

    ThreadLocalRandom current = ThreadLocalRandom.current();

    try {
      if (current.nextInt(10) % 3 != 0) {
        // 手动nack,告诉broker消费者处理失败,最后一个参数表示是否需要将消息重新入列
        // channel.basicNack(deliveryTag, false, true);
        // 手动拒绝消息,第二个参数表示是否重新入列
        channel.basicReject(deliveryTag, true);
      } else {
        // 手动ACK,deliveryTag表示消息的唯一标志,multiple表示是否批量确认
        channel.basicAck(deliveryTag, false);
        System.out.println("已经确认的消息" + msg);
      }
      Thread.sleep(ThreadLocalRandom.current().nextInt(500, 3000));
    } catch (IOException e) {
      e.printStackTrace();
    } catch (InterruptedException e) {
    }
  }
}

消息有33%的概率被拒绝,这样又会被重新放回队列,等待下次推送。

启动测试

运行main方法

【确认】消息内容:消息:0
【拒绝】消息内容:消息:1
【拒绝】消息内容:消息:2
【拒绝】消息内容:消息:3
【确认】消息内容:消息:4
【确认】消息内容:消息:5
【拒绝】消息内容:消息:6
【拒绝】消息内容:消息:7
【拒绝】消息内容:消息:8
【拒绝】消息内容:消息:9
【确认】消息内容:消息:1
【拒绝】消息内容:消息:2
【拒绝】消息内容:消息:3
【拒绝】消息内容:消息:6
【确认】消息内容:消息:7
【确认】消息内容:消息:8
【拒绝】消息内容:消息:9
【拒绝】消息内容:消息:2
【拒绝】消息内容:消息:3
【拒绝】消息内容:消息:6
【确认】消息内容:消息:9
【确认】消息内容:消息:2
【拒绝】消息内容:消息:3
【拒绝】消息内容:消息:6
【确认】消息内容:消息:3
【拒绝】消息内容:消息:6
【确认】消息内容:消息:6

从观察到的结果也印证了,反复的被推送,接收的一个过程中,使用命令查看队列的一个消费的情况

[root@nullnull-os rabbitmq]#  rabbitmqctl list_queues name,messages_ready,messages_unacknowledged,messages,consumers  --formatter pretty_table
Timeout: 60.0 seconds ...
Listing queues for vhost / ...
┌───────────────┬────────────────┬─────────────────────────┬──────────┬───────────┐
│ name          │ messages_ready │ messages_unacknowledged │ messages │ consumers │
├───────────────┼────────────────┼─────────────────────────┼──────────┼───────────┤
│ ack.qu        │ 0661         │
├───────────────┼────────────────┼─────────────────────────┼──────────┼───────────┤
│ persistent.qu │ 1010         │
└───────────────┴────────────────┴─────────────────────────┴──────────┴───────────┘
[root@nullnull-os rabbitmq]#  rabbitmqctl list_queues name,messages_ready,messages_unacknowledged,messages,consumers  --formatter pretty_table
Timeout: 60.0 seconds ...
Listing queues for vhost / ...
┌───────────────┬────────────────┬─────────────────────────┬──────────┬───────────┐
│ name          │ messages_ready │ messages_unacknowledged │ messages │ consumers │
├───────────────┼────────────────┼─────────────────────────┼──────────┼───────────┤
│ ack.qu        │ 0111         │
├───────────────┼────────────────┼─────────────────────────┼──────────┼───────────┤
│ persistent.qu │ 1010         │
└───────────────┴────────────────┴─────────────────────────┴──────────┴───────────┘
[root@nullnull-os rabbitmq]#  rabbitmqctl list_queues name,messages_ready,messages_unacknowledged,messages,consumers  --formatter pretty_table
Timeout: 60.0 seconds ...
Listing queues for vhost / ...
┌───────────────┬────────────────┬─────────────────────────┬──────────┬───────────┐
│ name          │ messages_ready │ messages_unacknowledged │ messages │ consumers │
├───────────────┼────────────────┼─────────────────────────┼──────────┼───────────┤
│ ack.qu        │ 0001         │
├───────────────┼────────────────┼─────────────────────────┼──────────┼───────────┤
│ persistent.qu │ 1010         │
└───────────────┴────────────────┴─────────────────────────┴──────────┴───────────┘

使用拉确认消息

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.GetResponse;
import org.springframework.amqp.rabbit.core.ChannelCallback;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.nio.charset.StandardCharsets;
import java.util.concurrent.ThreadLocalRandom;

@RestController
public class MsgController {

  @Autowired private RabbitTemplate rabbitTemplate;

  @RequestMapping("/msg")
  public String getMessage() {
    String message =
        rabbitTemplate.execute(
            new ChannelCallback<String>() {
              @Override
              public String doInRabbit(Channel channel) throws Exception {

                GetResponse getResponse = channel.basicGet("ack.qu", false);

                if (null == getResponse) {
                  return "你已经消费完所有的消息";
                }

                String message = new String(getResponse.getBody(), StandardCharsets.UTF_8);

                if (ThreadLocalRandom.current().nextInt(10) % 3 == 0) {
                  // 执行消息确认操作
                  channel.basicAck(getResponse.getEnvelope().getDeliveryTag(), false);

                  return "已确认的消息:" + message;
                } else {
                  // 拒收一条消息并重新放回队列
                  channel.basicReject(getResponse.getEnvelope().getDeliveryTag(), true);
                  return "拒绝的消息:" + message;
                }
              }
            });

    return message;
  }
}

在浏览器中访问,同样有66%的概率会被拒绝,仅33%会被确认。

注:如果与监听在同一个工程,需将监听器给注释。

启动main函数,在浏览器中访问。http://127.0.0.1:8080/msg

可以看到返回:

拒绝的消息:消息:0
已确认的消息:消息:1
拒绝的消息:消息:2
......
已确认的消息:消息:9
你已经消费完所有的消息

同样的观察队列的一个消费情况:

[root@nullnull-os rabbitmq]#  rabbitmqctl list_queues name,messages_ready,messages_unacknowledged,messages,consumers  --formatter pretty_table
Timeout: 60.0 seconds ...
Listing queues for vhost / ...
┌───────────────┬────────────────┬─────────────────────────┬──────────┬───────────┐
│ name          │ messages_ready │ messages_unacknowledged │ messages │ consumers │
├───────────────┼────────────────┼─────────────────────────┼──────────┼───────────┤
│ ack.qu        │ 8080         │
├───────────────┼────────────────┼─────────────────────────┼──────────┼───────────┤
│ persistent.qu │ 1010         │
└───────────────┴────────────────┴─────────────────────────┴──────────┴───────────┘
[root@nullnull-os rabbitmq]#  rabbitmqctl list_queues name,messages_ready,messages_unacknowledged,messages,consumers  --formatter pretty_table
Timeout: 60.0 seconds ...
Listing queues for vhost / ...
┌───────────────┬────────────────┬─────────────────────────┬──────────┬───────────┐
│ name          │ messages_ready │ messages_unacknowledged │ messages │ consumers │
├───────────────┼────────────────┼─────────────────────────┼──────────┼───────────┤
│ ack.qu        │ 3030         │
├───────────────┼────────────────┼─────────────────────────┼──────────┼───────────┤
│ persistent.qu │ 1010         │
└───────────────┴────────────────┴─────────────────────────┴──────────┴───────────┘
[root@nullnull-os rabbitmq]#  rabbitmqctl list_queues name,messages_ready,messages_unacknowledged,messages,consumers  --formatter pretty_table
Timeout: 60.0 seconds ...
Listing queues for vhost / ...
┌───────────────┬────────────────┬─────────────────────────┬──────────┬───────────┐
│ name          │ messages_ready │ messages_unacknowledged │ messages │ consumers │
├───────────────┼────────────────┼─────────────────────────┼──────────┼───────────┤
│ ack.qu        │ 0000         │
├───────────────┼────────────────┼─────────────────────────┼──────────┼───────────┤
│ persistent.qu │ 1010         │
└───────────────┴────────────────┴─────────────────────────┴──────────┴───────────┘
[root@nullnull-os rabbitmq]#

使用拉模式进行消息ACK确认也已经完成。

RabbitMQ通过以下几种机制保证消费消息可靠性: 1. 确认机制Ack机制):消费在成功消费一条消息后,会向Broker发送Ack确认报文,告知自己已完成消费。如果Broker没有收到确认报文,那么消息不会被标记为已消费,从而避免了消息的丢失。然而,如果消费者在发送确认报文时由于网络断开等原因无法成功发送,会导致消息重复消费的问题。 2. 发布者确认(Publisher Confirm):生产者在发送消息后可以等待RabbitMQ返回确认通知。如果生产者接收到确认通知,就可以判断消息已被成功发送到Broker。如果网络断开或其他异常情况导致确认通知无法及时返回,生产者可以选择重新发送消息,以确保消息可靠性。然而,这可能会导致RabbitMQ中存在两条相同的消息,从而可能导致重复消费的问题。 3. QoS机制(Quality of Service):RabbitMQ提供了QoS保证机制,可以限制消费在一个Channel上未被Ack消息数量。如果超过了这个数量限制,RabbitMQ将不再向消费推送消息,以避免消费因大量消息瞬时到达而产生巨大压力。这是一种流控手段,能够保护消费可靠性。但需要注意的是,QoS机制仅对消费的推模式有效,对拉模式无效;同时,QoS机制不支持NONE Ack模式。 综上所述,RabbitMQ通过Ack机制、发布者确认和QoS机制保证消费消息可靠性。然而,由于网络断开和其他异常情况的存在,无法完全避免消息的重复消费
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值