一、过期队列
消息如果在队列中一直没有被消费且存在时间超过了ttl,消息就会变成死信,后续无法再消费。设置ttl有两种方式:
- 声明消息队列的时候,这个是全局的,所有发到这个队列的消息的过期时间是一样的
- 发送消息的时候设置属性,可以每条消息设置不同的ttl
假如你两种都设置了,以小的ttl为准
区别:queue的全局ttl,消息过期立刻就会被删掉;如果是发送消息时设置的ttl,过期之后并不会立刻删掉,这时候消息是否过期是需要投递给消费者的时候判断的
原因:queue的全局ttl,队列的有效期都一样,先入队列的队列头部,头部也是最早过期的消息,rabbitmq会有一个定时任务从队列的头部开始扫描是否有过期消息即可。而每条设置不同的ttl,只有遍历整个队列才可以筛选出来过期的消息,这样的效率实在是太低,而且如果消息量大了根本不可行,所以rabbitmq在等到消息投递给消费者的时候判断当前消息是否过期,虽然删除的不及时但是不影响功能
注意:注意,ttl队列一般需要设置监听者,因为过期之后我们会有一些通用处理逻辑比如转发到死信队列
- 代码演示
1、模拟生产者下单
@Service
public class OrderService {
@Autowired
private RabbitTemplate rabbitTemplate;
//ttl过期模式模拟下单
public void makeOrderTtl(String userId, String productId, int num) {
//1、生成订单
String orderId = UUID.randomUUID().toString();
System.out.println("过期队列ttl生成订单:" + orderId);
//2、通过MQ队列完成消息分发
String exchangeName = "ttl-direct-exchange";
String routingKey = "ttl";
rabbitTemplate.convertAndSend(exchangeName, routingKey, orderId);
}
}
2、绑定过期队列与交换机
@Configuration
public class TTLRabbitmqConfig {
//1、声明注册direct式交换机
@Bean
public DirectExchange ttldirectExchange() {
return new DirectExchange("ttl-direct-exchange", true, false);
}
//2、设置队列的过期时间
@Bean
public Queue ttlDirectQueue() {
HashMap<String, Object> ttlTime = new HashMap<>();
ttlTime.put("x-message-ttl", 5000);//时间单位为毫秒,必须为整型
return new Queue("ttl-direct-queue", true, false, false, ttlTime);
}
//3、完成交换机和队列的绑定关系
@Bean
public Binding ttlDirectBinding() {
return BindingBuilder.bind(ttlDirectQueue()).to(ttldirectExchange()).with("ttl");
}
}
3、测试代码
@SpringBootTest
class RabbitmqProducerApplicationTests {
@Autowired
private TTLOrderService ttlOrderService;
@Test
void contextLoadsTtl() {
ttlOrderService.makeOrderTtl("1", "1", 12);
}
}
运行结果:
已经发送消息,过期前:
过期后:
二、死信队列
当一个消息过期、被拒绝、达到了队列的最大长度成为死信队列的时候,就会把这些消息发送到一个队列,这个队列即为死信队列
- 代码演示
1、生产者生产一个过期的消息
@Service
public class TTLOrderService {
@Autowired
private RabbitTemplate rabbitTemplate;
//ttl过期模式模拟下单
public void makeOrderTtl(String userId, String productId, int num) {
//1、生成订单
String orderId = UUID.randomUUID().toString();
System.out.println("过期队列ttl生成订单:" + orderId);
//2、通过MQ队列完成消息分发
String exchangeName = "ttl-direct-exchange";
String routingKey = "ttl";
rabbitTemplate.convertAndSend(exchangeName, routingKey, orderId);
}
}
2、过期消息队列与交换机绑定(设置消息的过期时间和过期后将要转发到那个死信队列)
@Configuration
public class TTLRabbitmqConfig {
//1、声明注册direct式交换机
@Bean
public DirectExchange ttldirectExchange() {
return new DirectExchange("ttl-direct-exchange", true, false);
}
//2、设置队列的过期时间
@Bean
public Queue ttlDirectQueue() {
HashMap<String, Object> ttlTime = new HashMap<>();
ttlTime.put("x-message-ttl", 5000);//时间单位为毫秒,必须为整型
ttlTime.put("x-dead-letter-exchange", "dead-direct-exchange");//消息过期时,绑定死信队列
ttlTime.put("x-dead-letter-routing-key", "dead");//绑定路由key,fanout模式不需要绑定
return new Queue("ttl-direct-queue", true, false, false, ttlTime);
}
//3、完成交换机和队列的饿绑定关系
@Bean
public Binding ttlDirectBinding() {
return BindingBuilder.bind(ttlDirectQueue()).to(ttldirectExchange()).with("ttl");
}
}
3、设置死信队列并且绑定交换机
@Configuration
public class DeadRabbitmqConfig {
//1、声明死信队列的交换机direct
@Bean
public DirectExchange deadExchange() {
return new DirectExchange("dead-direct-exchange", true, false);
}
//2、声明死信队列
@Bean
public Queue deadDirectQueue() {
return new Queue("dead-direct-queue", true);
}
//3、完成交换机和队列的绑定关系
@Bean
public Binding deadDirectBinding() {
return BindingBuilder.bind(deadDirectQueue()).to(deadExchange()).with("dead");
}
}
4、测试
@SpringBootTest
class RabbitmqProducerApplicationTests {
@Autowired
private TTLOrderService ttlOrderService;
@Test
void contextLoadsTtl() {
ttlOrderService.makeOrderTtl("1", "1", 12);
}
}
过期队列过期前,消息在此队列中:
在过期后,此消息进入死信队列: