RabbitMQ TTL过期时间与死信队列说明
TTL过期时间
我们在RabbitMQ中发布消息时,有两种方法设置某个队列的消息过期时间:
1、针对队列来说,可以使用x-message-ttl参数设置当前队列中所有消息的过期时间,即当前队列中所有的消息过期时间都一样;
2、针对单个消息来说,在发布消息时,可以使用Expiration参数来设置单个消息的过期时间。
以上两个参数的单位都是毫秒,即1000毫秒为1秒。如果以上两个都设置,则以当前消息最短的那个过期时间为准。
死信队列有其特殊的应用场景,例如用户在商城下单成功并点击去支付的时候,如果在指定的时间内未支付,那么就可以将该下单消息投递到死信队列中,至于后续怎么处理死信队列需要结合具体的应用场景。
1.设置队列中所有消息的过期时间
通过以下方式可以完成TTL队列的创建
@Configuration public class RabbitExchangeAndQueueConfig { /** * 创建交换机 * durable:是否持久化 * autoDelete:是否自动删除,当没有生产者或消费者使用该交换机时,会自动删除 */ //创建direct交换机 @Bean public DirectExchange ttlDirectExchange(){ return new DirectExchange("ttl-direct-exchange",true,false); } /** * 创建队列 * durable:是否持久化 * exclusive:默认false,只能在当前创建连接时使用,连接关闭后队列自动删除,该优先级高于durable * autoDelete:是否自动删除,当没有生产者或消费者使用该交换机时,会自动删除 */ //创建ttl队列 @Bean public Queue ttlQueue(){ Map<String, Object> map = new HashMap<>(); //设置过期时间为10s map.put("x-message-ttl",10000); return new Queue("ttlQueue",true,false,false,map); } //ttl队列绑定交换机 @Bean public Binding ttlDirectBinding(){ return BindingBuilder.bind(ttlQueue()).to(ttlDirectExchange()).with("ttl"); } }
带有TTL的队列在我们的管理页面中会有显示
发送消息测试
@SpringBootTest class RabbitmqProviderApplicationTests { @Autowired RabbitTemplate rabbitTemplate; @Test void testTTLDirect() { String msg = "hello"; rabbitTemplate.convertAndSend("ttl-direct-exchange","ttl",msg); } }
然后可以自行通过rabbitmq管理页面观察队列中消息的存活时间
2.针对单个消息的过期时间
通过以下方式创建一个正常的队列
@Configuration public class RabbitExchangeAndQueueConfig { /** * 创建交换机 * durable:是否持久化 * autoDelete:是否自动删除,当没有生产者或消费者使用该交换机时,会自动删除 */ @Bean public DirectExchange directExchange(){ return new DirectExchange("direct-exchange",true,false); } /** * 创建队列 * durable:是否持久化 * exclusive:默认false,只能在当前创建连接时使用,连接关闭后队列自动删除,该优先级高于durable * autoDelete:是否自动删除,当没有生产者或消费者使用该交换机时,会自动删除 */ @Bean public Queue smsQueue(){ return new Queue("smsQueue",true); } /** * 绑定交换机和队列 */ //sms队列绑定direct交换机 @Bean public Binding smsDirectBinding(){ return BindingBuilder.bind(smsQueue()).to(directExchange()).with("sms"); } }
在发送消息的时候设置TTL过期时间
@SpringBootTest class RabbitmqProviderApplicationTests { @Autowired RabbitTemplate rabbitTemplate; @Test void testDirect() { MessagePostProcessor messagePostProcessor = new MessagePostProcessor() { @Override public Message postProcessMessage(Message message) throws AmqpException { //在此进行TTL设置 message.getMessageProperties().setExpiration("10000"); message.getMessageProperties().setContentEncoding("UTF-8"); return message; } }; String msg = "hello"; rabbitTemplate.convertAndSend("direct-exchange","sms",msg,messagePostProcessor); } }
这里需要注意一点,因为我们该队列不是TTL队列,又因为队列中的消息都是顺序排序的,所以当如果我们该队列之前存在没有设置TTL的消息,未被消费,那么我们设置了的TTL消息就会一直存在该队列中
死信队列
死信队列其实也是一个正常的队列,可以被消费,只不过,它是专门用来存放一些被丢弃了的消息。因为如果消息直接被丢弃了其实是个很危险的事情,所以我们使用一个队列来存放这些消息,这个队列就被称为死信队列。
通常什么消息会放置我们的死信队列呢?
1.消息TTL过期
2.队列达到了最大长度,无法再添加信息到MQ中
3.消息被拒绝,并且没有重新入队
死信队列的实现
1.创建死信交换机和队列
这里我们可以新创建一个交换机和队列用来接收我们的死信队列
@Configuration public class RabbitExchangeAndQueueConfig { /** * 创建交换机 * durable:是否持久化 * autoDelete:是否自动删除,当没有生产者或消费者使用该交换机时,会自动删除 */ //创建死信direct交换机 @Bean public DirectExchange deadDirectExchange(){ return new DirectExchange("dead-direct-exchange",true,false); } /** * 创建队列 * durable:是否持久化 * exclusive:默认false,只能在当前创建连接时使用,连接关闭后队列自动删除,该优先级高于durable * autoDelete:是否自动删除,当没有生产者或消费者使用该交换机时,会自动删除 */ //创建死信的队列 @Bean public Queue deadQueue(){ return new Queue("deadQueue",true); } /** * 绑定交换机和队列 */ //死信交换机与死信队列绑定 @Bean public Binding deadDirectBinding(){ return BindingBuilder.bind(deadQueue()).to(deadDirectExchange()).with("dead"); } }
2.基于TTL过期时间实现死信队列
然后重新创建一个TTL队列,这里不可直接修改我们之前上方所创建的TTL队列参数的,若需要继续使用我们之前上方所创建的队列,则需要先把该队列进行删除,否则会报错。当然生产环境的话,不建议直接去删除我们的队列。
#这里因为是测试,为了方便.......我将我之前创建的TTL队列进行删除,然后修改TTL队列代码
基于之前的TTL队列做以下修改
@Bean public Queue ttlQueue(){ Map<String, Object> map = new HashMap<>(); //设置过期时间为10s map.put("x-message-ttl",10000); //下方设置死信队列的交换机名称,因为我是direct的交换机,所以还需要设置其routingKey,若是fanout模式则不需要设置routingKey map.put("x-dead-letter-exchange","dead-direct-exchange"); map.put("x-dead-letter-routing-key","dead"); return new Queue("ttlQueue",true,false,false,map); }
发送消息测试
@SpringBootTest class RabbitmqProviderApplicationTests { @Autowired RabbitTemplate rabbitTemplate; @Test void testTTLDirect() { String msg = "hello"; rabbitTemplate.convertAndSend("ttl-direct-exchange","ttl",msg); } }
观察rabbitmq管理页面,可以发现,配置了死信队列的话,会多出两个参数DLX,DLK。DLX代表死信交换机,DLK代表死信routingKey
可以看到,当ttl队列消息过期后,就会存放至了我们的死信队列中
3.基于队列最大长度实现死信队列
还是基于刚刚的ttl队列进行修改
基于之前的TTL队列做以下修改
@Bean public Queue ttlQueue(){ Map<String, Object> map = new HashMap<>(); //设置过期时间为10s //map.put("x-message-ttl",10000); //设置队列最大长度为5 map.put("x-max-length",5); //下方设置死信队列的交换机名称,因为我是direct的交换机,所以还需要设置其routingKey,若是fanout模式则不需要设置routingKey map.put("x-dead-letter-exchange","dead-direct-exchange"); map.put("x-dead-letter-routing-key","dead"); return new Queue("ttlQueue",true,false,false,map); }
然后把存在的ttlQueue队列在页面上再次删除
发送消息测试
@Test void testLengthDirect(){ String msg = ""; for(int i = 0; i< 11 ;i++){ msg = "第" + i; rabbitTemplate.convertAndSend("ttl-direct-exchange","ttl",msg); } }
观察rabbitmq页面,可以看到由于我们ttlQueue队列最大长度只有5,所以其余7项都被转移到了死信队列中,Lim标签代表设置了最大长度