RabbitMQ 学习(三)——RabbitMQ的高级特性

本文详细介绍了RabbitMQ的高级特性,包括生产者消息传递的确认模式(确认模式和退回模式),确保消息的可靠性;消费端的消息确认机制,通过手动ACK实现限流;队列TTL(Time To Live)设置,以及死信队列的配置和应用,展示了如何通过TTL和死信队列实现延迟队列的功能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


前言

记录我学习的 RabbitMQ 的高级特性。包括:生产端 和 消费端的消息可靠性、死信队列、延迟队列 和 TTL。

提示:以下是本篇文章正文内容,下面案例可供参考

一、生产者可靠性

1 生产者消息传递的可靠性

在使用 RabbitM 的时候,作为消息的生产者(发送者),我们希望杜绝任何情况下的消息丢失投递失败。对此, RabbitMQ 提供了两种方法来确保投递过程的正确性。

  1. confirm 确认模式
  2. return 退回模式

1.1 模式介绍

RabbitMQ 的整个消息投递的过程可以划分为下面的步骤:
producer --> rabbitmq broker --> exchange --> queue --> consumer

  • 确认模式:消息从产生(producer)到 exchange(交换机)这一部分,若是出现问题,会返回一个 confirmCallBack;
  • 退回模式:消息从 exchange(交换机)到 queue(队列)这一部分,消息投递失败,会返回一个 returnCallBack。

我们就是 通过 这两个 Callback 确保消息投递的可靠性。

1.2 确认模式

  1. 确认模式开启:ConnectionFactory 中开启 publisher-confirms=“true”
  2. 在 rabbitTemplate 定义 ConfirmCallBack 回调函数.
  3. 发送消息,若是在消息投递到 exchange 之前出错,我们的回调函数的逻辑就会执行,进行进一步处理。
/**
* 确认模式:
* 步骤:
* 1. 确认模式开启:ConnectionFactory中开启publisher-confirms="true"
* 2. 在rabbitTemplate定义ConfirmCallBack回调函数
*/
// 定义回调函数
rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
    		/**
            *
            * @param correlationData 相关配置信息
            * @param ack exchange交换机 是否成功收到了消息。true 成功,false代表失败
            * @param cause 失败原因
            */
            public void confirm(CorrelationData correlationData, boolean ack, String cause) {
                System.out.println("confirm方法被执行了....");
                if (ack) {
                    //接收成功
                    System.out.println("接收成功消息" + cause);
                } else {
                    //接收失败
                    System.out.println("接收失败消息" + cause);
                    //做一些处理,让消息再次发送。
                }

            }
        });

1.2.1 示例代码

通过 spring 整合 RabbitMQ 做示范。

1.2.2 pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>rabbitmq-return-firm</artifactId>
    <version>1.0-SNAPSHOT</version>

    <!-- 引入依赖 -->
    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.1.7.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.amqp</groupId>
            <artifactId>spring-rabbit</artifactId>
            <version>2.1.8.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>5.1.7.RELEASE</version>
        </dependency>

    </dependencies>

</project>	

1.2.3 RabbitMQ 配置

rabbitmq.host=192.168.191.130
rabbitmq.port=5672
rabbitmq.username=admin
rabbitmq.password=admin
rabbitmq.virtual-host=/xzk

1.2.4 spring 整合配置文件

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

    <!-- 引入 rabbitmq 的配置文件 -->
    <context:property-placeholder location="classpath:rabbitmq/rabbitmq.properties"/>

    <!-- 定义 RabbitMQ 连接工厂 -->
    <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"/>

    <rabbit:queue id="test_confirm_queue" name="test_confirm_queue" auto-declare="true"></rabbit:queue>
    <rabbit:direct-exchange name="test_confirm_exchange" auto-declare="true">
        <rabbit:bindings>
            <rabbit:binding queue="test_confirm_queue" key="item.confirm"></rabbit:binding>
        </rabbit:bindings>
    </rabbit:direct-exchange>

    <!-- 定义 RabbitTemplate 对象,方便代码中操作数据 -->
    <rabbit:template id="rabbitTemplate" connection-factory="connectionFactory"/>
</beans>

1.2.5 测试类

package com.kaikeba.confirm;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.amqp.core.Message;
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;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:spring/spring-rabbitmq.xml")
public class RabbitConfirmTest {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Test
    public void testConfirm() throws InterruptedException {
        rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
            public void confirm(CorrelationData correlationData, boolean ack, String cause) {
                System.out.println("confirm方法被执行了....");
                if (ack) {
                    //接收成功
                    System.out.println("接收成功消息" + cause);
                } else {
                    //接收失败
                    System.out.println("接收失败消息" + cause);
                    //做一些处理,让消息再次发送。
                }

            }
        });

        rabbitTemplate.convertAndSend("test_confirm_exchange", "item.confirm", "whovwhuhuwf ");
        //
        Thread.sleep(2000);
    }
}

1.2.6 测试结果

● 正常传递
在这里插入图片描述
● 我们把交换机改错,然后再传递
在这里插入图片描述

1.3 退回模式

当消息发送给Exchange后,Exchange路由到Queue失败时才会执行 ReturnCallBack

  1. 开启回退模式:publisher-returns=“true”;
  2. 设置ReturnCallBack,复写对应方法;
  3. 设置Exchange处理消息失败的模式:setMandatory。
/**
* 回退模式: 当消息发送给Exchange后,Exchange路由到Queue失败是 才会执行 ReturnCallBack
* 步骤:
* 1. 开启回退模式:publisher-returns="true"
* 2. 设置ReturnCallBack
* 3. 设置Exchange处理消息失败的模式:setMandatory
* 1. 如果消息没有路由到Queue,则丢弃消息(默认)
* 2. 如果消息没有路由到Queue,返回给消息发送方ReturnCallBack
*/
  @Test
    public void testReturn() {
        // 设置交换机处理失败消息的模式
        rabbitTemplate.setMandatory(true);

        // 设置 ReturnCallBack
        rabbitTemplate.setReturnCallback(new RabbitTemplate.ReturnCallback() {
            /**
             *
             * @param message 消息对象
             * @param replyCode 错误码
             * @param replyText 错误信息
             * @param exchange 交换机
             * @param routingKey 路由键
             */
            public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
                System.out.println("return 执行了....");
                System.out.println(message);
                System.out.println(replyCode);
                System.out.println(replyText);
                System.out.println(exchange);
                System.out.println(routingKey);
            }
        });

        //3. 发送消息
        rabbitTemplate.convertAndSend("test_confirm_exchange", "item.confirm",
                "message returnback....");
    }

1.3.1 测试类

其他的东西和确认模式的一样

package com.kaikeba.confirm;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.amqp.core.Message;
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;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:spring/spring-rabbitmq.xml")
public class RabbitConfirmTest {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Test
    public void testReturn() {
        // 设置交换机处理失败消息的模式
        rabbitTemplate.setMandatory(true);

        // 设置 ReturnCallBack
        rabbitTemplate.setReturnCallback(new RabbitTemplate.ReturnCallback() {
            /**
             *
             * @param message 消息对象
             * @param replyCode 错误码
             * @param replyText 错误信息
             * @param exchange 交换机
             * @param routingKey 路由键
             */
            public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
                System.out.println("return 执行了....");
                System.out.println("message =" + message);
                System.out.println("replyCode = " + replyCode);
                System.out.println("replyText = " + replyText);
                System.out.println("exchange = " + exchange);
                System.out.println("routingKey = " + routingKey);
            }
        });

        //3. 发送消息
        rabbitTemplate.convertAndSend("test_confirm_exchange", "item.confi--rm",
                "message returnback....");
    }
}

1.3.2 测试结果

请添加图片描述

二、消费端消息传递的可靠性

2.1 Consume Ack

ack 指的是 Acknowledge 确认;表示消费端收到消息后的确认方式,这种方式有三种:

  • 自动确认:acknowledge=“none”;
  • 手动确认:acknowledge=“manual”;
  • 根据异常情况确认:acknowledge=“auto”;这种方式比较麻烦,不常用。

其中,自动确认是指当消息一旦被消费者 Consumer 接收到,就自动确认收到,并将相应的消息 message 从 RabbitMQ 的消息缓存中移除;但是在实际业务中,消息接收到之后,很有可能业务处理出现异常,那么代码中的消息处理失败,队列中的消息已经被移除,就会造成消息丢失。此时,我们就需要设置手动确认的逻辑;在业务处理成功之后,调用 channel.basicAck() 手动签收,如果出现异常。则调用 channel.basicNack() ,让消息自动重发。

Consumer Ack 机制:

  1. 设置手动签收: acknowledge=“manual” ;
  2. 让监听器类实现 ChannelAwareMessageListener 接口;
  3. 如果消息处理成功, 则调用channel的 basicAck()签收 ;
  4. 如果消息处理失败,则调用channel的basicNack()拒绝签收,broker重新发送给consumer 。

2.2 示例代码

2.2.1 pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>rabbit-ack-consumer</artifactId>
    <version>1.0-SNAPSHOT</version>

    <!-- 引入依赖 -->
    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.1.7.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.amqp</groupId>
            <artifactId>spring-rabbit</artifactId>
            <version>2.1.8.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>5.1.7.RELEASE</version>
        </dependency>

    </dependencies>

</project>

2.2.2 rabbitmq.properties

rabbitmq.host=192.168.191.131
rabbitmq.port=5672
rabbitmq.username=admin
rabbitmq.password=admin
rabbitmq.virtual-host=/xzk

2.2.3 spring-rabbitmq.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"
       xmlns:rabibt="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">

    <!-- 引入 rabbitmq 的配置文件 -->
    <context:property-placeholder location="classpath:rabbitmq/rabbitmq.properties"/>

    <!-- 定义 RabbitMQ 连接工厂 -->
    <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"/>

    <!-- 定义 RabbitTemplate 对象,方便代码中操作数据 -->
    <rabbit:template id="rabbitTemplate" connection-factory="connectionFactory"/>

    <context:component-scan base-package="com.kaikebai.listener"></context:component-scan>

    <!-- acknowledge:设置手动签收  -->
    <rabibt:listener-container connection-factory="connectionFactory" acknowledge="manual" prefetch="1">
        <rabbit:listener ref="ackListener" queue-names="test_confirm_queue"></rabbit:listener>
    </rabibt:listener-container>
</beans>

2.2.4 AckListener.java


package com.kaikebai.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;

@Component
public class AckListener implements ChannelAwareMessageListener {
    public void onMessage(Message message, Channel channel) throws Exception {
        long deliveryTag = message.getMessageProperties().getDeliveryTag();
        try {
            System.out.println(new String(message.getBody(), "UTF-8"));
            System.out.println("逻辑处理业务");

            // 模拟出异常
            int i = 3/0;
            channel.basicAck(deliveryTag, true);
        } catch (Exception e) {
            e.printStackTrace();
            // 拒绝签收
            /**
             * 第二个参数:true-表示可以拒绝多个消息,可以提高效率
             * 第三个参数 requeue:true-表示消息重回队列
             */
            channel.basicNack(deliveryTag, true, true);
            // 下面的也是拒绝签收消息,但是不推荐使用,每次只能拒绝一个,
            // channel.basicReject(deliveryTag, true);
        }
    }

    public void onMessage(Message message) {

    }
}

2.2.5 测试类

启动项目。试监听器开始工作

package com.kaikebai.listener;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.junit4.SpringRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:spring/spring-rabbitmq.xml")
public class AckListererTest {

    @Test
    public void test() {
        while (true) {

        }
    }

}

2.2.6 测试结果

● 异常结果请添加图片描述
● 正常情况请添加图片描述

2.3 Consumer 限流

在这里插入图片描述
MQ 可以进行“削峰填谷”,当在短时间出现大量数据时(如秒杀活动);我们可以将消息存放到消息队列中,然后系统从消息队列中,每次确定特定数量的消息进行消费,以此达到限流的目的。

Consumer 限流机制:

  1. 确保 Ack 机制是手动确认;
  2. listener-container 设置属性,
    a. perfetch=1,表示消费端每次从 MQ 队列中取出一条消息进行消费,消费完毕之后,再去拉取下一条先息进行消费。
package com.kaikebai.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;

@Component
public class QspListener implements ChannelAwareMessageListener {
    public void onMessage(Message message, Channel channel) throws Exception {
        Thread.sleep(1000);
        //1.获取消息
        System.out.println(new String(message.getBody()));
        //2. 处理业务逻辑
        //3. 签收
        channel.basicAck(message.getMessageProperties().getDeliveryTag(),true);
    }

    public void onMessage(Message message) {

    }
}

三、 TTL

全称是 Time To Live ,消息过期时间设置。队列或消息设置过期时间的话,在规定时间过后,队列中的消息将被重新处理;处理方式有两种:

  1. 进入死信队列:若是该队列有对应的死信队列,则消息过期之后,会根据配置的死信队列信息,转发到对应的四星队列中;
  2. 直接丢弃:若是没有配置死信队列,则消息过期之后,江北直接丢弃。

管控台中设置队列TTL :

在这里插入图片描述

3.1 示例代码

3.1.1 配置文件

rabbitmq.properties

rabbitmq.host=192.168.191.132
rabbitmq.port=5672
rabbitmq.username=admin
rabbitmq.password=admin
rabbitmq.virtual-host=/xzk

spring-rabbitmq.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"
       xmlns:contect="http://www.springframework.org/schema/context"
       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">
    <!-- 引入 rabbitmq 的配置文件 -->
    <context:property-placeholder location="classpath:rabbitmq.properties"/>

    <!-- 定义 RabbitMQ 连接工厂 -->
    <rabbit:connection-factory id="connectionFactory" host="${rabbitmq.host}"
                               port="${rabbitmq.port}"
                               username="${rabbitmq.username}"
                               password="${rabbitmq.password}"
                               virtual-host="${rabbitmq.virtual-host}"/>

    <!-- 定义交换机、队列 -->
    <rabbit:admin connection-factory="connectionFactory"/>

    <!-- 通配符模式 -->
    <rabbit:queue name="test_ttl_queue" id="test_ttl_queue" auto-declare="true">
        <rabbit:queue-arguments>
            <entry key="x-message-ttl" value="10000" value-type="java.lang.Integer"></entry>
        </rabbit:queue-arguments>
    </rabbit:queue>

    <rabbit:topic-exchange name="test_ttl_exchange" id="test_ttl_exchange"  auto-declare="true">
        <rabbit:bindings>
            <rabbit:binding pattern="ttl.#" queue="test_ttl_queue"></rabbit:binding>
        </rabbit:bindings>
    </rabbit:topic-exchange>

    <!-- 定义 RabbitTemplate 对象,方便代码中操作数据 -->
    <rabbit:template id="rabbitTemplate" connection-factory="connectionFactory"/>
</beans>

3.1.2 测试代码

package com.kaikeba;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.amqp.AmqpException;
import org.springframework.amqp.core.Correlation;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessagePostProcessor;
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;


@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:spring/spring-rabbitmq.xml")
public class TtlQueueTest {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    /**
     * TTL:过期时间
     * 1. 队列统一过期
     *
     * 2. 消息单独过期
     *
     *
     * 如果设置了消息的过期时间,也设置了队列的过期时间,它以时间短的为准。
     * 队列过期后,会将队列所有消息全部移除。
     * 消息过期后,只有消息在队列顶端,才会判断其是否过期(移除掉)
     *
     */

    /**
     * 队列设置过期时间
     */
    @Test
    public void testQueueTtl() {
        rabbitTemplate.convertAndSend("test_ttl_exchange", "ttl.hello", "测试TTL队列");
    }

    /**
     * 设置消息过期时间
     */
    @Test
    public void testMessageTtl() {
        // 消息处理对象,设置一些消息的参数信息
        MessagePostProcessor messagePostProcessor = new MessagePostProcessor() {
            public Message postProcessMessage(Message message) throws AmqpException {
                return null;
            }

            public Message postProcessMessage(Message message, Correlation correlation) {
                // 1. 设置 message 的过期时间
                message.getMessageProperties().setExpiration("5000");

                return message;
            }
        };

        // 消息单独发送
        rabbitTemplate.convertAndSend("test_ttl_queue", "ttl.hi", "消息单独过期时间测试", messagePostProcessor);
        /*// 下面这种设置过期的方式是不生效的,因为,单独设置的不是第一个消息
        for (int i = 0; i < 20; i++) {
            if(i == 5) {
                rabbitTemplate.convertAndSend("test_ttl_queue", "ttl.hi", "消息单独过期时间测试", messagePostProcessor);
            } else {
                rabbitTemplate.convertAndSend("test_ttl_queue", "ttl.hi", "消息单独过期时间测试");
            }
        }*/
    }
}

3.2 小结

  1. 消息过期可以从队列和消息本身两个维度进行设置;队列层次设置时,整个队列中的消息都以这个过期时间设置为准;在消息层次设置时,只对当前单个消息有效;同时设置时,那一个设置的时间短,那一个起作用。
  2. 针对单个消息设置过期时间时,只有当这个消息是队列中的第一个消息时(排在队列的首位),这个设置才会生效。否则设置不会生效。
  3. 单独设置消息过期时间是通过 messagePostProcessor 中的 postProcessMessage 方法 来设置的

四、 死信队列

4.1 介绍

死信队列DLX(Dead Letter Exchange 死信交换机),因为有的 MQ产品是没有交换机这个设计的,所以我们一般直接叫死信队列。当消息成为 Dead Message 之后,可以被重新发送到另一个交换机,这个交换机就是 DLX 。
请添加图片描述
消息成为死信的三种情况:

  1. 队列消息长度到达限制;
  2. 消费者拒接消费消息,basicNack/basicReject,并且不把消息重新放入原目标队 列,requeue=false;
  3. 原队列存在消息过期设置,消息到达超时时间未被消费;

队列绑定死信交换机:
给队列设置参数: x-dead-letter-exchangex-dead-letter-routing-key
请添加图片描述

4.2 示例代码

4.2.1 配置文件

rabbitmq.properties

rabbitmq.host=192.168.191.130
rabbitmq.port=5672
rabbitmq.username=admin
rabbitmq.password=admin
rabbitmq.virtual-host=/xzk

spring-rabbitmq.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"
       xmlns:contect="http://www.springframework.org/schema/context"
       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"></context:property-placeholder>

    <!-- 定义 RabbitMQ ConnectionFactory -->
    <!-- 定义 RabbitMQ 连接工厂 -->
    <rabbit:connection-factory id="connectionFactory"
                               host="${rabbitmq.host}"
                               port="${rabbitmq.port}"
                               username="${rabbitmq.username}"
                               password="${rabbitmq.password}"
                               virtual-host="${rabbitmq.virtual-host}"/>

    <rabbit:admin connection-factory="connectionFactory" />

    <!-- 声明并绑定死信交换机、私信队列 -->
    <rabbit:queue id="queue_dlx" name="queue_dlx" auto-declare="true"></rabbit:queue>
    <rabbit:topic-exchange id="exchange_dlx" name="exchange_dlx" auto-declare="true">
        <rabbit:bindings>
            <rabbit:binding pattern="dlx.#" queue="queue_dlx"></rabbit:binding>
        </rabbit:bindings>
    </rabbit:topic-exchange>

    <!-- 声明正常的队列 test_queue_dlx-->
    <rabbit:queue id="test_queue_dlx" name="test_queue_dlx" auto-declare="true">
        <!-- 绑定死信交换机 -->
        <rabbit:queue-arguments>
            <!-- x-dead-letter-exchange 死信交换机 -->
            <entry key="x-dead-letter-exchange" value="exchange_dlx"></entry>
            <!-- x-dead-letter-routing-key 发送给死信交换机的路由 key -->
            <entry key="x-dead-letter-routing-key" value="dlx.hello"></entry>
            <!-- 设置消息过期时间 -->
            <entry key="x-message-ttl" value="10000" value-type="java.lang.Integer"></entry>
            <!-- 设置队列的长度 -->
            <entry key="x-max-length" value="10" value-type="java.lang.Integer"></entry>
        </rabbit:queue-arguments>
    </rabbit:queue>
    <!-- 正常的交换机 -->
    <rabbit:topic-exchange name="test_exchange_dlx" auto-declare="true">
        <rabbit:bindings>
            <rabbit:binding pattern="test.dlx.#" queue="test_queue_dlx"></rabbit:binding>
        </rabbit:bindings>
    </rabbit:topic-exchange>

    <!-- 定义 RabbitTemplate 对象,方便代码中操作数据 -->
    <rabbit:template id="rabbitTemplate" connection-factory="connectionFactory"/>
</beans>

4.2.2 测试类

package com.kaikeba.dlx;

import org.junit.Test;
import org.junit.runner.RunWith;
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;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:spring-rabbitmq.xml")
public class DeadLetterExchangeTest {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    /**
     * 消息过期
     */
    @Test
    public void testDlxExpression() {
        // 死信 -- 测试过期时间
        rabbitTemplate.convertAndSend("test_exchange_dlx", "test.dlx.hehe", "死信队列,我是测试过期时间的!!!");
    }

    /**
     * 数量超过
     */
    @Test
    public void testDlxMax() {
        // 死信 -- 数量超过
        for(int i = 0; i < 20; i++) {
            rabbitTemplate.convertAndSend("test_exchange_dlx", "test.dlx.hehe", "死信队列,我是测试过期时间的!!!");
        }
    }

    /**
     * 数量拒收
     */
    @Test
    public void testDlxRef() {
        // 死信 -- 拒收
        rabbitTemplate.convertAndSend("test_exchange_dlx", "test.dlx.hehe", "死信队列,我是测试过期时间的!!!");
    }
}

4.2.3 消费者代码

消费者代码是模拟消费端拒接消息的场景

4.2.3.1 DlxListener.java
package com.kaikeba.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;

@Component
public class DlxListener implements ChannelAwareMessageListener {
    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("处理业务逻辑...");
            int i = 3/0;//出现错误
            //3. 手动签收
            channel.basicAck(deliveryTag,true);
        } catch (Exception e) {
            //e.printStackTrace();
            System.out.println("出现异常,拒绝接受");
            //4.拒绝签收,不重回队列 requeue=false
            channel.basicNack(deliveryTag,true,false);
        }
    }

    public void onMessage(Message message) {

    }
}

4.2.3.2 配置文件

rabbitmq.properties

rabbitmq.host=192.168.191.130
rabbitmq.port=5672
rabbitmq.username=admin
rabbitmq.password=admin
rabbitmq.virtual-host=/xzk

spring-rabbitmq.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"
       xmlns:contect="http://www.springframework.org/schema/context"
       xmlns:ra="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"></context:property-placeholder>

    <!-- 定义 RabbitMQ ConnectionFactory -->
    <!-- 定义 RabbitMQ 连接工厂 -->
    <rabbit:connection-factory id="connectionFactory"
                               host="${rabbitmq.host}"
                               port="${rabbitmq.port}"
                               username="${rabbitmq.username}"
                               password="${rabbitmq.password}"
                               virtual-host="${rabbitmq.virtual-host}"/>

    <rabbit:admin connection-factory="connectionFactory" />

    <!-- 声明并绑定死信交换机、私信队列 -->
    <rabbit:queue id="queue_dlx" name="queue_dlx" auto-declare="true"></rabbit:queue>
    <rabbit:topic-exchange id="exchange_dlx" name="exchange_dlx" auto-declare="true">
        <rabbit:bindings>
            <rabbit:binding pattern="dlx.#" queue="queue_dlx"></rabbit:binding>
        </rabbit:bindings>
    </rabbit:topic-exchange>

    <context:component-scan base-package="com.kaikeba"></context:component-scan>


    <rabbit:listener-container connection-factory="connectionFactory">
        <rabbit:listener ref="dlxListener" queue-names="test_queue_dlx"></rabbit:listener>
    </rabbit:listener-container>

    <!-- 定义 RabbitTemplate 对象,方便代码中操作数据 -->
    <rabbit:template id="rabbitTemplate" connection-factory="connectionFactory"/>
</beans>
4.2.3.3 测试类
package com.kaikeba.listener;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:spring-rabbitmq.xml")
public class DlsConsumerTest {

    @Test
    public void test() {
        while (true) {

        }
    }
}

4.2.4 测试结果

  1. 交换机
    请添加图片描述
  2. 交换机绑定关系
  • 交换机

请添加图片描述
在这里插入图片描述

  • 队列
    请添加图片描述

test_queue_dlx 是正常交换机,我们设置的是10秒,10 秒之后,就转发到死信队列 queue_dlx。

五、 延迟队列

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

我们模拟一个下单需求,来说明 RabbitMQ 的延时队列的实现

模拟需求:

  1. 下单后,30分钟未支付,取消订单,回滚库存。
  2. 新用户注册成功7天后,发送短信问候。

一般情况:
有的 MQ 产品是有延迟队列的,可以直接实现;

实现方式:

  1. 定时器
  2. 延迟队列

在这里插入图片描述
而我们的 RabbitMQ 中没有延迟队列的直接实现;但是我们可以通过 TTL + 死信队列 来达到延迟队列的效果。
在这里插入图片描述
我们把正常队列的消息过期时间设置为 30min;将消费投递到正常队列,30min 过后消息过期,转发到对应的死信队列,我们的程序监听死信队列,并消费其中的消息。就达到了消息延迟 30min 的目的。

实现过程:

  1. 定义正常交换机(order_exchange)和队列(order_queue)
  2. 定义死信交换机(order_exchange_dlx)和队列(order_queue_dlx)
  3. 绑定,设置正常队列过期时间为30分钟

5.1 示例代码

5.1.1 pom.xml


<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>rabbit-delay-producer</artifactId>
    <version>1.0-SNAPSHOT</version>

    <!-- 引入依赖 -->
    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.1.7.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.amqp</groupId>
            <artifactId>spring-rabbit</artifactId>
            <version>2.1.8.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>5.1.7.RELEASE</version>
        </dependency>

    </dependencies>

</project>

5.1.2 配置文件

rabbitmq.xml

rabbitmq.host=192.168.191.128
rabbitmq.port=5672
rabbitmq.username=admin
rabbitmq.password=admin
rabbitmq.virtual-host=/xzk

spring-rabbitmq.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"
       xmlns:contect="http://www.springframework.org/schema/context"
       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"></context:property-placeholder>

    <!-- 定义 RabbitMQ ConnectionFactory -->
    <!-- 定义 RabbitMQ 连接工厂 -->
    <rabbit:connection-factory id="connectionFactory"
                               host="${rabbitmq.host}"
                               port="${rabbitmq.port}"
                               username="${rabbitmq.username}"
                               password="${rabbitmq.password}"
                               virtual-host="${rabbitmq.virtual-host}"/>

    <rabbit:admin connection-factory="connectionFactory" />

    <!-- 定义 RabbitTemplate 对象,方便代码中操作数据 -->
    <rabbit:template id="rabbitTemplate" connection-factory="connectionFactory"/>

    <!-- 延迟队列 -->
    <!--
        1. 定义死信交换机(order_exchange_dlx)和队列(order_queue_dlx)
        2. 定义正常交换机(order_exchange)和队列(order_queue)
        3. 绑定,设置正常队列过期时间为 30 分钟。
    -->
    <!-- 1. 定义死信交换机(order_exchange_dlx)和队列(order_queue_dlx) -->
    <rabbit:queue id="order_queue_dlx" name="order_queue_dlx" auto-declare="true"></rabbit:queue>
    <rabbit:topic-exchange name="order_exchange_dlx" id="order_exchange_dlx"  auto-declare="true">
        <rabbit:bindings>
            <rabbit:binding pattern="dlx.order.#" queue="order_queue_dlx"></rabbit:binding>
        </rabbit:bindings>
    </rabbit:topic-exchange>

    <!-- 2. 定义正常交换机(order_exchange)和队列(order_queue) -->
    <rabbit:queue id="order_queue" name="order_queue"  auto-declare="true">
        <!-- 3. 绑定死信队列,设置过期时间(为了测试,设置10秒) -->
        <rabbit:queue-arguments>
            <!-- 死信交换机 -->
            <entry key="x-dead-letter-exchange" value="order_exchange_dlx"></entry>
            <!-- 死信 routing key -->
            <entry key="x-dead-letter-routing-key" value="dlx.order.cancel"></entry>
            <!-- 过期时间 -->
            <entry key="x-message-ttl" value="10000" value-type="java.lang.Integer"></entry>
        </rabbit:queue-arguments>
    </rabbit:queue>
    <rabbit:topic-exchange name="order_exchange" id="order_exchange"  auto-declare="true">
        <rabbit:bindings>
            <rabbit:binding pattern="order.#" queue="order_queue"></rabbit:binding>
        </rabbit:bindings>
    </rabbit:topic-exchange>
</beans>

5.1.3 测试类

package com.kaikeba.delay.kaikeba.delay;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.amqp.core.Message;
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.Date;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:spring-rabbitmq.xml")
public class DelayProducerTest {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Test
    public void testDelay() {
        // 1.发送订单消息。 将来是在订单系统中,下单成功后,发送消息
        rabbitTemplate.convertAndSend("order_exchange", "order.msg","订单信息:" + new Date());
    }

}

5.1.4 测试结果

  • 交换机
    请添加图片描述
    在这里插入图片描述
  • 队列
    在这里插入图片描述

5.2 消费者

消费者监听死信队列,模拟延时效果

5.2.1 监听类

package com.kaikeba.delay;

import com.rabbitmq.client.Channel;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.listener.api.ChannelAwareMessageListener;
import org.springframework.stereotype.Component;

@Component
public class DelayListener  implements ChannelAwareMessageListener {
    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("取消订单,回滚库存....");
            //3. 手动签收
            channel.basicAck(deliveryTag,true);
        } catch (Exception e) {
            //e.printStackTrace();
            System.out.println("出现异常,拒绝接受");
            //4.拒绝签收,不重回队列 requeue=false
            channel.basicNack(deliveryTag,true,false);
        }

    }

    public void onMessage(Message message) {

    }
}

5.2.2 配置文件

rabbitmq 的配置文件和生产者一样。
spring-rabbitmq.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"
       xmlns:contect="http://www.springframework.org/schema/context"
       xmlns:rabbir="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"></context:property-placeholder>

    <!-- 定义 RabbitMQ ConnectionFactory -->
    <!-- 定义 RabbitMQ 连接工厂 -->
    <rabbit:connection-factory id="connectionFactory"
                               host="${rabbitmq.host}"
                               port="${rabbitmq.port}"
                               username="${rabbitmq.username}"
                               password="${rabbitmq.password}"
                               virtual-host="${rabbitmq.virtual-host}"/>

    <rabbit:admin connection-factory="connectionFactory" />

    <!-- 定义 RabbitTemplate 对象,方便代码中操作数据 -->
    <rabbit:template id="rabbitTemplate" connection-factory="connectionFactory"/>

    <context:component-scan base-package="com.kaikeba"></context:component-scan>
    <rabbir:listener-container connection-factory="connectionFactory">
        <rabbit:listener ref="delayListener" queue-names="order_queue_dlx"/>
    </rabbir:listener-container>

</beans>

5.2.3 测试类

package com.kaikeba.delay;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:spring-rabbitmq.xml")
public class DelayListenerTest {

    @Test
    public void test() {
        while (true) {

        }
    }
}

5.2.4 消费结果请添加图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值