文章目录
- [一. 消息确认机制](#一. 消息确认机制)
-
- [1. 自动确认](#1. 自动确认)
- [2. 手动确认](#2. 手动确认)
- [3. 手动确认的方法和参数介绍](#3. 手动确认的方法和参数介绍)
-
- [1. 肯定确认 Channel.basicAck(long deliveryTag, boolean multiple)](#1. 肯定确认 Channel.basicAck(long deliveryTag, boolean multiple))
- [2. 否定确认 Channel.basicReject(long deliveryTag, boolean requeue)](#2. 否定确认 Channel.basicReject(long deliveryTag, boolean requeue))
- [3. 批量否定确认 Channel.basicNack(long deliveryTag, boolean multiple, boolean requeue)](#3. 批量否定确认 Channel.basicNack(long deliveryTag, boolean multiple, boolean requeue))
- [4. 原生sdk代码](#4. 原生sdk代码)
- [5. SpringAMQP中接口的变化](#5. SpringAMQP中接口的变化)
-
- [1. NONE](#1. NONE)
-
- [(1) yml配置](#(1) yml配置)
- [(2) 声明队列, 交换机与绑定关系](#(2) 声明队列, 交换机与绑定关系)
- [(3) 生产者](#(3) 生产者)
- [(4) 消费者](#(4) 消费者)
- [(5) 运行图](#(5) 运行图)
- [2. MANUAL](#2. MANUAL)
-
- [(1) yml配置](#(1) yml配置)
- [(2) 消费者代码](#(2) 消费者代码)
- [(3) 运行图](#(3) 运行图)
- [3. AUTO](#3. AUTO)
-
- [(1) yml配置](#(1) yml配置)
- [(2) 运行图](#(2) 运行图)
- [二. 持久化机制](#二. 持久化机制)
-
- [1. 交换器的持久化](#1. 交换器的持久化)
- [2. 队列持久化](#2. 队列持久化)
- [3. 消息持久化](#3. 消息持久化)
- [三. 发布方确认机制](#三. 发布方确认机制)
-
- [1. yml参数配置](#1. yml参数配置)
- [2. 单例模式RabbitTemplate设置ConfirmCallback方法的两个问题与解决方法](#2. 单例模式RabbitTemplate设置ConfirmCallback方法的两个问题与解决方法)
-
- [(1) 问题1](#(1) 问题1)
- [(3) 问题2](#(3) 问题2)
- [3. confirm确认模式(解决方案)](#3. confirm确认模式(解决方案))
-
- [1. 解决方案](#1. 解决方案)
- [2. 局限性](#2. 局限性)
- [4. return 回退模式](#4. return 回退模式)
一. 消息确认机制
这里我们要知道, 消费者确认消息包括 肯定确认ACK和否定确认NACK, 下面图片中的Unacked指的是未被确认, 意思是既没有肯定确认也没有否定确认才会被放到这个参数里面

1. 自动确认
接收消息时, 在basicAck方法中, 当autoAck参数等于true时, RabbitMQ 会自动把发送出去的消息置为确认, 然后从内存(或者磁盘)中删除, 不管消费者是否正确处理了这些消息, 适合对于消息可靠性要求不高的场景
2. 手动确认
消费者从Broker获取消息后, 万一处理失败, 能再次从Broker获取原来消息, 直到消费者可以正确处理消息后, 再显式调用确认方法发送ACK, Broker再将消息从内存(或者磁盘)中删除, 适合对于消息可靠性要求较高的场景
当autoAck参数置为false, 在RabbitMQ服务端中, 队列中的消息分成了两个部分, 一部分为在队列中还未发送的消息(等待投递的消息), 一部分是已经发送给消费者, 等待消费者ACK确认的消息(已投递, 还未确认), 这种机制就确保了, 当一条消息发送给消费者, 但是未收到确认, 消费者与Broker连接也中断时, 就会将消息重新加入队列

3. 手动确认的方法和参数介绍
1. 肯定确认 Channel.basicAck(long deliveryTag, boolean multiple)
deliveryTag: 消息的唯一标识ID, 64位递增的长整型, 由每个队列独立维护, 不同的通道之间deliveryTag可能相同, 但是不代表是同一个消息, 因此消费者进行消息确认时, 必须使用对应的通道
当 multiple 为true时, 会一次性确认小于等于 deliveryTag 的所有消息, 为false时, 只会确认当前消息
2. 否定确认 Channel.basicReject(long deliveryTag, boolean requeue)
收到消息时, 消费者表示拒绝这个消息
boolean requeue, 为true, 表示拒绝消息后, 将该消息重新添加到队列中, 来继续以供其他消费者使用, 为false, 表示会将该消息直接从队列中移除
3. 批量否定确认 Channel.basicNack(long deliveryTag, boolean multiple, boolean requeue)
可以使用multiple参数为true, 来批量拒绝小于等于 deliveryTag 的消息, 用boolean requeue来决定消息是否重新加入队列
4. 原生sdk代码
在前面我们介绍RPC工作机制中, 在服务器端接收消息时, 我们就使用的是手动确认机制, 代码如下
java
package com.ran.rpc;
import com.rabbitmq.client.*;
import com.ran.constant.Constants;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.UUID;
import java.util.concurrent.TimeoutException;
/**
* Created with IntelliJ IDEA.
* Description:
* User: ran
* Date: 2026-03-22
* Time: 22:26
*/
public class RpcServer {
public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
// 1. 建立TCP连接, 这里使用连接工厂来配置参数
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost(Constants.HOST);
connectionFactory.setPort(Constants.PORT); // RabbitMQ服务的默认端口
connectionFactory.setUsername(Constants.USER_NAME);
connectionFactory.setPassword(Constants.PASSWORD);
connectionFactory.setVirtualHost(Constants.VIRTUAL_HOST);
Connection connection = connectionFactory.newConnection();// new一个连接对象
// 2. 开启通道
Channel channel = connection.createChannel();
// 3. 声明交换机, 使用默认交换机
// 4. 声明队列
channel.queueDeclare(Constants.RPC_REQUEST_QUEUE, true, false, false, null);
channel.queueDeclare(Constants.RPC_RESPONSE_QUEUE, true, false, false, null);
// 5. 接收请求
// 用阻塞队列存储信息
channel.basicQos(1);// 每次只接收一个请求, 防止请求过多混淆
DefaultConsumer consumer = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
String request = new String(body);
String res = "对[" + request + "]做出响应";
// 6. 发送响应
AMQP.BasicProperties props = new AMQP.BasicProperties().builder()
.correlationId(properties.getCorrelationId())
.build();
channel.basicPublish("", Constants.RPC_RESPONSE_QUEUE,props, res.getBytes(StandardCharsets.UTF_8));
System.out.println("响应发送成功");
channel.basicAck(envelope.getDeliveryTag(), false);// 第一个参数deliveryTag为发送消息时自动删除的标签,参数multiple为是否开启批量确认
}
};
channel.basicConsume(Constants.RPC_REQUEST_QUEUE,false,consumer);// 改为手动确认, 自动确认的话, 收到消息就会立马从RabbitMQ删除
}
}
5. SpringAMQP中接口的变化
Spring-AMQP 对消息确认机制提供了三种策略
java
public enum AcknowledgeMode {
NONE,
MANUAL,
AUTO;
private AcknowledgeMode() {
}
}
1. NONE
NONE: 该策略下, RabbitMQ将消息发送给消费者后, 会自动确认消息并从队列中移除, 不论消费者是否成功处理, 与ackAuto类似
(1) yml配置
java
spring:
application:
name: Spring-extension-demo
rabbitmq:
host: localhost
port: 5672
username: admin
password: admin
virtual-host: extension
listener:
simple:
acknowledge-mode: none
(2) 声明队列, 交换机与绑定关系
java
package com.ran.extension.config;
import com.ran.extension.constant.Constants;
import org.springframework.amqp.core.*;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* Created with IntelliJ IDEA.
* Description:
* User: ran
* Date: 2026-03-29
* Time: 15:59
*/
@Configuration
public class RabbitMQConfig {
@Bean("ackQueue")
public Queue ackQueue() {
return QueueBuilder.durable(Constants.ACK_QUEUE).build();
}
@Bean("directExchange")
public DirectExchange directExchange() {
return ExchangeBuilder.directExchange(Constants.ACK_EXCHANGE).durable(true).build();
}
@Bean("ackBinding")
public Binding ackBinding(@Qualifier("directExchange") DirectExchange directExchange, @Qualifier("ackQueue") Queue queue) {
return BindingBuilder.bind(queue).to(directExchange).with("ack");
}
}
(3) 生产者
java
package com.ran.extension.controller;
import com.ran.extension.constant.Constants;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* Created with IntelliJ IDEA.
* Description:
* User: ran
* Date: 2026-03-29
* Time: 16:06
*/
@RestController
@RequestMapping("/producer")
public class ProducerController {
@Autowired
private RabbitTemplate rabbitTemplate;
@RequestMapping("ack")
public String ack() {
rabbitTemplate.convertAndSend(Constants.ACK_EXCHANGE, "ack", "Consumer ack mode ...");
return "发送成功";
}
}
(4) 消费者
这里我们在消费者逻辑这里设置了一行int sum = 1 / 0, 来抛出异常, 来看队列中消息情况
java
package com.ran.extension.listener;
import com.rabbitmq.client.Channel;
import com.ran.extension.constant.Constants;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
/**
* Created with IntelliJ IDEA.
* Description:
* User: ran
* Date: 2026-03-29
* Time: 16:11
*/
@Component
public class Listeners {
@RabbitListener(queues = Constants.ACK_QUEUE)
public void handlerAckMessage(Message message, Channel channel) {
System.out.println("接收到消息: " + new String(message.getBody()) + ", 当前deliveryTag: " + message.getMessageProperties().getDeliveryTag());
System.out.println("业务正在处理");
int sum = 1 / 0;
System.out.println("业务处理完成");
}
}
(5) 运行图
可以看到队列中, 消息已经被删除, 即使消费者业务抛出了异常
2. MANUAL
MANUAL: 该策略下, RabbitMQ将消息发送给消费者后, 会等待消费者成功处理完消息后, 消费者显式调用basicAck方法来确认, 当消息未被确认, RabbitMQ服务器在消费者还可用时, 重新发送该消息, 确保该消息可以被重新处理, 即手动确认
(1) yml配置
java
spring:
application:
name: Spring-extension-demo
rabbitmq:
host: localhost
port: 5672
username: admin
password: admin
virtual-host: extension
listener:
simple:
acknowledge-mode: manual
(2) 消费者代码
java
package com.ran.extension.listener;
import com.rabbitmq.client.Channel;
import com.ran.extension.constant.Constants;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
import java.io.IOException;
/**
* Created with IntelliJ IDEA.
* Description:
* User: ran
* Date: 2026-03-29
* Time: 16:11
*/
@Component
public class Listeners {
@RabbitListener(queues = Constants.ACK_QUEUE)
public void handlerAckMessage(Message message, Channel channel) throws IOException {
long deliveryTag = message.getMessageProperties().getDeliveryTag();
try {
System.out.println("接收到消息: " + new String(message.getBody()) + ", 当前deliveryTag: " + message.getMessageProperties().getDeliveryTag());
System.out.println("业务正在处理");
int sum = 1 / 0;
System.out.println("业务处理完成");
// 手动肯定确认
channel.basicAck(deliveryTag, false);
} catch (Exception e) {
// 否定确认逻辑
channel.basicNack(deliveryTag, false, true);
}
}
}
(3) 运行图
我们设置basicNack方法中的requeue参数为true, 当有异常抛出时, 会自动将消息重新添加到队列并发送, 队列中的Ready中始终为1, 可以看出和AUTO策略有较大差别
3. AUTO
AUTO: 该策略下, 消费者成功处理消息后会自动确认消息, 如果处理失败, 抛出异常后, 不会确认消息, 会自动将消息重发, 这也是Spring-AMQP的默认策略
(1) yml配置
java
spring:
application:
name: Spring-extension-demo
rabbitmq:
host: localhost
port: 5672
username: admin
password: admin
virtual-host: extension
listener:
simple:
acknowledge-mode: auto
(2) 运行图
队列中未被确认(unacked)的消息为1条, 公共(total)一条, 这时RabbitMQ服务器会不断地向消费者重复发送该信息来让消费者处理
deliveryTag几秒钟就递增到了4万
二. 持久化机制
RabbitMQ的持久化机制有三种: 交换器的持久化、队列的持久化和消息的持久化
1. 交换器的持久化
指的是在声明交换机时, 通过durable参数设置为true, 来将交换机的名称等参数配置保存下来, 即使RabbitMQ服务器重启, 也会自动将该交换机重新创建, 在SpringBoot中, 默认开启了交换机持久化
重启RabbitMQ后
java
@Bean("presExchange")
public DirectExchange presExchange() {
return ExchangeBuilder.directExchange(Constants.PRES_EXCHANGE).build();
}
2. 队列持久化
声明队列时, 通过durable参数设置为true, 将队列参数保存到文件中, 即使RabbitMQ重启, 也会自动创建, 在SpringBoot中同样也是默认开启的
重启RabbitMQ后
java
@Bean("presQueue")
public Queue presQueue() {
return QueueBuilder.durable(Constants.PRES_QUEUE).build();
}
@Bean("presBinging")
public Binding presBinging(@Qualifier("presQueue") Queue queue, @Qualifier("presExchange") DirectExchange presExchange) {
return BindingBuilder.bind(queue).to(presExchange).with("pres");
}
3. 消息持久化
在生产者发送消息时, 将DeliveryMode(发送模式)修改为PERSISTENT(持久化), 这里要注意, 只有当队列与消息同时开启持久化时, 重启RabbitMQ时, 消息才不会消失, 这时因为队列是存储消息的载体, 队列如果被删除, 消息也会被删除
重启RabbitMQ后
三. 发布方确认机制
发布方确认机制和我们上一篇博客中, 发布确认-工作模式很相似, 但这里是一种消息可靠性保证的机制之一, 与之还是有点差别的
publisher-confirm机制包含两个模式, confirm确认模式与return回退模式
1. yml参数配置
java
spring:
application:
name: Spring-extension-demo
rabbitmq:
host: localhost
port: 5672
username: admin
password: admin
virtual-host: extension
listener:
simple:
acknowledge-mode: manual # 消息确认机制 手动确认
publisher-confirm-type: correlated # 发送方消息确认机制
2. 单例模式RabbitTemplate设置ConfirmCallback方法的两个问题与解决方法
问题代码👇
java
@RequestMapping("/pres")
public String pres() {
Message message = new Message("Consumer pres mode ...".getBytes(StandardCharsets.UTF_8), new MessageProperties());
message.getMessageProperties().setDeliveryMode(MessageDeliveryMode.PERSISTENT);
rabbitTemplate.convertAndSend(Constants.PRES_EXCHANGE, "pres", message);
return "发送成功";
}
@RequestMapping("/confirm")
public String confirm() {
// 发送方的回调方法
rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
@Override
public void confirm(CorrelationData correlationData, boolean ack, String cause) {
System.out.println("执行了confirm方法...");
// ack为true, 代表RabbitMQ收到了消息
if (ack) {
System.out.println("接收到消息ID: " + (correlationData == null ? null : correlationData.getId()));
}else {
System.out.println("接收到消息ID: " + correlationData.getId() + ", 原因cause: " + cause);
}
System.out.println("---------------------------");
}
});
CorrelationData correlationData = new CorrelationData("1");
rabbitTemplate.convertAndSend(Constants.CONFIRM_EXCHANGE, "confirm", "confirm...",correlationData);
return "发送成功";
}
(1) 问题1
因为我们使用的是SpringBoot自动注入的ConfirmCallback, 是单例对象, 因此当在confirm接口设置了ConfirmCallback方法时, 其他使用了自动注入的ConfirmCallback的接口, 同样也会被监听
这里我们看到, 当我们先调用了confirm接口时, rabbitTemplate设置了ConfirmCallback, 意味着所有使用这个rabbitTemplate的接口都被监听了, 再调用pres接口时, 就会出现ConfirmCallback的执行
(3) 问题2
单例模式创建的RabbitTemplate, 只允许被设置一次ConfirmCallback方法, 因此我们第二次调用confirm接口时, 就会报错
3. confirm确认模式(解决方案)
1. 解决方案
confirm模式是通过ConfirmCallback方法与RabbitMQ的交换机进行沟通, 监听消息的发送, 无论消息是否到达交换机, 都会被执行
问题1使用创建两个RabbitTemplate实例解决
问题2使用在创建RabbitTemplate时, 就设置ConfirmCallback的参数, 来解决多次设置报错的问题
java
package com.ran.extension.config;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* Created with IntelliJ IDEA.
* Description:
* User: ran
* Date: 2026-03-31
* Time: 0:12
*/
@Configuration
public class RabbitTemplateConfig {
@Bean
public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
return new RabbitTemplate(connectionFactory);
}
@Bean
public RabbitTemplate confirmRabbitTemplate(ConnectionFactory connectionFactory) {
RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
// 发送方的回调方法
rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
@Override
public void confirm(CorrelationData correlationData, boolean ack, String cause) {
System.out.println("执行了confirm方法...");
// ack为true, 代表RabbitMQ收到了消息
if (ack) {
System.out.println("接收到消息ID: " + (correlationData == null ? null : correlationData.getId()));
}else {
System.out.println("发生错误,没有接收到消息ID: " + (correlationData == null ? null : correlationData.getId()) + ", 原因cause: " + cause);
}
System.out.println("---------------------------");
}
});
return rabbitTemplate;
}
}
java
@Bean("confirmQueue")
public Queue confirmQueue() {
return QueueBuilder.durable(Constants.CONFIRM_QUEUE).build();
}
@Bean("confirmExchange")
public DirectExchange confirmExchange() {
return ExchangeBuilder.directExchange(Constants.CONFIRM_EXCHANGE).build();
}
@Bean("confirmBinging")
public Binding confirmBinging(@Qualifier("confirmQueue") Queue queue, @Qualifier("confirmExchange") DirectExchange confirmExchange) {
return BindingBuilder.bind(queue).to(confirmExchange).with("confirm");
}
java
@RequestMapping("/confirm")
public String confirm() {
CorrelationData correlationData = new CorrelationData("1");
confirmRabbitTemplate.convertAndSend(Constants.CONFIRM_EXCHANGE, "confirm", "confirm...",correlationData);
return "发送成功";
}
2. 局限性
在confirm模式中, 当RoutingKey配置错误时, 没有与BindingKey对应, 这时本来路由错误, 但是confirm模式只监听消息是否到达交换机, 对于是否路由到队列中, 不会关心
java
@RequestMapping("/confirm")
public String confirm() {
CorrelationData correlationData = new CorrelationData("1");
confirmRabbitTemplate.convertAndSend(Constants.CONFIRM_EXCHANGE, "confirm111", "confirm...",correlationData);
return "发送成功";
}
可以看到当RoutingKey错误时, 打印的日志仍为接收到了消息
4. return 回退模式
当交换机收到消息后, 无法根据RoutingKey来将消息路由到队列上时, 可以使用setMandatory方法来将该消息回退到生产者, 再使用ReturnsCallback方法来监听并处理回退的消息
java
public class ReturnedMessage {
private final Message message;
private final int replyCode;
private final String replyText;
private final String exchange;
private final String routingKey;
}
介绍ReturnedMessage 的参数含义👇
message: 消息对象, 里面包含消息的deliveryTag, 内容等参数
replyCode: RabbitMQ服务器返回的回复码, 每个数字代表不同的含义, 类似于http协议中的状态码
replyText: 无法路由成功的错误原因
exchange: 交换机的名称
routingKey: 生产者发送消息时, 使用的RoutingKey
java
@Bean
public RabbitTemplate confirmRabbitTemplate(ConnectionFactory connectionFactory) {
RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
// 发送方的回调方法
rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
@Override
public void confirm(CorrelationData correlationData, boolean ack, String cause) {
System.out.println("执行了confirm方法...");
// ack为true, 代表RabbitMQ收到了消息
if (ack) {
System.out.println("接收到消息ID: " + (correlationData == null ? null : correlationData.getId()));
}else {
System.out.println("发生错误,没有接收到消息ID: " + (correlationData == null ? null : correlationData.getId()) + ", 原因cause: " + cause);
}
System.out.println("---------------------------");
}
});
// 消息被退回的回调函数
rabbitTemplate.setMandatory(true);
rabbitTemplate.setReturnsCallback(new RabbitTemplate.ReturnsCallback() {
@Override
public void returnedMessage(ReturnedMessage returned) {
System.out.println("消息退回: " + returned);
}
});
return rabbitTemplate;
}
java
@RequestMapping("/returned")
public String returned() {
CorrelationData correlationData = new CorrelationData("666");
confirmRabbitTemplate.convertAndSend(Constants.CONFIRM_EXCHANGE, "confirm111", "returned...",correlationData);
return "发送成功";
}
这里可以看到消息因为RoutingKey的问题被退回, 同时也能发现消息是正常发送到了交换机













