RabbitMQ高级特性:延迟队列

103 阅读2分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 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(死信队列)来实现延迟队列的效果