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标签代表设置了最大长度

 

posted @ 2022-04-21 17:13  RFAA  阅读(1188)  评论(0编辑  收藏  举报