背景
延迟消息的意思就是当我们的消息发送以后,并不希望马上被消费,需要隔一段时间后才能被消费。为什么会用到这个延迟消息?比如我们常见的下单场景:
我们订单下单之后,一般会有15分钟的支付时间,如果超时未支付,系统自动取消订单,并释放占用的库存。
要实现这个可以做一个定时任务轮训数据库,但是太消耗内存了,这就需要用到我们的延迟队列。
RabbitMQ延迟消息队列
实现原理
RabbitMQ本身没有实现延迟消息,但是我们可以通过他的TTL(Time To Live)和死信(Dead Letter Exchanges)来模拟延迟队列。
-
TTL(Time To Live)——消息的存活时间(其实就是消息在队列里面没有被消费,存活的时间)
-
Dead Letter Exchanges ——死信路由(在某一个设置Dead Letter Exchange的队列中有消息过期了,会自动触发消息的转发,发送到Dead Letter Exchange中去)
结合二者,我们就可以实现一个延迟队列。如图所示:
- 我们创建了一个交换机order-event-exchange,其实就是普通的交换机,用于转发消息。
- 创建了一个延迟队列order.delay.queue,设置了消息在队列里面存活30min,并设置了死信路由x-dead-letter-exchange到order-event-exchange、设置死信路由键order.release.order。(注意这个队列永远不会被消费)
- 创建了订单释放队列order.release.order.queue,消息在order.delay.queue里面存活30分钟以后,将根据死信路由经交换机order-event-exchange路由到这里,进行消费。
代码实现
-
创建交换机order-event-exchange(使用给容器中加组件的方式创建交换机及队列)
/** * TopicExchange * * @return */ @Bean public Exchange orderEventExchange() { /* * String name, * boolean durable, * boolean autoDelete, * Map<String, Object> arguments * */ return new TopicExchange("order-event-exchange", true, false); }
-
创建延迟队列order.delay.queue
/** * 死信队列 * * @return */ @Bean public Queue orderDelayQueue() { /* Queue(String name, 队列名字 boolean durable, 是否持久化 boolean exclusive, 是否排他 boolean autoDelete, 是否自动删除 Map<String, Object> arguments) 属性 */ HashMap<String, Object> arguments = new HashMap<>(); arguments.put("x-dead-letter-exchange", "order-event-exchange"); arguments.put("x-dead-letter-routing-key", "order.release.order"); arguments.put("x-message-ttl", 60000); // 消息过期时间 1分钟 Queue queue = new Queue("order.delay.queue", true, false, false, arguments); return queue; }
-
创建订单释放队列order.release.order.queue
/** * 普通队列 * * @return */ @Bean public Queue orderReleaseQueue() { Queue queue = new Queue("order.release.order.queue", true, false, false); return queue; }
-
接下来进行绑定,延迟队列与交换机进行bind(key:order.create.order),订单释放队列与交换机进行bind(key:order.release.order)。
@Bean public Binding orderCreateBinding() { /* * String destination, 目的地(队列名或者交换机名字) * DestinationType destinationType, 目的地类型(Queue、Exhcange) * */ return new Binding("order.delay.queue", Binding.DestinationType.QUEUE, "order-event-exchange", "order.create.order", null); } @Bean public Binding orderReleaseBinding() { return new Binding("order.release.order.queue", Binding.DestinationType.QUEUE, "order-event-exchange", "order.release.order", null); }
-
至此,准备工作已完成,现在我们定义好发送消息和接受消息的方法。
/** * 发送消息 */ @ResponseBody @GetMapping(value = "/test/createOrder") public String createOrderTest() { //订单下单成功 OrderEntity orderEntity = new OrderEntity(); orderEntity.setOrderSn(UUID.randomUUID().toString()); orderEntity.setModifyTime(new Date()); //给MQ发送消息 rabbitTemplate.convertAndSend("order-event-exchange","order.create.order",orderEntity); return "ok"; }
/** * 接受消息 * queues:声明需要监听的队列 * channel:当前传输数据的通道 */ @RabbitListener(queues = {"order.release.stock.queue"}) public void revieveMessage(Message message, OrderReturnReasonEntity content, Channel channel) throws IOException { //拿到主体内容 byte[] body = message.getBody(); //拿到的消息头属性信息 MessageProperties messageProperties = message.getMessageProperties(); System.out.println("接受到的消息...内容" + message + "===内容:" + content); // 消息抵达,可靠确认机制 long deliveryTag = messageProperties.getDeliveryTag(); channel.basicAck(deliveryTag, false); }
效果展示
可以看到消息发送以后,在我们延迟队列里面一直没有被消费
一分钟以后,由延迟队列转发到我们订单释放队列,并且马上被消费,至此大功告成!