一. 默认情况
- 在默认情况下,消息是不会过期的。
- 即如果不设置任何消息过期的相关参数,那么消息会一直存在队列中,直到被消费者消费消息
二. TTL (Time-To-Live)
I. TTL 的简介
- TTL --- 消息存活的时间,即消息的有效期。
- 如果消息的存活时间超过了 TTL 中设置的时间还没有被消费,那么此时的消息就会变成
死信
,然后存入到死信队列
中。 - 至于
死信
,死信队列
是啥,后面给大家介绍 - TTL 的设置有两种不同的方式
(1) 全局 ------ 在声明队列的时候,我们可以在队列属性中设置消息的有效期,这样所有进入该队列的消息都会有一个相同的有效期。
(2) 局部 ------ 在发送消息的时候设置消息的有效期,这样就是不同的消息具有不一样的有效期
- 如果两个都设置了 ------ 以时间短的为准
- 当我们设置消息有效期之后,消息过期了就会从队列中删除了(进入死信队列),但是两种方式对应的
删除时机有一些差异
(1) 全局 ------ 消息进入 RabbitMQ 是存在一个消息队列中的,队列的头部是最早要过期的消息,所以 RabbitMQ 只需要一个定时任务,从头部开始扫描是否有过期消息,有的话就直接删除。 (2) 局部 ------ 这种方式当消息过期时并不会立马被删除,而是当消息要投递给消费者的时候才会去删除。因为这种方式,每条消息的过期时间都不一样,想要知道哪条消息过期,必须要遍历队列中的所有消息才能实现,当消息比较多时这样就比较耗费性能了。因此对于这种方式,当消息需要投递给消费者时才去删除。
II. 单条消息过期
-
依赖及配置
依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-amqp</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>
application.properties 中的配置
# RabbitMQ 的端口号 spring.rabbitmq.port=5672 # 虚拟机的地址 spring.rabbitmq.host=192.168.31.129 # 虚拟主机 spring.rabbitmq.virtual-host=/ spring.rabbitmq.username=guest spring.rabbitmq.password=guest
-
配置一个消息队列
@Configuration public class DirectConfig { public static final String MY_DIRECT_EXCHANGE_NAME = "my_direct_exchange_name"; public static final String MY_DIRECT_QUEUE_NAME_01 = "my_direct_queue_name_01"; @Bean Queue directQueue01() { /** * 参数一:队列的名字 * 参数二:队列是否持久化。即当 rabbitMQ 关机重启后是否还存在 * 参数三:排他性。即哪个连接创建该队列,哪个连接才能操作该队列,一般都是 false * 参数四:是否自动删除。即如果没有人监听该队列,是否自动删除该队列 */ return new Queue(MY_DIRECT_QUEUE_NAME_01, true, false, false); } @Bean DirectExchange directExchange() { /** * 参数一:交换机的名字 * 参数二:是否具备持久性 * 参数三:是否自动删除 */ return new DirectExchange(MY_DIRECT_EXCHANGE_NAME, true, false); } @Bean Binding directBinding01() { return BindingBuilder //指定要绑定的队列 .bind(directQueue01()) //指定交换机 .to(directExchange()) //就用队列名作为 rotingKey .with(MY_DIRECT_QUEUE_NAME_01); } }
这段代码其实也没有什么好说的,在前面的
RabbitMQ
的使用中也详细介绍过了 -
然后就可以创建一个消息发送接口了,我这里还是使用单元测试来演示
@Autowired RabbitTemplate rabbitTemplate; @Test public void test01(){ Message msg = MessageBuilder .withBody("Hello Peng!".getBytes()) .setExpiration("10000") .build(); rabbitTemplate.send(DirectConfig.MY_DIRECT_EXCHANGE_NAME,DirectConfig.MY_DIRECT_QUEUE_NAME_01,msg); }
setExpiration
在这个方法中设置消息的过期时间,单位是毫秒。我这里是消息10秒后删除。 -
运行之后,在 RabbitMQ 的管理页面中查看消息的变化结果
III. 队列消息过期
-
队列的消息过期设置与以往有一些不样,在配置类消息队列的时候,加上一个参数,如下
@Bean Queue directQueue01() { Map<String, Object> args = new HashMap<>(); //设置队列的过期时间 args.put("x-message-ttl", 10000); return new Queue(MY_DIRECT_QUEUE_NAME_01, true, false, false, args); }
-
接下来就是在消费者中消费消息了
@Autowired RabbitTemplate rabbitTemplate; @Test public void test01(){ Message msg = MessageBuilder .withBody("Hello Peng!".getBytes()) .build(); rabbitTemplate.send(DirectConfig.MY_DIRECT_EXCHANGE_NAME,DirectConfig.MY_DIRECT_QUEUE_NAME_01,msg); }
-
同样的,也是在 RabbitMQ 的管理页面中查看消息的变化结果
可以看到,消息队列的 Features 属性变成了 D 和 TTL,10秒之后刷新页面,发现消息数又变成了 0 ,这就是给队列设置消息过期,所有进入该队列的消息,10秒后都会过期,进入死信队列中。
(1) D 表示消息队列中的持久化
(2) TTL 则表示消息会过期
IV. 特殊情况
当 TTL 设置为 0 时,表示如果消息不能立马消费则会立即丢弃掉
三. 死信队列以及死信交换机
I. 死信交换机
- 死信交换机 --- Dead-Letter-Exchange,即 DLX
- 死信交换机用来接收死信消息,那么什么是死信消息呢?一般消息变成死信消息有下面几种情况: (1) 消息被拒绝(Basic.Reject / Basic.Nack),并且设置 requeue 参数为 false (2) 消息过期 (3) 队列达到最大长度
- 当消息在一个队列中变成死信消息后,此时就会被发送到 DLX ,病毒 DLX 的消息队列则被称为死信队列
II. 死信队列
- 绑定死信交换机的队列,就是死信队列
- 其实死信队列和死信交换机与我们上面写的队列交换机本质上是没有区别的,只是概念上的不同而已 ,即死信队列、死信交换机,实际上就是普通的队列、交换机罢了。
III. 具体操作
-
创建一个配置文件,在配置文件中对死信队列、死信交换机进行配置。
@Configuration public class DirectConfig { public static final String MY_DIRECT_EXCHANGE_NAME = "my_direct_exchange_name"; public static final String MY_DIRECT_QUEUE_NAME_01 = "my_direct_queue_name_01"; //死信交换机的名字 public static final String MY_DLX_DIRECT_EXCHANGE_NAME = "my_dlx_direct_exchange_name"; //死信队列的名字 public static final String MY_DLX_DIRECT_QUEUE_NAME_01 = "my_dlx_direct_queue_name_01"; /** * 死信队列与死信交换机的连接 * @return */ @Bean Binding dlxBinding() { return BindingBuilder .bind(dlxQueue()) .to(dlxDirectExchange()) .with(MY_DLX_DIRECT_QUEUE_NAME_01); } /** * 死信队列 * @return */ @Bean Queue dlxQueue() { return new Queue(MY_DLX_DIRECT_QUEUE_NAME_01, true, false, false); } /** * 死信交换机 * @return */ @Bean DirectExchange dlxDirectExchange() { return new DirectExchange(MY_DLX_DIRECT_EXCHANGE_NAME, true, false); } @Bean Queue directQueue01() { Map<String, Object> args = new HashMap<>(); //设置消息有效期,消息到期未被消费,就胡进入到死信交换机,并由死信交换机路由到死信队列 args.put("x-message-ttl", 10000); //指定死信交换机 args.put("x-dead-letter-exchange", MY_DLX_DIRECT_EXCHANGE_NAME); //指定死信队列 args.put("x-dead-letter-routing-key", MY_DLX_DIRECT_QUEUE_NAME_01); return new Queue(MY_DIRECT_QUEUE_NAME_01, true, false, false, args); } @Bean DirectExchange directExchange() { return new DirectExchange(MY_DIRECT_EXCHANGE_NAME, true, false); } @Bean Binding directBinding01() { return BindingBuilder //指定要绑定的队列 .bind(directQueue01()) //指定交换机 .to(directExchange()) //就用队列名作为 rotingKey .with(MY_DIRECT_QUEUE_NAME_01); } }
-
其实这里的代码,也就是增加了一个交换机的名字、一个队列的名字,以及以此对应的交换机绑定。而这些操作其实与前面的都是一样的,不同的是在这里称为死信交换机、死信队列。
-
与上面不同的是,在队列那里多了几个参数
Map<String, Object> args = new HashMap<>(); //设置消息有效期,消息到期未被消费,就胡进入到死信交换机,并由死信交换机路由到死信队列 args.put("x-message-ttl", 10000); //指定死信交换机 args.put("x-dead-letter-exchange", MY_DLX_DIRECT_EXCHANGE_NAME); //指定死信的 routingkey args.put("x-dead-letter-routing-key", MY_DLX_DIRECT_QUEUE_NAME_01);
(1) 第一个我们前面已经用过了,设置队列的过期时间的
(2) 第二个则是指定死信交换机的名字,这个和平时我们使用的
BindingBuilder.to
这个方法是类似的。(3) 第三个则是指定死信的 routingkey,这也是与
BindingBuilder.with
一样的。(4) 那么有人会问,在
Fanout交换机
和Header交换机
不是没有 routingKey 吗?其实在没有 routingKey 的情况下,这个x-dead-letter-routing-key
是可以不用设置的。(4) 注意,这里面的 Key 是固定的,不能打错。
-
然后运行,还是观察 RabbitMQ 管理页面的变化