RabbitMQ 高级特性——TTL

文章目录

  • 前言
  • TTL
    • [设置消息的 TTL](#设置消息的 TTL)
    • [设置队列的 TTL](#设置队列的 TTL)

前言

对于前面讲到的重试机制中,当确认策略为 MANUAL 手动确认的时候,如果消费者出现了程序逻辑错误,那么消息就无法被争取处理,那么就会执行 basicNack 方法,如果我们的 basicNack 方法的第三个参数的值为 true 的话,在只有这一个消费者的情况下,这个消息就会反复重新进入队列并且返回投递给这个消费者,那么在这个队列中的后面的消息就无法被提递给消费者,这样就导致了消息积压,那么如何处理这个问题呢?答案就是为消息或者队列设置 TTL(ime-To-Live)生存时间,当消息或者队列存在一段时间后就会被丢弃或者投递到死信队列中。那么这篇文章将介绍 RabbitMQ 中的 TTL。

TTL

TTL(Time-to-Live)过期时间,RabibtMQ 可以对队列和消息设置 TTL。当消息到达存活时间之后,如果该消息还没有被消费,那么就会被自动处理掉。

就是我们平时购物的时候,如果下单超过 24 消失还没有付款的话,订单就会被自动取消。

设置消息的 TTL

RabbitMQ 有两种设置 TTL 的方法,一是设置队列的 TTL,队列中的所有消息都有相同的过期时间,而就是对消息单独设置 TTL,每条消息的 TTL 可以不同。如果两种方式一起使用,则过期时间为两者的较小值。

先来看看如何对消息设置 TTL。

java 复制代码
public static final String TTL_EXCHANGE = "ttl.exchange";
public static final String TTL_QUEUE = "ttl.queue";
java 复制代码
@Bean("ttlExchange")
public DirectExchange ttlExchange() {
    return ExchangeBuilder.directExchange(Constants.TTL_EXCHANGE).durable(true).build();
}
    
@Bean("ttlQueue")
public Queue ttlQueue() {
    return QueueBuilder.durable(Constants.TTL_QUEUE).build();
}
    
@Bean("ttlBinding")
public Binding ttlBinding(@Qualifier("ttlQueue") Queue queue,@Qualifier("ttlExchange") DirectExchange exchange) {
    return BindingBuilder.bind(queue).to(exchange).with("ttl");
}
java 复制代码
@RequestMapping("/ttl")
public String ttl() {
	//设置消息的过期时间
    MessagePostProcessor messagePostProcessor = new MessagePostProcessor() {
        @Override
        public Message postProcessMessage(Message message) throws AmqpException {
            message.getMessageProperties().setExpiration("10000");
            return message;
        }
    };
    rabbitTemplate.convertAndSend(Constants.TTL_EXCHANGE,"ttl","rabbitmq ttl",messagePostProcessor);
    return "消息发送成功";
}

这里消费者的代码我们就不写了,就通过 RabbitMQ 的管理页面观察消息的过期:

启动之后,并且让生产者生产消息,可以发现队列中已经存在一条消息了,然后等待一段时间再观察会发现刚刚生产的消息已经不存在了:

这是设置消息的过期时间,那么下面我们来看看如何设置队列的时间。

设置队列的 TTL

设置队列的 TTL 是在我们声明队列的时候设置的,声明队列的时候加入 x-message-ttl 参数实现的,单位是毫秒。

java 复制代码
//队列设置TTL的第一种方法
@Bean("ttlQueue")
public Queue ttlQueue() {
    return QueueBuilder.durable(Constants.TTL_QUEUE).ttl(20*1000).build();
}

//队列设置TTL的第二种方法
@Bean("ttlQueue")
public Queue ttlQueue() {
    Map<String, Object> arguments = new HashMap<>();
    arguments.put("x-message-ttl",20000);
    return QueueBuilder.durable(Constants.TTL_QUEUE).withArguments(arguments).build();
}

//也可以将map平铺出来
@Bean("ttlQueue")
public Queue ttlQueue() {
    Map<String, Object> arguments = new HashMap<>();
    arguments.put("x-message-ttl",20000);
    return QueueBuilder.durable(Constants.TTL_QUEUE).withArgument("x-message-ttl",20000).build();
}

这里创建两个队列,一个是设置了 TTL 的队列,一个是没有设置 TTL 的对了,然后看两个队列的差别:

java 复制代码
@Bean("ttlExchange")
public DirectExchange ttlExchange() {
    return ExchangeBuilder.directExchange(Constants.TTL_EXCHANGE).durable(true).build();
}

@Bean("ttlQueue")
public Queue ttlQueue() {
    Map<String, Object> arguments = new HashMap<>();
    arguments.put("x-message-ttl",20000);
    return QueueBuilder.durable(Constants.TTL_QUEUE).withArgument("x-message-ttl",20000).build();
}

@Bean("normalQueue")
public Queue normalQueue() {
    return QueueBuilder.durable(Constants.NORMAL_QUEUE).build();
}

@Bean("ttlBinding")
public Binding ttlBinding(@Qualifier("ttlQueue") Queue queue,@Qualifier("ttlExchange") DirectExchange exchange) {
    return BindingBuilder.bind(queue).to(exchange).with("ttl");
}

@Bean("ttlBinding2")
public Binding ttlBinding2(@Qualifier("normalQueue") Queue queue,@Qualifier("ttlExchange") DirectExchange exchange) {
    return BindingBuilder.bind(queue).to(exchange).with("ttl");
}


过一段时间之后再观察:

设置队列的过期时间和设置消息的过期时间的区别

设置队列TTL属性的方法,一旦消息过期,就会从队列中删除。

设置消息TTL的方法,即使消息过期,也不会马上从队列中删除,而是在即将投递到消费者之前进行判定的。

为什么这两种方法处理的方式不一样?

因为设置队列过期时间,队列中已过期的消息肯定在队列头部(假设是按照时间顺序入队的),RabbitMQ只要定期从队头开始扫描是否有过期的消息即可。

而设置消息TTL的方式,每条消息的过期时间不同,如果要删除所有过期消息需要扫描整个队列,这样效率较低。因此,RabbitMQ选择等到此消息即将被消费时再判定是否过期,如果过期再进行删除即可。跟 Redis 中的惰性删除是一个道理。这种方式避免了不必要的全队列扫描,提高了效率。

注意:这里的"即将投递到消费者之前"通常指的是在消息被发送到消费者之前,RabbitMQ会检查该消息是否已过期。如果消息已过期,RabbitMQ会将其删除,不会将其发送给消费者。这种机制确保了消费者不会接收到过期的消息。

相关推荐
用户8307196840821 天前
RabbitMQ vs RocketMQ 事务大对决:一个在“裸奔”,一个在“开挂”?
后端·rabbitmq·rocketmq
初次攀爬者2 天前
RabbitMQ的消息模式和高级特性
后端·消息队列·rabbitmq
初次攀爬者4 天前
ZooKeeper 实现分布式锁的两种方式
分布式·后端·zookeeper
让我上个超影吧5 天前
消息队列——RabbitMQ(高级)
java·rabbitmq
塔中妖6 天前
Windows 安装 RabbitMQ 详细教程(含 Erlang 环境配置)
windows·rabbitmq·erlang
断手当码农6 天前
Redis 实现分布式锁的三种方式
数据库·redis·分布式
初次攀爬者6 天前
Redis分布式锁实现的三种方式-基于setnx,lua脚本和Redisson
redis·分布式·后端
业精于勤_荒于稀6 天前
物流订单系统99.99%可用性全链路容灾体系落地操作手册
分布式
Ronin3056 天前
信道管理模块和异步线程模块
开发语言·c++·rabbitmq·异步线程·信道管理
Asher05096 天前
Hadoop核心技术与实战指南
大数据·hadoop·分布式