消费端限流
消费端限流是为了防止消息积压后,消费端一次拉取过多的消息而挂掉。
我们限制一个数字,其含义是:当所有的消息都被手动确认之后,消费者可以拉取的最大消息数。
核心配置:
(1)在<rabbit:listener-container>中设置 prefetch="30"
(2)消费端的确认模式一定是手动确认,即 acknowledge="manual"
<rabbit:listener-container connection-factory="connectionFactory" acknowledge="manual" prefetch="30">
<rabbit:listener ref="xxxListener" queue-names="xxx_queue"></rabbit:listener>
</rabbit:listener-container>
过期时间
当一个消息达到TTL(存活时间,也叫过期时间)后还没有被消费,那么它会被自动清除。
我们可以单独对某个消息设置过期时间,也可以对整个队列设置统一的过期时间。
❶ 消息单独过期时间
生产者发送消息时,给单个消息设置 expiration 参数,字符串类型,单位ms。
rabbitTemplate.convertAndSend("exchange_ttl", "routing_key", "我是一个TTL消息~", new MessagePostProcessor() {
public Message postProcessMessage(Message message) throws AmqpException {
// 设置message的参数
message.getMessageProperties().setExpiration("5000");
// 返回message
return message;
}
});
❷ 队列整体过期时间
配置队列时,给队列设置 x-message-ttl 参数,整型,单位ms。
<rabbit:queue name="queue_ttl" id="queue_ttl">
<!-- 设置队列的参数 -->
<rabbit:queue-arguments>
<entry key="x-message-ttl" value="10000" value-type="java.lang.Integer"></entry>
</rabbit:queue-arguments>
</rabbit:queue>
当我们给一个消息设置超时时间后,是不是过了这个时间,消息队列就 立即 将该消息清除了呢?
不是。如果想要实现超时立即删除,那么需要一直轮询检查每个消息,这开销过大。因此,消息队列对超时消息的清除,是一种 惰性删除 —— 当某个消息位于队列头部,即将被消费时,才会对其进行一次超时检查,如果超时,则清除。
死信队列
死信队列和死信交换机是一个概念,有两种叫法是因为有的MQ产品中没有交换机的概念。
看图不难理解:
- 当一个队列中的消息成为死信后,不会被清除,而是被送到另一个交换机
- 这第二个交换机就叫做死信交换机,它和普通交换机其实没什么两样
一个消息如何成为死信呢(重点)?
- 时间过期。当消息达到过期时间仍未被消费时,便成为死信
- 长度限制。一个队列满了,之后来的消息都成为死信
- 消息拒收。消费者拒绝确认某个消息,且该消息不重回队列,则其成为死信
下面,我们看着图,手写一个死信队列模型:
(1) 声明正常队列和正常交换机(queue_normal, exchange_normal)
(2) 声明死信队列和死信交换机(queue_dlx, exchange_dlx)
(3) 绑定正常队列和死信交换机(x-dead-letter-exchange, x-dead-letter-routing-key)
<!--
1.声明正常队列和正常交换机(queue_normal, exchange_normal)
2.声明死信队列和死信交换机(queue_dlx, exchange_dlx)
3.绑定正常队列和死信交换机(x-dead-letter-exchange, x-dead-letter-routing-key)
-->
<!-- 正常队列/正常交换机 -->
<rabbit:queue name="queue_normal" id="queue_normal">
<rabbit:queue-arguments>
<entry key="x-dead-letter-exchange" value="exchange_dlx"/>
<entry key="x-dead-letter-routing-key" value="routing_key"/>
<entry key="x-message-ttl" value="10000" value-type="java.lang.Integer"/>
<entry key="x-max-length" value="10" value-type="java.lang.Integer"/>
</rabbit:queue-arguments>
</rabbit:queue>
<rabbit:direct-exchange name="exchange_normal" id="exchange_normal">
<rabbit:bindings>
<rabbit:binding key="routing_key" queue="queue_normal"></rabbit:binding>
</rabbit:bindings>
</rabbit:direct-exchange>
<!-- 死信队列/死信交换机 -->
<rabbit:queue name="queue_dlx" id="queue_dlx"></rabbit:queue>
<rabbit:direct-exchange name="exchange_dlx" id="exchange_dlx">
<rabbit:bindings>
<rabbit:binding key="routing_key" queue="queue_dlx"></rabbit:binding>
</rabbit:bindings>
</rabbit:direct-exchange>
分别测试3中成为死信的情况:
生产者端 ------------------------------------------------------------------------------------
// case1:过期时间
rabbitTemplate.convertAndSend("exchange_normal", "routing_key", "我会成为死信消息吗?");
// case2:长度限制
for (int i = 0; i < 20; i++) {
rabbitTemplate.convertAndSend("exchange_normal", "routing_key", "我会成为死信消息吗?");
}
消费者端 ------------------------------------------------------------------------------------
// case3:拒接接收
// 注意设置不重回队列(第三个参数)
channel.basicNack(message.getMessageProperties().getDeliveryTag(), true, false);
延迟队列
消息进入队列后不会立即被消费,只有到达指定时间后,才会被消费。
延迟队列的使用场景其实非常多:
(1)订单在30min之内未支付则自动取消
(2)用户注册成功后,如果三天内没有登录则进行短信提醒
很可惜,在RabbitMQ中并未提供具有延迟队列功能的黑箱。但是,我们可以通过 TTL + DLX (过期时间 + 死信队列)来实现延迟队列的效果。
🐰 Rabbit~