延时队列
-
意义
延时队列顾名思义,即放置在该队列里面的消息是不需要立即消费的,而是等待一段时间之后取出消费。 例如: 当你一个订单执行时,你未能即时的支付,需要在30分钟后自动判断是否支付成功,这时候就需要延时队列,简单来说就是一个可动态变化的定时器。当生成订单时就会触发的定时器
-
方式
死信交换器(DLX)
DLX是Dead-Letter-Exchange的缩写,全称死信交换器。成为死信队列后,可以被重新发送到另外一个交换器中,这个交换器就是DLX,绑定DLX到队列称为死信队列。注意,死信队列和死信交换器不一样哦。
成为死信一般由以下几种情况:
-
消息被拒绝 (basic.reject or basic.nack) 且带 requeue=false 参数
-
消息的TTL-存活时间已经过期
-
队列长度限制被超越(队列满)
DLX和一般的交换器没有区别,可以声明在任何的队列上,理解为队列的一个属性吧。当这个队列有死信时会根据设置自动的将死信重新发布到设置的DLX上进行消费。这个消费了死信的队列称之为死信队列,并不是绑定了DLX的队列是死信队列,大家要区分清楚。
通过在队列里设置 x-dead-letter-exchange 参数来声明DLX,
如果当前DLX是 direct 类型还要 声明 x-dead-letter-routing-key 参数来指定路由键,如果没有指定,则使用原队列的路由键。
过期时间(TTL)
TTL是Time To Live的缩写, 也就是生存时间。RabbitMq支持对消息和队列设置TTL,对消息这设置是在发送的时候指定,对队列设置是从消息入队列开始计算, 只要超过了队列的超时时间配置, 那么消息会自动清除。
如果两种方式一起使用消息对TTL和队列的TTL之间较小的为准,也就是消息5s过期,队列是10s,那么5s的生效。
默认是没有过期时间的,表示消息没有过期时间;如果设置为0,表示消息在投递到消费者的时候直接被消息,否则丢弃。
设置消息的过期时间用 x-message-ttl 参数实现,单位毫秒。
设置队列的过期时间用 x-expires 参数,单位毫秒,注意,不能设置为0
-
-
添加Maven依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-amqp</artifactId> </dependency>
-
application.yml( application.properties)
spring: rabbitmq: host: 127.0.0.1(本地安装就是本地ip,Linux就是虚拟机ip) port: 5672(一般都是这个) username: guest(默认的用户名) password: guest(默认密码)
简单测试
1.简单来说就是两个Queue,两个DirectExchange ,两个Binding. with后面的是路由
package com.example.rdemo.config; import lombok.extern.slf4j.Slf4j; import org.springframework.amqp.core.*; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.util.HashMap; import java.util.Map; @Configuration @Slf4j public class DelayRabbitConfig { /** * 延迟队列 TTL 名称 */ private static final String ORDER_DELAY_QUEUE = "user.order.delay.queue"; /** * DLX,dead letter发送到的 exchange * 延时消息就是发送到该交换机的 */ public static final String ORDER_DELAY_EXCHANGE = "user.order.delay.exchange"; /** * routing key 名称 * 具体消息发送在该 routingKey 的 */ public static final String ORDER_DELAY_ROUTING_KEY = "order_delay"; public static final String ORDER_QUEUE_NAME = "user.order.queue"; public static final String ORDER_EXCHANGE_NAME = "user.order.exchange"; public static final String ORDER_ROUTING_KEY = "order"; /** * 延迟队列配置 * <p> * 1、params.put("x-message-ttl", 5 * 1000); * 第一种方式是直接设置 Queue 延迟时间 但如果直接给队列设置过期时间,这种做法不是很灵活,(当然二者是兼容的,默认是时间小的优先) * 2、rabbitTemplate.convertAndSend(book, message -> { * message.getMessageProperties().setExpiration(2 * 1000 + ""); * return message; * }); * 第二种就是每次发送消息动态设置延迟时间,这样我们可以灵活控制 **/ /** * 普通队列 */ @Bean public Queue delayOrderQueue() { Map<String, Object> params = new HashMap<>(); // x-dead-letter-exchange 声明了队列里的死信转发到的DLX名称, params.put("x-dead-letter-exchange", ORDER_EXCHANGE_NAME); // x-dead-letter-routing-key 声明了这些死信在转发时携带的 routing-key 名称。 params.put("x-dead-letter-routing-key", ORDER_ROUTING_KEY); return new Queue(ORDER_DELAY_QUEUE, true, false, false, params); } @Bean public DirectExchange orderDelayExchange() { return new DirectExchange(ORDER_DELAY_EXCHANGE); } /** * 普通队列 */ @Bean public Binding dlxBinding() { return BindingBuilder.bind(delayOrderQueue()).to(orderDelayExchange()).with(ORDER_DELAY_ROUTING_KEY); } /** * 死信接收队列 * * @return */ @Bean public Queue orderQueue() { return new Queue(ORDER_QUEUE_NAME, true); } /** * 死信接收交换机 * * @return */ @Bean public TopicExchange orderTopicExchange() { return new TopicExchange(ORDER_EXCHANGE_NAME); } /** * 死信交换机绑定消费队列 * * @return */ @Bean public Binding orderBinding() { // TODO 如果要让延迟队列之间有关联,这里的 routingKey 和 绑定的交换机很关键 return BindingBuilder.bind(orderQueue()).to(orderTopicExchange()).with(ORDER_ROUTING_KEY); } }
-
实体类
package com.example.rdemo.pojo; import lombok.Data; import java.io.Serializable; @Data public class Order implements Serializable { private static final long serialVersionUID = -2221214252163879885L; private String orderId; // 订单id private Integer orderStatus; // 订单状态 0:未支付,1:已支付,2:订单已取消 private String orderName; // 订单名字 }
-
发送者
package com.example.rdemo.config; import com.example.rdemo.pojo.Order; import lombok.extern.slf4j.Slf4j; import org.springframework.amqp.core.AmqpTemplate; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import java.util.Date; @Component @Slf4j public class DelaySender { @Autowired private AmqpTemplate amqpTemplate; public void sendDelay(Order order) { log.info("【订单生成时间】" + new Date().toString() +"【1分钟后检查订单是否已经支付】" + order.toString() ); //第一个参数是前面DelayRabbitConfig的交换机名称 第二个参数的路由名称 第三个参数是传递的参数 第四个参数是配置属性 this.amqpTemplate.convertAndSend(DelayRabbitConfig.ORDER_DELAY_EXCHANGE, DelayRabbitConfig.ORDER_DELAY_ROUTING_KEY, order, message -> { // 如果配置了 params.put("x-message-ttl", 5 * 1000); 那么这一句也可以省略,具体根据业务需要是声明 Queue 的时候就指定好延迟时间还是在发送自己控制时间 message.getMessageProperties().setExpiration(1 * 1000 * 60 + ""); return message; }); } }
-
接收者
package com.example.rdemo.config; import com.example.rdemo.pojo.Order; import com.rabbitmq.client.Channel; import lombok.extern.slf4j.Slf4j; import org.springframework.amqp.core.Message; import org.springframework.amqp.rabbit.annotation.RabbitListener; import org.springframework.stereotype.Component; import java.util.Date; @Component @Slf4j public class DelayReceiver { //对应实现队列 @RabbitListener(queues = {DelayRabbitConfig.ORDER_QUEUE_NAME}) public void orderDelayQueue(Order order, Message message, Channel channel) { log.info("###########################################"); log.info("【orderDelayQueue 监听的消息】 - 【消费时间】 - [{}]- 【订单内容】 - [{}]", new Date(), order.toString()); if(order.getOrderStatus() == 0) { order.setOrderStatus(2); log.info("【该订单未支付,取消订单】" + order.toString()); } else if(order.getOrderStatus() == 1) { log.info("【该订单已完成支付】"); } else if(order.getOrderStatus() == 2) { log.info("【该订单已取消】"); } log.info("###########################################"); } }
-
测试
package com.example.rdemo.controller; import com.example.rdemo.config.DelaySender; import com.example.rdemo.pojo.Order; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import java.util.concurrent.DelayQueue; @RestController public class TestController { @Autowired private DelaySender delaySender; @GetMapping("/sendDelay") public Object sendDelay() { Order order1 = new Order(); order1.setOrderStatus(0); order1.setOrderId("123456"); order1.setOrderName("小米6"); Order order2 = new Order(); order2.setOrderStatus(1); order2.setOrderId("456789"); order2.setOrderName("小米8"); delaySender.sendDelay(order1); delaySender.sendDelay(order2); return "ok"; } }
6.运行
-
7.结果
一分钟后