• 【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 消息被拒绝 .... 

     

     

     

  • 相关阅读:
    解剖SQLSERVER 第十二篇 OrcaMDF 行压缩支持(译)
    解剖SQLSERVER 第十三篇 Integers在行压缩和页压缩里的存储格式揭秘(译)
    解剖SQLSERVER 第十四篇 Vardecimals 存储格式揭秘(译)
    解剖SQLSERVER 第十五篇 SQLSERVER存储过程的源文本存放在哪里?(译)
    解剖SQLSERVER 第七篇 OrcaMDF 特性概述(译)
    解剖SQLSERVER 第八篇 OrcaMDF 现在支持多数据文件的数据库(译)
    解剖SQLSERVER 第九篇 OrcaMDF现在能通过系统DMVs显示元数据(译)
    解剖SQLSERVER 第十篇 OrcaMDF Studio 发布+ 特性重温(译)
    解剖SQLSERVER 第十一篇 对SQLSERVER的多个版本进行自动化测试(译)
    解剖SQLSERVER 第三篇 数据类型的实现(译)
  • 原文地址:https://www.cnblogs.com/mindzone/p/15388557.html
Copyright © 2020-2023  润新知