延时队列是一个内部有序的数据结构,其主要功能体现在其延时特性上。这种队列存储的元素都设定了特定的处理时间,意味着它们需要在规定的时间点或者延迟之后才能被取出并进行相应的处理。简而言之,延时队列被设计用于存放那些需要在特定时间到达时才处理的元素。
延迟队列使用场景
1、定时任务调度:在任务调度系统中,延时队列可以用来安排任务在未来的某个时间点执行,比如定时发送邮件、消息推送、定时刷新缓存等。
2、订单超时取消:在电子商务网站中,未在规定时间内完成的订单可以通过延时队列来设置超时取消,如果订单在队列规定的延迟时间内没有被支付,系统将会自动取消订单。
3、消息重试机制:在消息队列中,如果消息处理失败了,可以将消息放入延时队列中,等待一段时间后再次尝试处理,以实现重试机制。
4、缓存数据过期:对于缓存系统,延时队列可以用来管理数据的过期时间,当数据在队列中的时间超过了设定的延迟时间,就将其从缓存中移除。
5、会话管理:在Web应用中,可以使用延时队列来管理用户会话的过期时间,当会话超过设定的有效时间后,系统将自动销毁会话。
6、任务延迟执行:有些任务可能需要在特定的时间窗口之后执行,比如数据分析任务可能需要在当天结束后才能进行,延时队列可以用来实现这种延迟执行。
7、权限验证Token过期:在安全系统中,发放的Token通常有有效时间,延时队列可以用来检测Token是否过期,并在过期时将其从系统中移除。
RabbitMQ 中的 TTL
TTL 是 RabbitMQ 中一个消息或者队列的属性,表明一条消息或者该队列中的所有
消息的最大存活时间,单位是毫秒。换句话说,如果一条消息设置了 TTL 属性或者进入了设置 TTL 属性的队列,那么这条消息如果在 TTL 设置的时间内没有被消费,则会成为"死信"。如果同时配置了队列的 TTL 和消息的TTL,那么较小的那个值将会被使用,有两种方式设置 TTL。
一、消息设置 TTL
rabbitTemplate.convertAndSend("X","XC",message,col -> {
//设置ttl过期属性
col.getMessageProperties().setExpiration("10000");
return col;
});
二、队列设置
//通过属性设置ttl
args.put("x-message-ttl",30000);
//创建队列A
return QueueBuilder.durable(QUEUE_B).withArguments(args).build();
区别
一、队列的 TTL 属性
一旦消息过期,就会被队列丢弃(如果配置了死信队列被丢到死信队列中)。
二、消息设置 TTL
消息即使过期,也不一定会被马上丢弃,因为消息是否过期是在即将投递到消费者
之前判定的,如果当前队列有严重的消息积压情况,则已过期的消息也许还能存活较长时间;另外,还需要注意的一点是,如果不设置 TTL,表示消息永远不会过期,如果将 TTL 设置为 0,则表示除非此时可以直接投递该消息到消费者,否则该消息将会被丢弃。
代码
首选创建一个配置类
package mq.consumer.config;
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 java.util.HashMap;
import java.util.Map;
@Configuration
public class TtlQueueConfig {
public static final String X_EXCHANGE = "X_EXCHANGE";
public static final String QUEUE_A = "QUEUE_A";
public static final String QUEUE_B = "QUEUE_B";
public static final String QUEUE_C = "QUEUE_C";
//死信队列交换机
public static final String Y_DEAD_LETTER_EXCHANGE = "Y_DEAD_LETTER_EXCHANGE";
//死信队列
public static final String DEAD_LETTER_QUEUE = "DEAD_LETTER_QUEUE";
/**
* 声明xExchange
* @return
*/
@Bean("xExchange")
public DirectExchange xExchange(){
return new DirectExchange(X_EXCHANGE);
}
/**
* 声明yExchange
* @return
*/
@Bean("yExchange")
public DirectExchange yExchange(){
return new DirectExchange(Y_DEAD_LETTER_EXCHANGE);
}
/**
* 声明队列A过期时间(TTL)为10秒
* @return
*/
@Bean("queueA")
public Queue queueA(){
Map<String,Object> args = new HashMap<>();
//声明当前队列绑定的死信交换机
args.put("x-dead-letter-exchange",Y_DEAD_LETTER_EXCHANGE);
//声明当前队列的死信路由Key
args.put("x-dead-letter-routing-key","YD");
//声明队列的TTL
args.put("x-message-ttl",10000);
//创建队列A
return QueueBuilder.durable(QUEUE_A).withArguments(args).build();
}
/**
* 声明队列A绑定X交换机
* @param queueA
* @param xExchange
* @return
*/
@Bean
public Binding queueABindingX(@Qualifier("queueA") Queue queueA,
@Qualifier("xExchange") DirectExchange xExchange){
return BindingBuilder.bind(queueA).to(xExchange).with("XA");
}
/**
* 声明队列B过期时间(TTL)为30秒
* @return
*/
@Bean("queueB")
public Queue queueB(){
Map<String,Object> args = new HashMap<>();
//声明当前队列绑定的死信交换机
args.put("x-dead-letter-exchange",Y_DEAD_LETTER_EXCHANGE);
//声明当前队列的死信路由Key
args.put("x-dead-letter-routing-key","YD");
//声明队列的TTL
args.put("x-message-ttl",30000);
//创建队列A
return QueueBuilder.durable(QUEUE_B).withArguments(args).build();
}
/**
* 声明队列B绑定X交换机
* @param queueB
* @param xExchange
* @return
*/
@Bean
public Binding queueBBindingX(@Qualifier("queueB") Queue queueB,
@Qualifier("xExchange") DirectExchange xExchange){
return BindingBuilder.bind(queueB).to(xExchange).with("XB");
}
/**
* 声明死信队列QD
* @return
*/
@Bean("queueD")
public Queue queueD(){
return new Queue(DEAD_LETTER_QUEUE);
}
/**
* 声明死信队列QD绑定关系
* @param QueueD
* @param yExchange
* @return
*/
@Bean
public Binding deadLetterBindingQAD(@Qualifier("queueD") Queue queueD,@Qualifier("yExchange") DirectExchange yExchange){
return BindingBuilder.bind(queueD).to(yExchange).with("YD");
}
}
创建生产者
@Slf4j
@RequestMapping("ttl")
@RestController
public class SendMessageController {
@Autowired
private RabbitTemplate rabbitTemplate;
@GetMapping("sendMsg/{message}")
public void sendMsg(@PathVariable String message){
log.info("当前时间:{},发送一条消息到队列:{}",new Date(),message);
rabbitTemplate.convertAndSend("X_EXCHANGE","XA","消息来自TTL=10秒的队列:"+message);
rabbitTemplate.convertAndSend("X_EXCHANGE","XB","消息来自TTL=30秒的队列:"+message);
}
}
创建消费者 接收信息
@Slf4j
@Component
public class DeadLetterQueueConsumer {
@RabbitListener(queues = "DEAD_LETTER_QUEUE")
public void receiveD(Message message){
String msg = new String(message.getBody());
log.info("当前时间:{},收到死信队列{}",new Date().toString(),msg);
}
}
测试效果,发送一个请求
消费者端接收到消息,可以看到消费者在两个时间段接收到了数据
缺点
通过以上的延迟队列可以看到有一个缺点,无法灵活的指定延迟的时间。
延时队列的优化
先去官网下载rabbitmq_delayed_message_exchange插件
https://github.com/rabbitmq/rabbitmq-delayed-message-exchange/releases
将下载后的插件放到安装目录下的plugins目录
在rabbitMQ安装目录的sbin中运行 rabbitmg plugins enable rabbitmq_delayed_message_exchange。见上图
等待完成安装.
windows下安装
进入到rabbitMQ安装目录下的plugins目录
将下载的插件放进去
进入到安装目录下的sbin目录,在地址栏里面输入cmd
运行命令
rabbitmq-plugins.bat enable rabbitmq_delayed_message_exchange
在TtlQueueConfig类中添加队列C代码
@Bean("queueC")
public Queue queueC(){
Map<String,Object> args = new HashMap<>();
//声明当前队列绑定的死信交换机
args.put("x-dead-letter-exchange",Y_DEAD_LETTER_EXCHANGE);
//声明当前队列的死信路由Key
args.put("x-dead-letter-routing-key","YD");
//创建队列C
return QueueBuilder.durable(QUEUE_C).withArguments(args).build();
}
/**
* 声明死信队列QD绑定关系
* @param queueC
* @param xExchange
* @return
*/
@Bean
public Binding queuecBindingX(@Qualifier("queueC") Queue queueC,@Qualifier("xExchange") DirectExchange xExchange){
return BindingBuilder.bind(queueC).to(xExchange).with("XC");
}
生产者添加代码 ttl参数为接收的时间
@GetMapping("sendMsgWithTtl/{message}/{ttl}")
public void sendMsgWithTtl(@PathVariable String message,@PathVariable String ttl){
rabbitTemplate.convertAndSend("X_EXCHANGE","XC",message,messagePostProcessor -> {
messagePostProcessor.getMessageProperties().setExpiration(ttl);
return messagePostProcessor;
});
log.info("当前时间:{},发送一条消息给一个TTL队列:队列是自定义时间{},延迟时间{}",new Date(),message,ttl);
}
测试
生产者记录发送时间
消费者消费时间