大伙可以到我的RabbitMQ专栏获取更多信息
demo示例这里拿
概述
什么是TTL
- TTL全程:time to live(存活时间/过期时间)
- 当消息被RabbitMQ broker接收到并由exchange分配到对应的queue之后,MQ会开始根据TTL来清除消息,到达TTL时间的消息如果还没有被消费,就被清除
- 在RabbitMQ中可以对某条消息设置过期时间,也可以对某个队列设置过期时间
场景举例
常见的有:
- 订单超时未付款自动取消
- 与死信队列结合用过定时任务执行器
TTL的使用
打开RabbitMQ的控制台找到创建队列的地方:
在创建队列这里,最下面有一个参数选项,这里可以让我们设置一些队列的初始化参数,其中举例的第一条就是Message TTL,其官方解释如下:
How long a message published to a queue can live before it is discarded (milliseconds).
(Sets the "x-message-ttl" argument.)
解释的再明显不过了,发布在队列中的消息在被丢弃之前能活多久,单位是毫秒,参数key为x-message-ttl
- 在这里随便创建一个队列,设置其x-message-ttl = 10000
- 然后创建一个交换机与该队列绑定
- 发送一条消息进行测试
- 观察队列中的该消息的total和ready中的数值在10秒钟之后的变化
代码示例
队列TTL
队列TTL的相关参数设置都是在生产者创建队列时候设置的,创建队列代码如下:
创建queue的时候设置x-message-ttl参数
package com.leolee.rabbitmq.config;
import org.springframework.amqp.core.*;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
import java.util.Map;
/**
* @ClassName RabbitMQConfig
* @Description: TODO
* @Author LeoLee
* @Date 2020/11/7
* @Version V1.0
**/
@Configuration
public class RabbitMQConfig {
public static final String EXCHANGE_NAME = "boot_topic_exchange";
public static final String QUEUE_TTL_NAME = "ttl_queue";
//交换机
@Bean("bootExchange")
public Exchange bootExchange() {
return ExchangeBuilder.topicExchange(EXCHANGE_NAME).durable(true).build();
}
//设置TTL的队列
@Bean("queueWithTTL")
public Queue queueWithTTL() {
Map<String, Object> argumentsMap = new HashMap<>();
argumentsMap.put("x-message-ttl", 10000);
return QueueBuilder.durable(QUEUE_TTL_NAME).withArguments(argumentsMap).build();
}
@Bean
public Binding bingQueueTTLExchange(@Qualifier("queueWithTTL") Queue queue, @Qualifier("bootExchange") Exchange exchange) {
return BindingBuilder.bind(queue).to(exchange).with("ttl.#").noargs();
}
}
生产者发送消息到对应的队列测试TTL:
/*
* 功能描述: <br>
* 〈测试过期时间〉
* 1.队列的过期时间设置
* 2.针对某条消息的过期时间设置
* @Param: []
* @Return: void
* @Author: LeoLee
* @Date: 2020/11/8 11:58
*/
@Test
public void testTTL() {
rabbitTemplate.convertAndSend(RabbitMQConfig.EXCHANGE_NAME, "ttl.test", "test msg ttl");
}
刚发出消息后RabbitMQ的控制台上可以看到ttl_queue队列中已经存在了该消息,由于一直没有消费者来获取消息,10s中之后消息自动被丢弃,total和ready变为0
消息TTL
为了更方便观察数据变化结果,把队列的过期时间调大到50秒
编写生产者代码,循环发送10条消息,基数不设置过期时间,偶数设置过期时间为5秒
/*
* 功能描述: <br>
* 〈测试过期时间〉
* 1.队列的过期时间设置
* 2.针对某条消息的过期时间设置
* 如果设置了消息的过期时间,也设置了队列的过期时间,最终过期时间以两者中时间较短的为准
* 消息过期后并不会直接被移除掉,MQ并不是轮询所有队列中消息的过期时间,只有消息在顶端的时候才会去判断该条消息是否过期
* @Param: []
* @Return: void
* @Author: LeoLee
* @Date: 2020/11/8 11:58
*/
@Test
public void testTTL() {
for (int i = 0; i < 10; i++) {
if ((i&1) == 1) {
System.out.println("奇数:" + i);
//该条消息测试队列过期
rabbitTemplate.convertAndSend(RabbitMQConfig.EXCHANGE_NAME, "ttl.test", "test msg ttl");
} else {
System.out.println("偶数:" + i);
//该条消息测试消息过期
rabbitTemplate.convertAndSend(RabbitMQConfig.EXCHANGE_NAME, "ttl.test", "test msg ttl", message -> {
message.getMessageProperties().setExpiration("5000");
return message;
});
}
}
}
发送知道控制台打印如下:
偶数:0
奇数:1
偶数:2
奇数:3
偶数:4
奇数:5
偶数:6
奇数:7
偶数:8
奇数:9
RabbitMQ控制台对应的队列消息数量为10
之后变为9
到了50秒队列过期事件后才变为0
这是为什么呢?
为什么不是10条变为5,再变为0呢?不是说好了消息的过期时间小于队列的过期时间以消息的过期时间为准嘛?不按套路出牌啊
查阅了相关资料后,得到了一个结论:
RabbitMQ中,消息过期后并不会直接被移除掉,MQ并不是轮询所有队列中消息的过期时间,只有消息在顶端的时候才会去判断该条消息是否过期:
这就说得通了,10变成9是因为队列的顶端那条消息是偶数消息,MQ判断其过期了,就丢弃了该条消息,但是下一条顶上来的消息是奇数消息,它要等到队列过期时间到了才会被丢弃呢。实际上剩下的4条偶数消息已经过期了,只是没有被MQ清除而已。
这个机制是为了节约服务器资源保证性能,MQ如果去轮询每一个队列中的消息时,会非常非常消耗性能的,有一些类似于redis中数据的过期机制,当该条已经过期的数据被调用时,才判断它是否是过期的。