RabbitMq延时队列实际应用场景
比如,boss让你开发一个30分钟客户不付款就取消订单的场景;
如果在促销活动期间,肯定会有大量的未付款的订单数据,如果用轮训,或者是redis失效key来作为处理方案,肯定会被CTO骂死;因为,如果高频次轮训,虽然能保证比较及时的取消订单。但是无疑给系统造成了很大压力。如果用redis失效key来做,那么redis也会承受很高的压力。此时,就需要mq这样的削峰填谷、异步处理中间件帮忙处理此种场景。
将订单数据放入延时队列,到达过期时间,队列数据发送给消费者,进行取消订单操作。即保证了服务器的稳定性,也能保证客户过期订单被较为及时的处理掉。
基于死信队列的延时队列
如图所示,先声明不同过期时间(TTL)的业务队列, 过期时间到了,业务队列将数据发送给死信队列,消费者就可以通过过期时间,来延时处理业务数据;
但是这种方案的缺点是,不够灵活,如果业务每次的过期时间都是不同的,并且需要是自定义过期时间的。那么久需要新建更多不同过期时间的业务队列,所以基于死信队列的延时队列局限性还是非常大的。
RabbitMq为了解决这个问题,提供了专门的延时交换机
基于延时交换机的延时队列
要想试用此种延时队列,需要到RabbitMq官网下载插件;
https://www.rabbitmq.com/community-plugins.html
下载rabbitmq_delayed_message_exchange插件,然后解压放置到RabbitMQ的插件目录。进入
RabbitMQ的安装目录下的plgins目录,执行下面命令让该插件生效,并且重启RabbitMQ
# 安装插件
rabbitmq-plugins enable rabbitmq_delayed_message_exchange
# 重启RabbitMq
rabbitmq-server restart
延时队列图解
在我们自定义的delay交换机中,这是一种新的交换类型,该类型消息支持延迟投递机制消息传递后并不会立即投递到目标队列中,而是存储在mnesia(一个分布式数据系统)表中,当达到投递时间时,才投递到目标队列中。
SpringBoot伪代码
配置类
/**
* 配置类
*/
@Configuration
public class RabbitConfig {
@Bean
public Queue delayedQueue() {
return new Queue("delayed.queue", true, false);
}
/**
* 自定义交换机。
*/
@Bean
public CustomExchange delayedExchange(){
Map<String, Object> args = newHashMap<>();
//自定义交换机的类型
args.put("x-delayed-type", "direct");
return new CustomExchange("delayed.exchange", "x-delayed-message", true, false, args);
}
@Bean
public Binding bindingDelayedQueue(@Qualifier("delayedQueue")Queue queue,
@Qualifier("delayedExchange") CustomExchange delayedExchange){
return BindingBuilder.bind(queue)
.to(delayedExchange)
.with("delayed.routingkey")
.noargs();
}
}
生产者
@GetMapping("sendDelayMsg")
public void sendMsg(@RequestParam String message, @RequestParam Integer delayTime) {
rabbitTemplate.convertAndSend("delayed.exchange", "delayed.routingkey", message,
correlationData ->{
correlationData.getMessageProperties()
.setDelay(delayTime);
return correlationData;
});
log.info("当前时间:{},发送一条延迟{}毫秒的信息给队列delayed.queue,消息内容:{}",
new Date(),
delayTime,
message);
}
消费者无特殊配置,此处省略;
消息的TTL和队列的TTL
队列TTL:
如果设置了队列的TTL属性,那么一旦消息过期,就会被队列丢弃(如果配置了死信队列被丢到死信队列中)。
消息TTL:
消息即使过期,也不一定会被马上丢弃,因为消息是否过期是在即将投递到消费者之前判定的,如果当前队列有严重的消息积压情况,则已过期的消息也许还能存活较长时间;另外,还需要注意的一点是,如果不设置TTL,表示消息永远不会过期,如果将TTL设置为0,则表示除非此时可以直接投递该消息到消费者,否则该消息将会被丢弃。
总结
当然,技术服务与业务;延时队列还有很多其它选择,比如利用Java的DelayQueue,利用Redis的zset,利用Quartz或者利用kafka的时间轮,这些方式各有特点,看需要适用的场景