一. TTL(Time To Live)
消息的过期时间有两种设置方式:
1. 通过队列属性设置消息过期时间
@Bean("ttlQueue")
public Queue queue() {
Map<String, Object> map = new HashMap<String, Object>();
map.put("x-message-ttl", 10000); // 队列中的消息未被消费 10 秒后过期
return new Queue("TTL_QUEUE", true, false, false, map);
}
2. 设置单条消息的过期时间
MessageProperties messageProperties = new MessageProperties();
messageProperties.setExpiration("4000"); // 消息的过期属性,单位 ms
Message message = new Message("这条消息 4 秒后过期".getBytes(), messageProperties);
rabbitTemplate.send("TTL_EXCHANGE", "test.ttl", message);
如果同时指定了Message TTL 和Queue TTL,则优先较小的那一个。
二. 死信队列
消息在某些情况下会变成死信(Dead Letter)。队列在创建的时候可以指定一个死信交换机DLX(Dead Letter Exchange)。死信交换机绑定的队列被称为死信队列DLQ(Dead Letter Queue),DLX实际上也是普通的交换机,DLQ也是普通的队列。
什么情况下消息会变成死信?
1.消息被消费者拒绝并且未设置重回队列
2.消息过期
3.队列达到最大长度,超过了 Max length(消息数)或者 Max length bytes(字节数),最先入队的消息会被发送到 DLX。
实现过程:
1.声明死信交换机 DEAD_LETTER_EXCHANGE 、死信队列 DEAD_LETTER_QUEUE,相互绑定。
@Bean("deadLetterExchange")
public TopicExchange deadLetterExchange() {
return new TopicExchange("GP_DEAD_LETTER_EXCHANGE", true, false, new HashMap<>());
}
@Bean("deadLetterQueue")
public Queue deadLetterQueue() {
return new Queue("DEAD_LETTER_QUEUE", true, false, false, new HashMap<>());
}
@Bean
public Binding bindingDead(@Qualifier("deadLetterQueue") Queue queue, @Qualifier("deadLetterExchange") TopicExchange exchange) {
return BindingBuilder.bind(queue).to(exchange).with("#"); // 无条件路由
}
2.声明交换机 SIMPLE_EXCHANGE、队列 SIMPLE_QUEUE,相互绑定。
3.指定队列的死信交换机 DEAD_LETTER_EXCHANGE。
4.队列中的消息10秒钟过期,因为没有消费者,会变成死信,通过死信交换机进入死信队列。
@Bean("simpleExchange")
public DirectExchange exchange() {
return new DirectExchange("SIMPLE_EXCHANGE", true, false, new HashMap<>());
}
@Bean("simpleQueue")
public Queue queue() {
Map<String, Object> map = new HashMap<>();
// 10 秒钟后成为死信
map.put("x-message-ttl", 10000);
// 队列中的消息变成死信后,进入死信交换机
map.put("x-dead-letter-exchange", "DEAD_LETTER_EXCHANGE");
return new Queue("SIMPLE_QUEUE", true, false, false, map);
}
@Bean
public Binding binding(@Qualifier("simpleQueue") Queue queue,@Qualifier("simpleExchange") DirectExchange exchange) {
return BindingBuilder.bind(queue).to(exchange).with("hujy.test");
}
三.延迟队列
RabbitMQ 本身不支持延迟队列,总的来说有三种实现方案:
1.先存储到数据库,用定时任务扫描。
2.利用 RabbitMQ的死信队列(Dead Letter Queue)实现。
主要过程:
生产者 —> 原交换机 —> 原队列(超过 TTL 之后) —> 死信交换机 —> 死信队列 —> 最终消费者
使用死信队列实现延时消息的缺点:
(1)如果统一用队列来设置消息的 TTL,当梯度非常多的情况下,比如 1 分钟,2 分钟,5 分钟,10 分钟,20 分钟,30 分钟......需要创建很多交换机和队列来路由消息。
(2)如果单独设置消息的 TTL,则可能会造成队列中的消息阻塞,即前一条消息没有出队(没有被消费),后面的消息无法投递。比如第一条消息过期 TTL 是 30min,第二条消息 TTL 是 10min。10 分钟后,即使第二条消息应该投递了,但是由于第一条消息 还未出队,所以无法投递。
(3)可能存在一定时间误差
3.利用 rabbitmq-delayed-message-exchange 插件实现。