一、何谓延迟队列?
顾名思义,延迟队列就是进入该队列的消息会被延迟消费的队列。而普通的队列,消息一旦进入队列就会被消费者立即消费。有些同学可能会思考,那延迟队列可以用来干什么,为什么要使用延迟队列以及该如何使用?接下来,笔者将会为同学们一一介绍,然后带领同学们一起进行实战演练。
二、延迟队列的应用场景
延迟队列的应用场景有很多,以下列举了最常用的几种:
- 用户生成订单30分钟之后,如果还未支付订单则需要及时的取消订单。
- 当用户注册成功之后,过一分钟然后给用户发送短信注册提示以及邮箱注册提示。
有些同学在此刻可能会有一些疑虑。上面两种场景通过定时器也可以实现,而且非常的简单,也不用去维护RabbitMq,为什么还要使用延迟队列?不可否认,定时器是可以实现上面的场景,但是熟悉定时器同学们肯定知道,定时器是通过不断的轮询的方式实现的,这种做法十分的不优雅,而且还会存在及时性的问题。说了这么多定时器实现的诸多缺点,那RabbitMq又是如何实现的,会不会有同样的问题?在下文,笔者将会为同学们详细的介绍。
三、如何实现延迟队列?
通过上文,笔者相信同学们对延迟队列也有了一定的了解。接下来,笔者将带领同学们进行实战演练。实现延迟队列的方式有以下两种:
- 通过消息过期后进入死信交换机,再由交换机转发到延迟消费队列,实现延迟功能。
- 使用rabbitmq-delayed-message-exchange插件实现延迟功能。
在实际开发中,通常情况下都会使用RabbitMq插件实现延迟队列。因此,笔者在本文会详细的介绍第二种实现方式。有兴趣的同学可以尝试实践第一种。
在开始实战之前,笔者默认同学们都已经安装好了RabbitMq。
1.1 下载插件
打开官网下载:http://www.rabbitmq.com/community-plugins.html
这里应该选择与RabbitMq相对应的版本进行下载。(注意:此插件只适应于RabbitMQ 3.5.7及以上的版本,依赖Erlang/OPT 18.0及以上运行环境)
1.2 安装插件
- 上传下载的文件至linux服务器,然后解压下载的插件:
unzip rabbitmq_delayed_message_exchange-20171215-3.6.x.zip - 复制插件到RabbitMq的plugins目录下:
sudo cp -r rabbitmq_delayed_message_exchange-20171215-3.6.x /usr/lib/rabbitmq/lib/rabbitmq_server-3.6.6/plugins - 关闭RabbitMQ服务,然后启动延时插件:
rabbitmq-plugins enable rabbitmq_delayed_message_exchange - 到这一步,RabbitMq的延时插件已经安装完毕
1.3 代码实现
讲了这么多理论知识,想必某些同学已经迫不及待的想要看代码。接下来,笔者就开始满足同学们的要求,开始上代码。
-
配置启动类
@SpringBootApplication @EnableRabbit class RabbitMqApplication { public static void main(String[] args) { SpringApplication.run(RabbitMqApplication.class, args); } }
-
配置队列
@Configuration public class RabbitMqConfig { public final static String DELAYED_QUEUE_NAME = "delayed.order"; public final static String EXCHANGE_NAME = "delayed_exchange"; public final static String DELAY_ORDER_ROUTING_KEY = "delay_order_routing_key"; @Bean public Queue queue() { return new Queue(DELAYED_QUEUE_NAME); } // 配置默认的交换机 @Bean CustomExchange customExchange() { Map<String, Object> args = new HashMap<>(); args.put("x-delayed-type", "direct"); //参数二为类型:必须是x-delayed-message return new CustomExchange(EXCHANGE_NAME, "x-delayed-message", true, false, args); } // 绑定队列到交换器 @Bean Binding binding() { return BindingBuilder.bind(queue()).to(customExchange()).with(DELAY_ORDER_ROUTING_KEY).noargs(); } }
-
发送消息
@Slf4j public class RabbitMqSender { @Autowired private RabbitTemplate rabbitTemplate; public static void senderDelayedOrderOutTradeNo(RabbitTemplate rabbitTemplate, String outTradeNo, Integer second) { rabbitTemplate.convertAndSend(RabbitMqConfig.EXCHANGE_NAME, RabbitMqConfig.DELAY_ORDER_ROUTING_KEY, outTradeNo, message -> { message.getMessageProperties().setHeader("x-delay", second.intValue()*1000); log.info("send message: " + outTradeNo); return message; }); } }
-
消费消息
@Component @Slf4j public class DelayedOrderReceiver { @RabbitListener(queues = RabbitMqConfig.DELAYED_QUEUE_NAME) @RabbitHandler public void process(String outTradeNo) { log.info("接受时间: " + new Date()); log.info("Fetch order id from rabbit mq: " + outTradeNo); //在这里可以加入业务逻辑,比如: 修改还支付订单的状态; } }
-
测试延迟队列
@Slf4j @RequestMapping("/api/rabbitmq") @RestController public class RabbitMqController { public static final int THIRD_SECOND = 3; @Autowired private RabbitTemplate rabbitTemplate; @PostMapping("/senderDelayedOrder") public void senderDelayedOrder() { log.info("发送时间: " + new Date()); RabbitMqSender.senderDelayedOrderOutTradeNo(rabbitTemplate, "订单号", THIRD_SECOND); } }
-
测试结果
发送时间: Mon Jan 28 18:48:18 CST 2019 Send message:订单号 接受时间: Mon Jan 28 18:48:21 CST 2019 Fetch message:订单号
通过测试结果可以看出,在消息发送3秒后,消费者如我们所期望的的接受到了消息。实践证明,我们的延迟队列已经成功实现。
四、总结及回顾
笔者通过本文主要给同学们介绍了什么是延迟对列,以及延迟队列的使用场景,同时也和同学们一起完成了RabbitMq延迟队列的实战演练。建议大家按照本文的步骤动手试一下,肯定会有新的收获。如果遇到不懂问题,可以联系笔者一起探讨。最后祝大家学习开心,生活愉快!