RabbitMQ高级特性

518 阅读4分钟

1.发送端确认机制

- 为什么需要发送端确认机制?

消息发送后,发送端不知道RabbitMQ是否真的收到了消息

- 什么是发送端确认机制?

消息发送后,若RabbitMQ收到消息,则会给发送端一个应答,
发送端接收应答,确认消息发送成功

- 三种确认机制

  • 1.单条同步确认

    • 1.1 配置channel,开启确认模式

      channel.confirmSelect();

    • 1.2 每发送一条消息后,调用方法waitForConfirms()方法,等待确认,返回true则代表RabbitMQ收到了消息

      channel.waitForConfirms();

  • 2.多条同步确认

    • 2.1 配置channel,开启确认模式

      channel.confirmSelect();

    • 2.2 发送多条消息后,调用waitForConfirms()方法,等待确认,返回true则代表RabbitMQ收到了发送的全部消息,但返回false不代表全部失败,有可能是部分消息发送失败,无法知道哪些消息发送失败了!

  • 3.异步确认

    • 3.1 配置channel,开启确认模式

      channel.confirmSelect();

    • 3.2 在channel上添加监听 addConfirmListener,发送消息后,则会回调该方法,通知是否发送成功,但RabbitMQ异步确认有可能是单条,有可能是多条

异步确认代码

ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("192.168.10.233");
try (Connection connection = connectionFactory.newConnection();
    Channel channel = connection.createChannel()) {
    channel.confirmSelect();
    ConfirmListener confirmListener = new ConfirmListener() {
        @Override
        public void handleAck(long deliveryTag, boolean multiple) throws IOException {
            // 确认成功
            // deliveryTag:在确认前多少条消息
            // multiple:true代表确认多条消息,false代表确认单条消息
        }

        @Override
        public void handleNack(long deliveryTag, boolean multiple) throws IOException {
            // 确认失败
        }
    };
    channel.addConfirmListener(confirmListener);
    String msgToSend = "消息内容";
    // 发消息
    channel.basicPublish("exchange.test.msg",
                "key.msg",
                null, msgToSend.getBytes(StandardCharsets.UTF_8));
}

:: 推荐使用单条同步确认 ::


2.消息返回机制

- 为什么需要消息返回机制?

发送端消息发出后,不知道该消息是否被正确路由

- 什么是消息返回机制

发送端消息发出后,RabbitMQ会对消息进行路由,
如果没有发现目标队列,则会回调 ReturnListener 通知发送方 

- 开启消息返回机制

设置 Mandatory 为true,
若设置为false,RabbitMQ则会直接丢弃无法路由的消息

basicPublish 有三个不同参数的方法:

public void basicPublish(String exchange, String routingKey, BasicProperties props, byte[] body) throws IOException {
    this.delegate.basicPublish(exchange, routingKey, props, body);
}

public void basicPublish(String exchange, String routingKey, boolean mandatory, BasicProperties props, byte[] body) throws IOException {
    this.delegate.basicPublish(exchange, routingKey, mandatory, props, body);
}

public void basicPublish(String exchange, String routingKey, boolean mandatory, boolean immediate, BasicProperties props, byte[] body) throws IOException {
    this.delegate.basicPublish(exchange, routingKey, mandatory, immediate, props, body);
}

我们调用有 boolean mandatory 这个参数的即可,传参为true,大致代码如下:

第一种,new ReturnListener()

channel.addReturnListener(new ReturnListener() {
    @Override
    public void handleReturn(int replayCode, String replayText, String exchange, String routingKey, AMQP.BasicProperties basicProperties, byte[] body) throws IOException {
        // 消息无法被路由
    }
});

String msgToSend = objectMapper.writeValueAsString(orderMessage);
// 发消息
channel.basicPublish("exchange.order.restaurant",
        "key.restaurant", true,
        null, msgToSend.getBytes(StandardCharsets.UTF_8));

第二种,new ReturnCallback()

channel.addReturnListener(new ReturnCallback() {
    @Override
    public void handle(Return returnMessage) {

    }
});

两种方法没有区别,只是 new ReturnCallback() 将返回的数据封装在了 Return 类里,返回的数据都是一样的。

返回的数据有 replayCode、replayText、exchange、routingKey、basicProperties、body


3.消费端确认机制

默认情况下,消费端接收到消息后,会自动确认(ACK)

- 消费端ACK类型

    1. 自动ACK:消费端收到消息后,会自动签收消息
    1. 手动ACK:不会自动签收消息,需要代码中显式进行签收

- 手动ACK类型

    1. 单条手动ACK:multiple=false
    1. 多条手动ACK:multiple=true

推荐单条手动ACK

DeliverCallback deliverCallback = (consumerTag, message) -> {
    String messageBody = new String(message.getBody());
    channel.basicAck(message.getEnvelope().getDeliveryTag(),false);
};

4.消费端限流机制

- 为什么需要限流

业务高峰时期,消息过多,接收端处理不了,导致服务崩溃
开启限流机制,限制消息推送速度,保证服务稳定

- QoS(服务质量保证)

QoS功能保证了在一定数目的消息未被确认前,不消费新的消息

QoS前提:不使用自动确认

具体参数设置

prefetchCount:针对一个消费端最多推送多少未确认消息
globaltrue:针对整个消费端限流;false:针对当前channel
prefetchSize:单个消息大小限制,一般为0

RabbitMQ暂未实现global和prefetchSize

// 开启限流
channel.basicQos(10);
channel.basicConsume("queue.msg", false, deliverCallback, consumerTag -> {});

5.消息过期机制

默认情况下,消息进入队列,会一直存在,直到被消费

RabbitMQ的过期时间称为TTL(Time to Live)

分为消息TTL(单条消息过期时间) 和 队列TTL(队列中所有消息的过期时间)

不推荐单独使用TTL,因为这会导致直接删除消息

如何设置合适的TTL

TTL应该要长于服务平均重启的时间,并且长于业务高峰期时间

使用方法

  • 设置单条消息TTL
// expiration 单位为毫秒
AMQP.BasicProperties properties = new AMQP.BasicProperties().builder().expiration("15000").build();
channel.basicPublish("exchange.test.msg", "key.msg", properties, messageToSend.getBytes());
  • 设置队列TTL
Map<String, Object> args = new HashMap<>(16);
args.put("x-message-ttl", 15000);
channel.queueDeclare("queue.test",
        true, false, false, args);

如果当前队列已经存在,则需要先删除重新进行创建才能设置过期时间,不然执行会报错!

6.死信队列

设置了过期时间的消息,过期后会被直接丢弃,无法对系统异常发出警报。

死信队列的作用就是收集过期的消息

什么是死信队列

队列设置了DLX属性(Dead-Letter-Exchange)

生产消息 -> Exchange -> Queue -> DL Exchange -> DL Queue -> 异常监听处理

什么情况下会变成死信

  • 消息被拒绝并且requeue=false(不重回队列)
  • 消息过期
  • 队列达到最大长度

设置方法

  • 1.设置转发、接收死信的交换机和队列
    • Exchange:dlx.exchange
    • Queue:dlx.queue
    • RoutingKey:#
  • 2.给死信队列设置参数
    • x-dead-letter-exchange=dlx.exchange
// 创建接收死信的交换机
channel.exchangeDeclare("exchange.dlx", BuiltinExchangeType.TOPIC,
        true, false, null);
// 创建接收死信的队列
channel.queueDeclare("queue.dlx", true, false, false, null);
// 进行绑定
channel.queueBind("queue.dlx", "exchange.dlx", "*");

// 设置死信队列
Map<String, Object> args = new HashMap<>(16);
args.put("x-dead-letter-exchange", "exchange.dlx");
channel.queueDeclare("queue.restaurant", true, false, false, args);