【RabbitMQ】11 深入部分P4 延迟队列
一、延迟队列:
消息经过交换机分配到队列上之后,在到达指定的时间,才会被消费?
需求:
1、下单之后的30分钟,用户未支付,订单取消,回滚库存
2、新用户注册7天后,发送短信慰问,或者是用户生日发送短信祝福
业务事件触发之后进入一个时间间隔,消息在这个间隔量的节点上再执行
Java程序提供了一些定时任务的框架,可以调用Java程序实现
或者是消息中间件自己做的延迟队列
是使用程序解决还是用消息队列解决,取决于现实业务的考量
二、RabbitMQ的技术方案:
RabbitMQ没有延迟队列这样的功能提供
可以使用 TTL + DLX 组合实现延迟队列的效果
三、RabbitMQ代码实现:
<?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:rabbitmq.properties"/> <!-- 定义rabbitmq connectionFactory publisher-confirms="true" 消息发送可确认 publisher-returns="true" --> <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" /> <!--定义管理交换机、队列--> <rabbit:admin connection-factory="connectionFactory"/> <!--定义rabbitTemplate对象操作可以在代码中方便发送消息--> <rabbit:template id="rabbitTemplate" connection-factory="connectionFactory"/> <!-- 延迟队列 1、定义正常的交换机和队列 2、定义死信的交换机和队列 3、绑定,并设置正常过期时间为30分钟 delay --> <!-- 正常 --> <rabbit:queue id="delay-regular-Q" name="delay-regular-Q"> <rabbit:queue-arguments> <!-- 绑定分配死信--> <entry key="x-dead-letter-exchange" value="delay-dead-X" /> <entry key="x-dead-letter-routing-key" value="dlx.order.cancel"/> <!-- TTL超时限定 --> <entry key="x-message-ttl" value="10000" value-type="java.lang.Integer" /> </rabbit:queue-arguments> </rabbit:queue> <rabbit:topic-exchange name="delay-regular-X" > <rabbit:bindings> <rabbit:binding pattern="order.#" queue="delay-regular-Q"/> </rabbit:bindings> </rabbit:topic-exchange> <!-- 死信 --> <rabbit:queue id="delay-dead-Q" name="delay-dead-Q" /> <rabbit:topic-exchange name="delay-dead-X" > <rabbit:bindings> <rabbit:binding pattern="dlx.order.#" queue="delay-dead-Q"/> </rabbit:bindings> </rabbit:topic-exchange> </beans>
测试类编写发送一条消息:
@Test public void delayTest() { // rabbitTemplate.convertAndSend( "delay-regular-X", "order.info", "延迟队列测试 。。。。 在死信队列查看此消息 "); }
消费者服务编写延迟队列的监听器
注意这个监听器是监听死信队列的
package cn.dzz.rabbitmq.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; import java.nio.charset.StandardCharsets; @Component public class DelayListener implements ChannelAwareMessageListener { @Override public void onMessage(Message message, Channel channel) throws Exception { long deliveryTag = 0; try { System.out.println(new String(message.getBody(), StandardCharsets.UTF_8)); // 处理业务逻辑 // todo... /** * * 根据接收的ID查询订单状态 * 判断状态是否为支付成功 * 判断状态是否为支付成功 * 取消订单, 回滚库存 * */ int i = 10 / 0; // 这个异常会被捕获,然后触发RabbitMQ一直让消息重新入列发送 // 业务签收 deliveryTag = message.getMessageProperties().getDeliveryTag(); channel.basicAck(deliveryTag, true); } catch (Exception exception) { // exception.printStackTrace(); System.out.println("已经触发异常Catch 消息被拒绝 .... "); channel.basicNack(deliveryTag, true, false); // 被拒绝的消息不再重回队列, 这样这个消息才会分配到死信队列中 } } }
监听器XML配置:
<?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:rabbitmq.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}"/> <context:component-scan base-package="cn.dzz.rabbitmq.listener" /> <!-- 定义监听器容器 acknowledge="manual" 默认就是none --> <rabbit:listener-container connection-factory="connectionFactory" auto-declare="true" acknowledge="manual"> <rabbit:listener ref="delayListener" queue-names="delay-dead-Q" /> </rabbit:listener-container> </beans>
消费者服务经过延迟后收到消息:
监听测试
延迟队列测试 。。。。 在死信队列查看此消息
已经触发异常Catch 消息被拒绝 ....