RabbitMQ 高级特性——过期时间加死信队列实现延迟队列

目录

1.过期时间 TTL 

2.死信队列/死信交换机

2.1过期消息

2.2 消息数量达到最大限制

2.3 消费者拒收消息

3.延迟队列(重点)


1.过期时间 TTL 

TTL:time to live(存活时间/过期时间)

当消息到达存活时间后,还没有被消费,就会被自动清除;

过期时间可以对整队列设置,也可以对消息单独进行设置(如果同时设置了队列和消息的过期时间,以时间短的为准,队列过期时间到了之后,会将消息都移除 消息到达过期时间之后,只有在队列顶端的消息,才会判断是否需要移除(提高时间过期的效率))

配置:

队列的过期时间设为15s

<?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}"
                               publisher-confirms="true"
                               publisher-returns="true" />
    <!--定义管理交换机、队列-->
    <rabbit:admin connection-factory="connectionFactory"/>
    <!--定义rabbitTemple对象-->
    <rabbit:template id="rabbitTemplate" connection-factory="connectionFactory"/>

    <!--ttl-->
    <rabbit:queue id="spring_queue_ttl" name="spring_queue_ttl" auto-delete="true">
    <!--设置queue的参数-->
        <rabbit:queue-arguments>
            <entry key="x-message-ttl" value="15000" value-type="java.lang.Integer"/>
        </rabbit:queue-arguments>
    </rabbit:queue>
    <rabbit:topic-exchange name="spring_ttl_exchange" id="spring_ttl_exchange" >
        <rabbit:bindings>
            <rabbit:binding pattern="ttl.#" queue="spring_queue_ttl"/>
        </rabbit:bindings>
    </rabbit:topic-exchange>

</beans>

生产者代码:

将消息的过期时间设为5s

 /**
     * ttl 过期时间
     *      1.队列统一过期
     *      2.消息单独过期
     *
     *      如果同时设置了队列和消息的过期时间,以时间短的为准,队列过期时间到了之后,会将消息都移除
     *
     *      消息到达过其实是那之后,只有在队列顶端的消息,才会判断是否需要移除(提高时间过期的效率)
     */
@Test
    public void testTtl(){
        /*for (int i = 0; i < 10; i++) {
            rabbitTemplate.convertAndSend("spring_ttl_exchange", "ttl.test", "message ttl test ");
        }*/

        MessagePostProcessor messagePostProcessor = new MessagePostProcessor() {
            //消息的后置处理器,设置消息的一些参数信息
            @Override
            public Message postProcessMessage(Message message) throws AmqpException {
                message.getMessageProperties().setExpiration("5000");
                return message;
            }
        };
    
        
        for (int i = 0; i < 10; i++) {
            if(i==5) {
                rabbitTemplate.convertAndSend("spring_ttl_exchange", "ttl.test", "message ttl test",
                    messagePostProcessor);
            }else{
                rabbitTemplate.convertAndSend("spring_ttl_exchange", "ttl.test", "message ttl test ");
            }

        }  });
        }
    }

运行结果为:

  1. 如果消息不设置过期时间,消息会在队列中待15s;
  2. 如果消息设置了过期时间,消息在队列中待了5s后,就被移除了;
  3. 如果对单个消息设置了过期时间,只有当消息到达过期时间之后,且该消息在队列顶端的消息,才会被移除

2.死信队列/死信交换机

英文Dead Letter Exchange,当消息成为死信消息后,可以被重新发送到另一个交换机,这个交换机就是DLX。

消息成为死信有三种种方式:

  1. 消息到达过期时间 TTL;
  2. 消息数量达到最大限制;
  3. 消费者拒收消息,使用channel.basicNack/basicReject,并且不把消息重新放回原队列,即requeue=false.

我们来实践一下:

生产端队列以及交换机的配置

<?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}"
                               publisher-confirms="true"
                               publisher-returns="true" />
    <!--定义管理交换机、队列-->
    <rabbit:admin connection-factory="connectionFactory"/>
    <!--定义rabbitTemple对象-->
    <rabbit:template id="rabbitTemplate" connection-factory="connectionFactory"/>

    <!--死信队列 -->
    <!--1声明正常的队列 spring_queue_dlx 和交换机 spring_exchange_dlx-->
    <!--2声明死信队列queue_dlx和死信交换机exchange_dlx-->
    <!--3正常队列绑定死信交换机
      设置两个参数:x-dead-letter-exchange:死信交换机名称
                x-dead-letter-routing-key:发送给死信交换机的路由key
    -->
    <!--4消息如何成为死信?   ①消息过期 ②消息数量到达最大限度③消费者拒收消息-->
    <!--1-->
    <rabbit:queue id="spring_queue_dlx" name="spring_queue_dlx" auto-delete="true">
        <!--3-->
        <rabbit:queue-arguments>
            <!--3.1 设置x-dead-letter-exchange-->
            <entry key="x-dead-letter-exchange" value="exchange_dlx"></entry>
            <!--3.1 设置x-dead-letter-routing-key-->
            <entry key="x-dead-letter-routing-key" value="dlx.test"></entry>

            <!--4.1设置消息的过期时间-->
            <entry key="x-message-ttl" value="15000" value-type="java.lang.Integer"/>
            <!--4.2设置队列的长度-->
            <entry key="x-max-length" value="10" value-type="java.lang.Integer"/>
        </rabbit:queue-arguments>
    </rabbit:queue>
    <rabbit:topic-exchange name="spring_exchange_dlx" id="spring_exchange_dlx" >
        <rabbit:bindings>
            <rabbit:binding queue="spring_queue_dlx" pattern="test.dlx.#"></rabbit:binding>
        </rabbit:bindings>
    </rabbit:topic-exchange>
    <!--2-->
    <rabbit:queue id="queue_dlx" name="queue_dlx" auto-delete="true"/>
    <rabbit:topic-exchange name="exchange_dlx" id="exchange_dlx" >
        <rabbit:bindings>
            <rabbit:binding queue="queue_dlx" pattern="dlx.#"></rabbit:binding>
        </rabbit:bindings>
    </rabbit:topic-exchange>
</beans>

测试:

2.1过期消息

消息的过期时间我们设置了15s

<!--4.1设置消息的过期时间-->
<entry key="x-message-ttl" value="15000" value-type="java.lang.Integer"/>

@Test
    public void testDlx(){
        //1.测试过期消息
        rabbitTemplate.convertAndSend("spring_exchange_dlx", "test.dlx.test", "message dlx test ");
    }

发送消息后,可见,正常队列spring_queue_dlx接收到一条消息,死信队列queue_dlx暂时无数据 

15s后,正常队列里的消息达到过期时间,消息被发送到死信交换机:

2.2 消息数量达到最大限制

正常的消息队列最大数量我们设置了10条

<!--4.2设置队列的长度-->
<entry key="x-max-length" value="10" value-type="java.lang.Integer"/>

 @Test
    public void testDlx(){
        //2.测试消息最大数量
        for (int i = 0; i < 20; i++) {
            rabbitTemplate.convertAndSend("spring_exchange_dlx", "test.dlx.test", "message dlx test ");
        }
    }

发送消息后,15s内正常队列里面应该有10条数据,死信队列里面应该有11条数据(2.1中的测试导致死信队列中已有一条数据)

15s后,正常队列里没有消息,死信队列中应该有21条数据:

2.3 消费者拒收消息

消费者配置:

<?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}"/>

    <bean id = "DlxListener" class="com.cjian.rabbitmq.spring_rabbit.DlxListener"/>
    <!--绑定监听器与队列的关系-->
    <rabbit:listener-container connection-factory="connectionFactory" auto-declare="true" acknowledge="manual" prefetch="5">
        <rabbit:listener ref="DlxListener" queue-names="spring_queue_dlx"/>
    </rabbit:listener-container>

</beans>

消费者代码:

package com.cjian.rabbitmq.spring_rabbit;

import com.rabbitmq.client.Channel;

import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.listener.api.ChannelAwareMessageListener;

import java.util.Date;

/**
 * @description: 消费者拒收消息,让消息进入死信队列
 *
 * @author: CJ
 * @time: 2021/1/27 10:05
 */
public class DlxListener implements ChannelAwareMessageListener {
    @Override
    public void onMessage(Message message, Channel channel) throws Exception {
        long deliveryTag = message.getMessageProperties().getDeliveryTag();
        try {
            //接收消息
            System.out.println(new String(message.getBody()));
            //处理业务逻辑
            //Thread.sleep(1000);
            //手动签收
            channel.basicAck(deliveryTag,true);
        } catch (Exception e) {
            System.out.println("消费消息出现异常,拒绝接收");
            //拒绝签收
            //requeue参数的意思为是否重回队列
            channel.basicNack(deliveryTag,true,false);
            //使用这个也可以
            //channel.basicReject(deliveryTag,false);
            //或者这个
            //channel.basicRecover(false);
        }
    }
}

我们让生产者发送一条消息后,消费者消费出现异常,消息被移到死信队列

 

3.延迟队列(重点)

消息进入队列后不会立即被消费,只有达到指定时间后才会被消费

 

很可惜,rabbitmq并没有提供延迟队列的功能,但是经过上面的学习,我们可以利用TTL+DLX去实现

测试:

生产者配置:

<?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}"
                               publisher-confirms="true"
                               publisher-returns="true" />
    <!--定义管理交换机、队列-->
    <rabbit:admin connection-factory="connectionFactory"/>
    <!--定义rabbitTemple对象-->
    <rabbit:template id="rabbitTemplate" connection-factory="connectionFactory"/>

    <!--延迟队列
    1.定义正常的交换机order_exchange和队列 order_queue
    2.定义死信交换机order_exchange_dlx和队列order_queue_dlx
    3.绑定,设置正常队列的过期时间
    -->
    <!--1.定义正常的交换机order_exchange和队列 order_queue-->
    <rabbit:queue id="order_queue" name ="order_queue">
        <!--3.绑定,设置正常队列的过期时间-->
        <rabbit:queue-arguments>
            <entry key="x-dead-letter-exchange" value="order_exchange_dlx"></entry>
            <entry key="x-dead-letter-routing-key" value="order.dlx.cancel"></entry>
            <entry key="x-message-ttl" value="15000" value-type="java.lang.Integer"/>
        </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="order.dlx.#" queue="order_queue_dlx"></rabbit:binding>
        </rabbit:bindings>
    </rabbit:topic-exchange>
</beans>

生产者代码:

 @Test
    public void testDelay(){
        rabbitTemplate.convertAndSend("order_exchange", "order.msg", "delay message test ");
        System.out.println("消息发送成功:"+new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
    }

 消费者配置:

<?xml version="1.0" encoding="UTF-8"?>
<!--
  ~ Copyright (c) Huawei Technologies Co., Ltd. 2019-2021. All rights reserved.
  -->

<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}"/>

    <bean id = "delayListener" class="com.cjian.rabbitmq.spring_rabbit.DelayListener"/>
    <!--绑定监听器与队列的关系-->
    <rabbit:listener-container connection-factory="connectionFactory" auto-declare="true" acknowledge="manual" prefetch="5">
        <rabbit:listener ref="delayListener" queue-names="order_queue_dlx"/>
       
    </rabbit:listener-container>

</beans>

消费者代码:

package com.cjian.rabbitmq.spring_rabbit;

import com.rabbitmq.client.Channel;

import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.listener.api.ChannelAwareMessageListener;

import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * @description: 延迟队列测试
 *
 * @author: CJ
 * @time: 2021/1/27 11:00
 */
public class DelayListener implements ChannelAwareMessageListener {
    @Override
    public void onMessage(Message message, Channel channel) throws Exception {
        long deliveryTag = message.getMessageProperties().getDeliveryTag();
        try {
            //接收消息
            System.out.println("消息接收成功:"+new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
            System.out.println(new String(message.getBody()));
            //手动签收
            channel.basicAck(deliveryTag,true);
        } catch (Exception e) {
            System.out.println("消费消息出现异常,拒绝接收");
            //拒绝签收
            //requeue参数的意思为是否重回队列
            channel.basicNack(deliveryTag,true,true);
        }
    }
}

先让队列和交换机都创建成功(可以启动生产者,不发送任何消息),然后我们启动消费者,以便后面观察:

这里order_queue_dlx死信队列已被消费者绑定:

生产者发送消息:

消息首先进入正常的队列,15s后进入死信队列,然后立即被消费:

消费者消费日志:

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值