开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第24天,点击查看活动详情
什么叫做延迟队列
延迟队列,即消息进入队列后不会立马被消费,只有到达指定的时间后,才会被消费
需求
1 下单后30分钟未支付,取消订单,回滚库存
2 新用户注册成功7天后,发送短信问候
实现方式
1 定时器(不建议)
2 延迟队列(建议)
很可惜,在rabbitMQ中并未提供延迟队列功能
但是可以使用:TTL(存活时间)+DLX(死信队列)组合实现延迟队列的效果
代码实现延迟队列(TTL+DLX)
1 定义正常的交换机(order_exchange)和队列(order_queue)
2 定义死信的交换机(order_exchange_dlx)和队列(order_queue_dlx)
3 绑定,设置正常的队列过期时间为30分钟
相关代码如下
生产者配置
<? xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:rabbit="http://www.springframework.org/schema/rabbit"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/rabbit
http://www.springframework.org/schema/rabbit/spring-rabbit.xsd" >
<!--加载配置文件-->
<context:property-placeholder location="classpath:application.properties" />
<!-- 定义rabbitmq connectionFactory -->
<rabbit:connection-factory id="connectionFactory" host="${rabbitmq.host}"
port="${rabbitmq.port}"
username="${rabbitmq.username}"
password="${rabbitmq.password}"
virtual-host="${rabbitmq.virtual-host}"
publisher-confirms="true"
publisher-returns="true"
/>
<!--
延迟队列
1 定义正常的交换机(order_exchange)和队列(order_queue)
2 定义死信的交换机(order_exchange_dlx)和队列(order_queue_dlx)
3 绑定,设置正常的队列过期时间为30分钟
-->
<!-- 1 定义正常的交换机(order_exchange)和队列(order_queue)-->
<rabbit:queue id="order_queue" name="order_queue" >
<!-- 3 绑定,设置正常的队列过期时间为30分钟-->
<rabbit:queue-arguments>
<!--死信队列名称-->
<entry key="x-dead-letter-exchange" value="order_exchange_dlx" ></entry>
<!--路由键-->
<entry key="x-dead-letter-routing-key" value="dlx.order.cancal" ></entry>
<!--过期时间-->
<entry key="x-message-ttl" value="10000" value-type="java.lang.Integer" ></entry>
</rabbit:queue-arguments>
</rabbit:queue>
<rabbit:topic-exchange id="order_exchange" name="order_exchange" >
<!--绑定队列-->
<rabbit:bindings>
<rabbit:binding pattern="order.#" queue="order_queue" ></rabbit:binding>
</rabbit:bindings>
</rabbit:topic-exchange>
<!-- 2 定义死信的交换机(order_exchange_dlx)和队列(order_queue_dlx)-->
<rabbit:queue id="order_queue_dlx" name="order_queue_dlx" ></rabbit:queue>
<rabbit:topic-exchange id="order_exchange_dlx" name="order_exchange_dlx" >
<!--绑定队列-->
<rabbit:bindings>
<rabbit:binding pattern="dlx.order.#" queue="order_queue_dlx" ></rabbit:binding>
</rabbit:bindings>
</rabbit:topic-exchange>
</beans>
生产者测试
package com.producer.test;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.amqp.AmqpException;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessagePostProcessor;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import java.util.concurrent.TimeUnit;
/**
* @prog ram: SpringBoot_RabbitMQ_Advanced
* @description: 测试确认模式消息是否发送成功
* @author: 魏一鹤
* @createDate: 2022-04-04 23:10
**/
//spring配置文件
@RunWith(SpringJUnit4ClassRunner.class)
//加载文件的路径
@ContextConfiguration(locations = "classpath:spring-rabbitmq-producer.xml" )
public class ProducerTest {
//注入 RabbitTemplate
@Autowired
private RabbitTemplate rabbitTemplate;
//延迟队列测试
@Test
public void testDelay() throws InterruptedException {
//发送订单消息 将来是在订单系统中,下单成功后发送的,现在只是模拟
rabbitTemplate.convertAndSend( "order_exchange" , "order.msg" , "订单信息 id=1,time=2022-4-10- 14:37:35" );
//打印倒计时10s 因为设置的消息存活就是10s
for (int i = 10; i >0; i--) {
System.out.println(i+ "...." );
TimeUnit.SECONDS.sleep(1);
}
}
}
消费者配置
<? xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:rabbit="http://www.springframework.org/schema/rabbit"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/rabbit
http://www.springframework.org/schema/rabbit/spring-rabbit.xsd" >
<!--加载配置文件-->
<context:property-placeholder location="classpath:application.properties" />
<!-- 定义rabbitmq connectionFactory -->
<rabbit:connection-factory id="connectionFactory" host="${rabbitmq.host}"
port="${rabbitmq.port}"
username="${rabbitmq.username}"
password="${rabbitmq.password}"
virtual-host="${rabbitmq.virtual-host}"
/>
<!--配置监听器bean 扫描这个包的路径 配置类需要加注解-->
<context:component-scan base-package="com.wyh.listener" />
<!--定义监听器 acknowledge="" 设置签收方式 -->
<rabbit:listener-container connection-factory="connectionFactory" acknowledge="manual" prefetch="5" >
<!--加载对应监听器的类 具体的类名和监听的队列名-->
<!-- <rabbit:listener ref="qasCKListener" queue-names="test_queue_confirm" />-->
<!--
<rabbit:listener ref="rabbitMQACKListener" queue-names="test_queue_confirm" />
-->
<!--监听正常队列 拒绝签收并不允许重返队列 成为死信队列-->
<!-- <rabbit:listener ref="DLXCKListener" queue-names="test_queue_dlx" />-->
<!--延迟队列监听(DDL+DLX) 一定要监听死信队列-->
<rabbit:listener ref="orderListener" queue-names="order_queue_dlx" />
</rabbit:listener-container>
</beans>
消费者测试
package com.wyh.listener;
import com.rabbitmq.client.Channel;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.listener.api.ChannelAwareMessageListener;
import org.springframework.stereotype.Component;
/**
* @program: SpringBoot_RabbitMQ_Advanced
* @description: RabbitMQ TTL+DLX延迟队列
* @author: 魏一鹤
* @createDate: 2022-04-06 20:30
**/
//包扫描注解 把bean加载到spring容器中
@Component
//实现MessageListener接口并重写onMessage方法
public class OrderListener implements ChannelAwareMessageListener {
@Override
public void onMessage(Message message, Channel channel) throws Exception {
long deliveryTag = message.getMessageProperties().getDeliveryTag();
try {
//1 接收并打印消费者收到(消费)的消息
System.out.println(new String(message.getBody()));
//2 处理业务逻辑
System.out.println( "处理业务逻辑......" );
System.out.println( "根据订单的id查询状态...." );
System.out.println( "判断状态是否为支付状态" );
System.out.println( "如果未支付,应该取消订单,回滚库存" );
channel.basicAck(deliveryTag,true);
} catch (Exception e) {
System.out.println( "出现异常,消费者拒绝签收!" );
//死信队列 拒绝签收requeue=false 将消息路由到死信队列中
channel.basicNack(deliveryTag,true,false);
}
}
}
生产者消息到期后会执行消费者业务
延迟队列小结
1 延迟队列 指消息进入队列后,可以被延迟一定时间,再进行消费
2 RabbitMQ没有提供延迟队列功能,但是可以使用TTL(消息存活时间)+DLX(死信队列)来实现延迟队列的效果