Springboot+rabbitmq延时队列实现方式
1.什么是延时队列,延时队列
延时队列顾名思义,即放置在该队列里面的消息是不需要立即消费的,而是等待一段时间之后取出消费。
那么,为什么需要延迟消费呢?我们来看以下的场景
-
网上商城下订单后30分钟后没有完成支付,取消订单(如:淘宝、去哪儿网)
-
系统创建了预约之后,需要在预约时间到达前一小时提醒被预约的双方参会
-
系统中的业务失败之后,需要重试
## 2. SpringBoot整合RabbitMQ具体实现
1. 在 `pom.xml` 中添加 项目所需的依赖
````xml
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
````
2. 在appliaction.yml文件中配置rabbitmq相关内容
spring:
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest
具体编码实现
-
配置队列
package com.lzc.rabbitmq.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); } /** * 需要将一个队列绑定到交换机上,要求该消息与一个特定的路由键完全匹配。 * 这是一个完整的匹配。如果一个队列绑定到该交换机上要求路由键 “dog”,则只有被标记为“dog”的消息才被转发, * 不会转发dog.puppy,也不会转发dog.guard,只会转发dog。 * @return DirectExchange */ @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); } @Bean public Queue orderQueue() { return new Queue(ORDER_QUEUE_NAME, true); } /** * 将路由键和某模式进行匹配。此时队列需要绑定要一个模式上。 * 符号“#”匹配一个或多个词,符号“*”匹配不多不少一个词。因此“audit.#”能够匹配到“audit.irs.corporate”,但是“audit.*” 只会匹配到“audit.irs”。 **/ @Bean public TopicExchange orderTopicExchange() { return new TopicExchange(ORDER_EXCHANGE_NAME); } @Bean public Binding orderBinding() { // TODO 如果要让延迟队列之间有关联,这里的 routingKey 和 绑定的交换机很关键 return Bindi ngBuilder.bind(orderQueue()).to(orderTopicExchange()).with(ORDER_ROUTING_KEY); }
-
创建一个Order实体类
package com.lzc.rabbitmq.dataobject; 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.lzc.rabbitmq.config; import com.lzc.rabbitmq.dataobject.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.lzc.rabbitmq.config; import com.lzc.rabbitmq.dataobject.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() ); 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; }); } }
-
测试,访问http://localhost:8080/sendDelay,查看日志输出
package com.lzc.rabbitmq.controller;
import com.lzc.rabbitmq.config.DelaySender;
import com.lzc.rabbitmq.config.Sender;
import com.lzc.rabbitmq.dataobject.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";
}
-
日志输出
【订单生成时间】Mon Jun 18 11:55:36 CST 2018【1分钟后检查订单是否已经支付】Order(orderId=123456, orderStatus=0, orderName=小米6) 【订单生成时间】Mon Jun 18 11:55:36 CST 2018【1分钟后检查订单是否已经支付】Order(orderId=456789, orderStatus=1, orderName=小米8) ########################################### 【orderDelayQueue 监听的消息】 - 【消费时间】 - [Mon Jun 18 11:56:36 CST 2018]- 【订单内容】 - [Order(orderId=123456, orderStatus=0, orderName=小米6)] 【该订单未支付,取消订单】Order(orderId=123456, orderStatus=2, orderName=小米6) ########################################### ########################################### 【orderDelayQueue 监听的消息】 - 【消费时间】 - [Mon Jun 18 11:56:36 CST 2018]- 【订单内容】 - [Order(orderId=456789, orderStatus=1, orderName=小米8)] 【该订单已完成支付】 ###########################################