一、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到期后,自动转移到死信交换机
- 死信交换机根据路由键将消息路由到死信队列
- 消费者从死信队列中消费已经到达延迟时间的消息