TTL
TTL(Time to Live,过期时间)即过期时间。RabbitMQ 可以对消息和队列设置 TTL。当消息到达存活时间之后,还没有被消费,就会被自动清除。
在业务场景中,例如网上购物下单超过 24 小时未付款订单会自动取消,或申请退款后超过 7 天未被处理则自动退款,均可利用此机制实现。
添加配置
spring:
application:
name: rabbit-extensions-demo
rabbitmq:
addresses: amqp://study:[email protected]:5672/extension
常量类
public class Constants {
// ttl
public static final String TTL_QUEUE = "ttl.queue";
public static final String TTL_QUEUE2 = "ttl2.queue";
public static final String TTL_EXCHANGE = "ttl.exchange";
}
消息的 TTL
声明队列和交换机并绑定二者关系
import org.springframework.amqp.core.*;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import rabbitextensionsdemo.constant.Constants;
@Configuration
public class RabbitMQConfig {
@Bean("ttlQueue")
public Queue ttlQueue() {
return QueueBuilder.durable(Constants.TTL_QUEUE).build();
}
@Bean("ttlExchange")
public DirectExchange ttlExchange() {
return ExchangeBuilder.directExchange(Constants.TTL_EXCHANGE).build();
}
@Bean("ttlBinding")
public Binding ttlBinding(@Qualifier("ttlQueue") Queue queue, @Qualifier("ttlExchange") Exchange exchange) {
return BindingBuilder.bind(queue).to(exchange).with("ttl").noargs();
}
}
编写生产消息代码
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import rabbitextensionsdemo.constant.Constants;
@RequestMapping("/producer")
@RestController
public class ProducerController {
@Resource(name = "rabbitTemplate")
private RabbitTemplate rabbitTemplate;
@RequestMapping("/ttl")
public String ttl() {
System.out.println("ttl...");
rabbitTemplate.convertAndSend(Constants.TTL_EXCHANGE, "ttl", "ttl test 30s...", message -> {
message.getMessageProperties().setExpiration("30000"); // 单位:毫秒,过期时间为 30s
return message;
});
rabbitTemplate.convertAndSend(Constants.TTL_EXCHANGE, "ttl", "ttl test 10s...", message -> {
message.getMessageProperties().setExpiration("10000"); // 单位:毫秒,过期时间为 10s
return message;
});
return "消息发送成功";
}
}
可以看到,生产的两条消息的确消失了,但是耗时 30 秒。原因是因为设置消息的 TTL,哪怕消息过期了,也不会立即删除,而是在将消息投递给消费者之前进行判定。
队列的 TTL
声明队列和交换机并绑定二者关系
import org.springframework.amqp.core.*;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import rabbitextensionsdemo.constant.Constants;
@Configuration
public class RabbitMQConfig {
@Bean("ttlQueue2")
public Queue ttlQueue2() {
return QueueBuilder.durable(Constants.TTL_QUEUE2).ttl(20000).build(); // 设置队列的 ttl 为 20s
}
@Bean("ttlExchange")
public DirectExchange ttlExchange() {
return ExchangeBuilder.directExchange(Constants.TTL_EXCHANGE).build();
}
@Bean("ttlBinding2")
public Binding ttlBinding2(@Qualifier("ttlQueue2") Queue queue, @Qualifier("ttlExchange") Exchange exchange) {
return BindingBuilder.bind(queue).to(exchange).with("ttl").noargs();
}
}
编写生产消息代码
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import rabbitextensionsdemo.constant.Constants;
@RequestMapping("/producer")
@RestController
public class ProducerController {
@Resource(name = "rabbitTemplate")
private RabbitTemplate rabbitTemplate;
@RequestMapping("/ttl")
public String ttl() {
System.out.println("ttl...");
rabbitTemplate.convertAndSend(Constants.TTL_EXCHANGE, "ttl", "ttl test 30s...", message -> {
message.getMessageProperties().setExpiration("30000"); // 单位:毫秒,过期时间为 30s
return message;
});
return "消息发送成功";
}
@RequestMapping("/ttl2")
public String ttl2() {
System.out.println("ttl2...");
// 发送普通消息
rabbitTemplate.convertAndSend(Constants.TTL_EXCHANGE, "ttl", "ttl test...");
return "消息发送成功";
}
}
生产消息(消息无 TTL)
虽然没有给消息设置 TTL,但是给 ttl2.queue 队列设置了 20 秒的 TTL,20 秒过后,ttl2.queue 队列中的消息消失了。
生产消息(消息有 TTL)
此时可以看到,20 秒之后,消息消失了。我们给队列设置的 TTL 为 20 秒,给消息设置的 TTL 为 30 秒,最终消息的 TTL 为 min(消息的 TTL,队列的 TTL)。
消息的 TTL 和队列的 TTL
设置队列 TTL 属性的方法,一旦消息过期,就会从队列中删除。 设置消息 TTL 的方法,即使消息过期,也不会马上从队列中删除,而是在即将投递到消费者之前进行判定的。
为什么这两种方法处理的方式不一样? 因为设置队列过期时间,队列中已过期的消息肯定在队列头部,RabbitMQ 只要定期从队头开始扫描是否有过期的消息即可。 而设置消息 TTL 的方法,每条消息的过期时间不同,如果要删除所有过期消息需要扫描整个队列,所以不如等到此消息即将被消费时再判定是否过期,如果过期再进行删除即可。


