介绍
延迟队列中的消息,指的是消息发送成功之后,并不想立马被消费,而是等待一段时间后再消费
常见场景
1、手机购物,下单但未支付的订单,在半小时后自动取消订单
2、12306购票,下单但未支付的订单,会取消占座和订单
3、小程序排队取号,15分钟后不确认排队,号就归属于他人...
实现思路
可以使用rabbitmq_delayed_message_exchange插件来实现
主要思路和死信队列差不多,这个消息是基于插件把消息存放在延时交换机中
1、生产者将消息和路由键发送到指定的延时交换机上
2、延时交换机存放消息,等待消息到期根据路由键找到绑定自己的队列,并把消息发送出去
3、队列再把消息发送给监听它的消费者
四个角色三个过程
安装插件
1、下载插件
https://github.com/rabbitmq/rabbitmq-delayed-message-exchange/releases
2、放置到指定目录
把下载好的插件放到rabbitmq-server的安装路径下的plugins目录下3、启动并加载插件
#查看插件集 rabbitmq-plugins list rabbitmq-plugins enable rabbitmq_delayed_message_exchange
4、重启rabbitmq服务
systemctl restart rabbitmq-server
5、验证是否安装成功
进入RabbitMQ控制台,查看Exchange,如图说明插件安装成功
代码演示
1、添加依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-amqp</artifactId> </dependency>
2、添加配置信息
spring.application.name=springboot_rabbitmq spring.rabbitmq.host=127.0.0.1 spring.rabbitmq.virtual-host=/ spring.rabbitmq.username=guest spring.rabbitmq.password=guest spring.rabbitmq.port=5672
3、编写mq延迟配置类
@Configuration public class RabbitMqDelayedConfig { @Bean public Queue delayedQueue() { Queue queue = new Queue("queue.delayed", false, false, false, null); return queue; } /** * 声明延迟交换机 */ @Bean public Exchange delayedExchange() { Map<String, Object> props = new HashMap<>(); //采用广播模式,做类似闹钟提醒 props.put("x-delayed-type", ExchangeTypes.FANOUT); //声明延迟队列类型的交换机 Exchange exchange = new CustomExchange("exchange.delayed", "x-delayed-message", true, false, props); return exchange; } @Bean public Binding delayedBinding() { return BindingBuilder.bind(delayedQueue()).to(delayedExchange()).with("key.delayed").noargs(); } }
4、编写延迟队列监听类
@Component public class DelayedListener { @RabbitListener(queues = "queue.delayed") public void broadcastAlarm(Message message, Channel channel) throws IOException { System.out.println(new String(message.getBody(), "utf-8")); System.out.println("================" + new Date() + "===================="); channel.basicAck(message.getMessageProperties().getDeliveryTag(), false); } }
5、编写接口发送消息
@RestController public class DelayedController { @Autowired private AmqpTemplate amqpTemplate; @RequestMapping("/delayed/{seconds}") public String toMeeting(@PathVariable Integer seconds) throws UnsupportedEncodingException { // RabbitMQ只会检查队列头部的消息是否过期,如果过期就放到死信队列 // 假如第一个过期时间很长,10s,第二个消息3s,则系统先看第一个消息,等到第一个消息过期,放到DLX死信队列 // 此时才会检查第二个消息,但实际上此时第二个消息早已经过期了,但是并没 有先于第一个消息放到DLX死信队列。 // 插件rabbitmq_delayed_message_exchange会帮我们解决这个问题 MessageProperties properties = new MessageProperties(); properties.setHeader("x-delay", seconds * 1000); Message message = new Message((seconds + "秒后大门口集合!").getBytes("utf-8"), properties); // 如果不设置message的properties,也可以使用下述方法设置x-delay属性的值 // amqpTemplate.convertAndSend("exchange.delayed", "key.delayed", message, msg -> { // //使用定制的属性x-delay设置过期时间,也就是提前5s提醒,当消息转换完,设置消息头字段 // msg.getMessageProperties().setHeader("x-delay", seconds * 1000); // return msg; // }); amqpTemplate.convertAndSend("exchange.delayed", "key.delayed", message); System.out.println("================" + new Date() + "===================="); return "已经通知大家了"; } }
6、请求接口并验证是否延迟
请求接口:http://localhost:8080/delayed/5
五秒的延迟消费,然后查看一下控制台日志打印
两次时间打印相差5s,延迟队列生效,集成完毕