RabbitMQ高级

一、RabbitMQ高级特性

1.1 消息可靠性投递

RabbitMQ整个消息投递的路径为:生产者-交换机-队列-消费者

消息可靠性投递:如何保证生产者发送的消息不会出现丢失或者投递失败。

在使用RabbitMQ的时候:作为消息发送方希望杜绝任何消息丢失或者投递失败场景。RabbitMQ为我们提供了两种方式用来控制消息的投递可靠性模式。

  • confirm确认模式:消息从生产者到交换机,则会返回一个confirmCallback。不管消息是否成功到达交换机,confirmCallback都会执行,通过回调函数的参数可以判断消息是否成功。
  • return退回模式:消息从交换机到队列,投递失败则会返回一个returnCallback。

confirm确认模式

开启确认模式

生产者application.yml

yml 复制代码
spring:
  rabbitmq:
    host: localhost
    port: 5672
    virtual-host: /liming
    username: liming
    password: 123456
    publisher-confirm-type: correlated # 开启确认模式
定义回调函数
java 复制代码
package com.liming;

import org.junit.jupiter.api.Test;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.boot.test.context.SpringBootTest;

import javax.annotation.Resource;

@SpringBootTest
class SpringbootRabbitmqProducerApplicationTests {

    @Resource
    private RabbitTemplate rabbitTemplate;

    @Test
    void testConfirm(){
        // 设置确认回调,用于确认消息是否成功发送到交换机
        rabbitTemplate.setConfirmCallback( (correlationData, ack, cause) -> {
            // 如果ack为true,表示消息成功发送到交换机
            if (ack){
                System.out.println("消息发送成功,到达交换机");
            }else {
                // 如果ack为false,表示消息发送失败,打印失败原因
                System.out.println("消息发送失败:"+cause);
            }
        });

        // 发送消息到指定交换机和路由键
        rabbitTemplate.convertAndSend("springboot_topic_exchange","item.insert","添加信息item.insert");
    }

}

return退回模式

当消息发送给交换机后,从交换机路由到队列失败时,才会执行returnCallback

开启回退模式

生产者application.yml

yml 复制代码
spring:
  rabbitmq:
    host: localhost
    port: 5672
    virtual-host: /liming
    username: liming
    password: 123456
    publisher-confirm-type: correlated # 开启确认模式
    publisher-returns: true # 开启回退模式
定义回调函数
java 复制代码
package com.liming;

import org.junit.jupiter.api.Test;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.boot.test.context.SpringBootTest;

import javax.annotation.Resource;

@SpringBootTest
class SpringbootRabbitmqProducerApplicationTests {

    @Resource
    private RabbitTemplate rabbitTemplate;

    @Test
    void testReturn(){
        rabbitTemplate.setMandatory(true);

        rabbitTemplate.setReturnsCallback( (message) -> {
            // 如果消息被交换机返回,表示消息没有路由到任何队列
            System.out.println("消息被交换机返回:"+message.getMessage());
            System.out.println("消息的路由键是:"+message.getRoutingKey());
            System.out.println("消息的交换机是:"+message.getExchange());
            System.out.println("消息的路由键是:"+message.getRoutingKey());
        });

        rabbitTemplate.convertAndSend("springboot_topic_exchange","item1.insert","添加信息item.insert");
    }

}

1.2 Consumer ACK

ack指Acknowledge,确认。表示消费端收到消息的确认方式。

有三种确认方式:

  • 自动确认(默认):acknowledge-mode=none
  • 手动确认:acknowledge-mode=manual
  • 根据异常情况确认:acknowledge-mode=auto(这种方式使用麻烦,不推荐)

其中自动确认是指,当消息一旦被消费者接收到并将相应消息从RabbitMQ的消息缓存中移除。但是实际业务处理中,很可能消息接收到,业务处理异常,那么消息就会丢失。

如果设置了手动确认,则需要在业务处理成功后,调用channel.basicAck(),手动签收,如果出现异常,则调用channel.basicNack()方法,让其自动重新发送消息。

设置手动确认

消费者application.yml

yml 复制代码
spring:
  rabbitmq:
    host: localhost
    port: 5672
    virtual-host: /liming
    username: liming
    password: 123456
    listener:
      simple:
        acknowledge-mode: manual # 消费者手动确认

代码实现

java 复制代码
package com.liming.listener;

import com.rabbitmq.client.Channel;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

import java.io.IOException;

/**
 * Ack机制消息监听器
 * 用于演示Spring Boot集成RabbitMQ的手动ACK确认机制
 * 在消息处理成功后手动发送ACK确认,处理失败时发送NACK拒绝
 */
@Component
public class AckListener {

    /**
     * 监听指定队列的消息处理器
     * 使用手动ACK模式确保消息可靠消费
     *
     * @param message 消息对象,包含消息体和属性信息
     * @param channel RabbitMQ通道对象,用于发送ACK或NACK确认
     * @throws InterruptedException 线程中断异常
     * @throws IOException IO异常
     */
    @RabbitListener(queues = "springboot_queue")
    public void listenerMessage(Message message, Channel channel) throws InterruptedException, IOException {
        // 模拟业务处理耗时
        Thread.sleep(1000);

        try {
            // 打印接收到的消息内容
            System.out.println("接收到的消息:"+new String(message.getBody()));
            // 表示正在处理业务逻辑
            System.out.println("处理业务消息。。。");
            // 手动发送ACK确认,表示消息已成功处理
            // deliveryTag: 消息投递标签,用于标识特定消息
            // multiple: 是否批量确认(true表示确认所有小于等于该deliveryTag的消息)
            channel.basicAck(message.getMessageProperties().getDeliveryTag(), true);
        }catch (Exception e){
            // 发生异常时发送NACK拒绝消息
            // deliveryTag: 消息投递标签
            // multiple: 是否批量拒绝
            // requeue: 是否重新入队(true表示将消息重新放回队列供其他消费者处理)
            channel.basicNack(message.getMessageProperties().getDeliveryTag(), true, true);
        }
    }
}

1.3 消费端限流

  • 假设一个场景,首先我们RabbitMQ服务器积压了有上万条未处理的消息,我们随便打开一个消费者客户端会出现这样的情况:巨量的消息瞬间全部推送过来,但是我们单个客户端无法同时处理这么多数据。

  • 当数据量特别大的时候,我们对生产端限流肯定是不科学的,因为有时候并发量就是特别大,有时候并发量又特别少,我们无法约束生产端,这是用户的行为。所以我们应该对消费端限流,用于保持消费端的稳定,当消息数量激增的时候很有可能造成资源耗尽,以及影响服务的性能,导致系统的卡顿甚至直接崩溃。

设置消费端限流

根据消费端的能力按需处理消息

消费者application.yml

yml 复制代码
# 设置消费端限流必须手动确认
spring:
  rabbitmq:
    host: localhost
    port: 5672
    virtual-host: /liming
    username: liming
    password: 123456
    listener:
      simple:
        # 设置消息确认模式为手动确认,这是实现消息可靠性的重要配置
        # 当设置为manual时,需要在代码中手动调用channel.basicAck()或basicNack()
        acknowledge-mode: manual # 消费者手动确认
        # 预取数量,限制每个消费者同时处理的消息数量,用于流量控制
        # 值为1表示每次只从队列获取一条消息,处理完后再获取下一条
        prefetch: 1

1.4 过期时间TTL

过期时间TTL表示可以对消息设置预期的时间,在这个时间内都可以被消费者接受获取;当消息达到存活时间后,还没有被消费,会被自动清除。RabbitMQ可以对消息和队列设置TTL。

  • 对整个队列设置过期时间(推荐)
  • 对消息设置过期时间

如果上述两种方法同时使用,则消息的过期时间以两者之间TTL较小的那个值为准。消息在队列的生存时间一旦超过设置的TTL值,就称为死信,消息被丢弃。

实现方式

创建交换机和队列时设置TTL

java 复制代码
package com.liming.config;

import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * RabbitMQ配置类
 * 该类用于配置Spring Boot应用程序中的RabbitMQ相关组件
 * 包括交换机、队列和绑定关系的声明和配置
 */
@Configuration
public class RabbitMQConfig {

    // 定义交换机名称常量,使用Topic Exchange类型
    // Topic Exchange支持通配符模式的消息路由
    public static final String ITEM_TOPIC_EXCHANGE = "test_ttl_exchange";
    
    // 定义队列名称常量
    public static final String ITEM_QUEUE = "test_ttl_queue";

    /**
     * 创建Topic交换机的Bean
     * Topic Exchange是一种灵活的交换机类型,支持使用通配符进行路由匹配
     * @return 配置好的Topic Exchange对象
     */
    @Bean("itemTopicExchange")
    public Exchange topicExchange() {
        // 使用ExchangeBuilder构建Topic交换机
        // 设置交换机持久化,确保服务器重启后交换机依然存在
        return ExchangeBuilder.topicExchange(ITEM_TOPIC_EXCHANGE)
                .durable(true)
                .build();
    }

    /**
     * 创建队列的Bean
     * 该队列用于存储从交换机路由过来的消息
     * @return 配置好的队列对象
     */
    @Bean("itemQueue")
    public Queue itemQueue() {
        // 使用QueueBuilder构建持久化队列
        // 设置队列持久化,确保服务器重启后队列依然存在
        return QueueBuilder.durable(ITEM_QUEUE)
                .ttl(10000) // 队列过期时间,单位为毫秒
                .build();
    }

    /**
     * 创建绑定关系的Bean
     * 将队列与交换机进行绑定,并设置路由键模式
     * @return 配置好的绑定关系对象
     */
    @Bean
    public Binding itemQueueExchange() {
        // 使用BindingBuilder将队列绑定到交换机
        // 路由键模式"item.#"表示匹配所有以"item."开头的路由键
        // "#"是通配符,代表零个或多个词
        // 例如:"item.insert"、"item.update"、"item.delete.status"都会匹配
        return BindingBuilder.bind(itemQueue()).to(topicExchange()).with("item.#").noargs();
    }
}
java 复制代码
@Test
void testTTL(){
   // 测试队列的TTL
   rabbitTemplate.convertAndSend("test_ttl_exchange","test_ttl_queue","测试TTL消息");
   
   // 测试消息的TTL
   // TTL是消息的生存时间,超过此时间未被消费的消息会被自动删除或进入死信队列
   rabbitTemplate.convertAndSend("test_ttl_exchange","test_ttl_queue","测试TTL消息,设置3秒过期",message -> {
       // 设置消息的过期时间为3000毫秒(3秒)
       // 过期的消息会在队列中被丢弃或者路由到配置的死信交换机
       message.getMessageProperties().setExpiration("3000");
       return message;
   });
}

1.5 死信队列

DLX,称之为死信交换机。当消息在一个队列中变成死信之后,它可以被重新发送到另一个交换机中,这个交换机就是DLX,绑定DLX的队列称之为死信队列。


DLX也是一种正常的交换机,和一般的交换机没有区别。它能在任何的队列上被指定,实际上就是设置某一个队列的属性。当这个队列中存在死信时,RabbitMQ就会自动的将这个消息重新发布到设置的DLX上去,进而被路由到另一个队列,即死信队列。

消息称为死信的三种情况:

  • 队列消息长度到达限制
  • 消费者拒绝消费消息,并且不把消息重新放到原目标队列
  • 原队列存在消息过期设置,消息到达超时时间未被消费

队列绑定死信交换机

  • 给队列设置参数:x-dead-letter-exchange和x-dead-letter-routing-key

代码实现

java 复制代码
package com.liming.config;

import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * 死信队列配置类
 * 死信队列(DLX, Dead Letter Exchange)是RabbitMQ中的一种特殊队列,
 * 用于处理无法被正常消费的消息(如过期、达到最大重试次数等)
 */
@Configuration
public class RabbitMQDlxConfig {

    /**
     * 创建正常的Topic交换机
     * 用于接收生产者发送的原始消息
     *
     * @return Topic类型的交换机实例
     */
    @Bean
    public Exchange normalExchange() {
        // 创建持久化的topic交换机,保证服务器重启后交换机依然存在
        return ExchangeBuilder.topicExchange("normal_exchange").durable(true).build();
    }

    /**
     * 创建正常的队列,并配置死信相关参数
     * 该队列的消息在满足特定条件时会被转发到死信交换机
     *
     * @return 配置了死信参数的队列实例
     */
    @Bean
    public Queue normalQueue() {
        /**
         * 正常队列绑定死信交换机
         * 队列属性说明:
         * - deadLetterExchange: 指定死信交换机名称,当消息变成死信时会发送到该交换机
         * - deadLetterRoutingKey: 指定死信路由键,决定死信如何路由到死信队列
         * - ttl: 设置队列中每条消息的TTL(Time To Live),单位毫秒
         * - maxLength: 设置队列的最大长度,超过该长度的消息会被丢弃或进入死信队列
         */
        return QueueBuilder.durable("normal_queue")
                // 设置死信交换机名称,当消息成为死信时会发送到该交换机
                .deadLetterExchange("dlx_exchange")
                // 设置死信路由键,决定死信如何路由到对应的死信队列
                .deadLetterRoutingKey("dlx.hh")
                // 设置队列中每条消息的存活时间,单位毫秒(10秒)
                .ttl(10000)
                // 设置队列的最大长度,当队列消息数量达到10条时,新消息会变成死信
                .maxLength(10)
                .build();
    }

    /**
     * 绑定正常交换机和正常队列
     * 定义消息如何从交换机路由到队列
     *
     * @param normalExchange 正常交换机
     * @param normalQueue   正常队列
     * @return 绑定关系实例
     */
    @Bean
    public Binding normalBinding(Exchange normalExchange, Queue normalQueue) {
        // 将正常队列绑定到正常交换机,使用通配符路由键"normal.#"
        // 匹配以"normal."开头的所有路由键的消息
        return BindingBuilder.bind(normalQueue).to(normalExchange).with("normal.#").noargs();
    }

    /**
     * 创建死信交换机
     * 用于接收从正常队列转移过来的死信消息
     *
     * @return 死信交换机实例
     */
    @Bean
    public Exchange dlxExchange() {
        // 创建持久化的topic类型死信交换机
        // 当正常队列中的消息变成死信时,会被发送到这个交换机
        return ExchangeBuilder.topicExchange("dlx_exchange").durable(true).build();
    }

    /**
     * 创建死信队列
     * 用于存储从正常队列转移过来的死信消息
     *
     * @return 死信队列实例
     */
    @Bean
    public Queue dlxQueue() {
        // 创建持久化死信队列,用于存储无法被正常消费的消息
        // 消息管理员或专门的消费者可以检查这些死信消息
        return QueueBuilder.durable("dlx_queue").build();
    }

    /**
     * 绑定死信交换机和死信队列
     * 定义死信消息如何从死信交换机路由到死信队列
     *
     * @param dlxExchange 死信交换机
     * @param dlxQueue   死信队列
     * @return 死信绑定关系实例
     */
    @Bean
    public Binding dlxBinding(Exchange dlxExchange, Queue dlxQueue) {
        // 将死信队列绑定到死信交换机,使用通配符路由键"dlx.#"
        // 匹配以"dlx."开头的所有路由键的死信消息
        return BindingBuilder.bind(dlxQueue).to(dlxExchange).with("dlx.#").noargs();
    }
}

1.6 延迟队列

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

  • RabbitMQ中并没有提供延迟队列的功能
  • 替代方案:TTL+死信队列 组合实现延迟队列的效果

代码实现

java 复制代码
package com.liming.config;

import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * 延迟队列配置类
 * 利用TTL(Time To Live)和死信队列组合实现延迟队列效果
 * 延迟队列:消息进入队列后不会被立即消费,只有到达指定时间后才会被消费
 * 实现原理:消息在延迟队列中设置TTL,过期后自动转移到死信队列,由死信队列的消费者处理
 */
@Configuration
public class RabbitMQDelayConfig {

    /**
     * 创建延迟交换机
     * 用于接收带有延迟需求的消息
     *
     * @return Topic类型的延迟交换机实例
     */
    @Bean
    public Exchange delayExchange() {
        // 创建持久化的topic交换机,用于延迟消息的路由
        return ExchangeBuilder.topicExchange("delay_exchange").durable(true).build();
    }

    /**
     * 创建延迟队列
     * 该队列用于存储需要延迟处理的消息
     * 消息在该队列中等待指定时间后过期,然后被转移到死信队列
     *
     * @return 配置了死信参数的延迟队列实例
     */
    @Bean
    public Queue delayQueue() {
        /**
         * 延迟队列配置说明:
         * - deadLetterExchange: 指定死信交换机名称,当消息TTL到期后会发送到该交换机
         * - deadLetterRoutingKey: 指定死信路由键,决定过期消息如何路由到死信队列
         * - x-message-ttl: 队列中每条消息的TTL(Time To Live),单位毫秒
         * - durable: 设置为true表示队列持久化,服务器重启后队列依然存在
         */
        return QueueBuilder.durable("delay_queue")
                // 设置死信交换机名称,当消息TTL到期后会发送到该交换机
                .deadLetterExchange("delay_dlx_exchange")
                // 设置死信路由键,决定过期消息如何路由到对应的死信队列
                .deadLetterRoutingKey("delay.dlx")
                // 设置队列的默认消息TTL,所有消息都会在这个时间后过期
                .ttl(30000) // 30秒延迟,可以根据实际需求调整
                .build();
    }

    /**
     * 绑定延迟交换机和延迟队列
     * 定义延迟消息如何从交换机路由到延迟队列
     *
     * @param delayExchange 延迟交换机
     * @param delayQueue   延迟队列
     * @return 延迟队列绑定关系实例
     */
    @Bean
    public Binding delayBinding(Exchange delayExchange, Queue delayQueue) {
        // 将延迟队列绑定到延迟交换机,使用通配符路由键"delay.#"
        // 匹配以"delay."开头的所有路由键的延迟消息
        return BindingBuilder.bind(delayQueue).to(delayExchange).with("delay.#").noargs();
    }

    /**
     * 创建延迟死信交换机
     * 用于接收从延迟队列转移过来的过期消息
     *
     * @return 死信交换机实例
     */
    @Bean
    public Exchange delayDlxExchange() {
        // 创建持久化的topic类型死信交换机
        // 当延迟队列中的消息TTL到期后,会被发送到这个交换机
        return ExchangeBuilder.topicExchange("delay_dlx_exchange").durable(true).build();
    }

    /**
     * 创建延迟死信队列
     * 用于存储从延迟队列转移过来的过期消息
     * 这些消息已经过了延迟时间,可以被消费者立即处理
     *
     * @return 延迟死信队列实例
     */
    @Bean
    public Queue delayDlxQueue() {
        // 创建持久化死信队列,用于存储已过期的延迟消息
        // 消费者可以从这个队列中消费已经到达延迟时间的消息
        return QueueBuilder.durable("delay_dlx_queue").build();
    }

    /**
     * 绑定延迟死信交换机和延迟死信队列
     * 定义过期消息如何从死信交换机路由到死信队列
     *
     * @param delayDlxExchange 延迟死信交换机
     * @param delayDlxQueue   延迟死信队列
     * @return 死信绑定关系实例
     */
    @Bean
    public Binding delayDlxBinding(Exchange delayDlxExchange, Queue delayDlxQueue) {
        // 将延迟死信队列绑定到延迟死信交换机,使用通配符路由键"delay.dlx"
        // 匹配路由键为"delay.dlx"的消息
        return BindingBuilder.bind(delayDlxQueue).to(delayDlxExchange).with("delay.dlx").noargs();
    }
}
java 复制代码
@Test
void testDelayQueue() {
    // 测试延迟队列功能
    // 发送带有自定义TTL的消息到延迟队列
    // 消息将在指定的延迟时间后才被转移到死信队列并被消费
    
    // 发送一条消息,设置5秒延迟(5000毫秒)
    // 由于delay_queue本身设置了30秒的TTL,这里的消息TTL会覆盖队列级别的TTL
    String message = "这是一条延迟5秒的消息";
    rabbitTemplate.convertAndSend("delay_exchange", "delay.test", message, msg -> {
        // 设置消息级别的TTL为5000毫秒(5秒)
        // 这样每条消息都可以有不同的延迟时间
        msg.getMessageProperties().setExpiration("5000");
        return msg;
    });
    
    System.out.println("消息已发送到延迟队列,将在5秒后被消费");
    
    // 发送另一条消息,设置10秒延迟
    String message2 = "这是一条延迟10秒的消息";
    rabbitTemplate.convertAndSend("delay_exchange", "delay.test", message2, msg -> {
        // 设置消息级别的TTL为10000毫秒(10秒)
        msg.getMessageProperties().setExpiration("10000");
        return msg;
    });
    
    System.out.println("第二条消息已发送到延迟队列,将在10秒后被消费");
}
  • 生产者将消息发送到延迟交换机
  • 消息根据路由键被路由到延迟队列
  • 消息在延迟队列中等待指定时间(TTL)
  • 消息TTL到期后,自动转移到死信交换机
  • 死信交换机根据路由键将消息路由到死信队列
  • 消费者从死信队列中消费已经到达延迟时间的消息
相关推荐
Francek Chen2 小时前
【大数据基础】大数据处理架构Hadoop:02 Hadoop生态系统
大数据·hadoop·分布式·hdfs·架构
Thomas21433 小时前
spark view永久保存 + paimon对应的view
大数据·分布式·spark
_codemonster3 小时前
分布式深度学习训练框架Horovod
人工智能·分布式·深度学习
小楼v3 小时前
消息队列的核心概念与应用(RabbitMQ快速入门)
java·后端·消息队列·rabbitmq·死信队列·交换机·安装步骤
小北方城市网3 小时前
SpringBoot 全局异常处理最佳实践:从混乱到规范
java·spring boot·后端·spring·rabbitmq·mybatis·java-rabbitmq
Knight_AL4 小时前
RabbitMQ + Flink 为什么必然会重复?以及如何用 seq 做稳定去重
flink·rabbitmq·ruby
❀͜͡傀儡师4 小时前
全新分布式ID组件TSID支持N种数据类型
分布式
乌恩大侠4 小时前
【AI-RAN 调研】软银株式会社的 “AITRAS” 基于 Arm 架构的 NVIDIA 平台 实现 集中式与分布式 AI-RAN 架构
人工智能·分布式·fpga开发·架构·usrp·mimo
alonewolf_994 小时前
ZooKeeper ZAB协议源码深度剖析:从理论到实践的分布式一致性指南
分布式·zookeeper