开头
我学习 RabbitMQ 高级特性时,一开始以为只要把队列设置成 durable(true),消息就不会丢了。后来在测试 RabbitMQ 重启、交换机写错、路由键写错这些场景时才发现:队列持久化、消息持久化、发送方确认解决的是不同阶段的问题。
这篇文章主要解决两个问题:
- RabbitMQ 重启后,为什么有些消息还会丢?
- 生产者发送消息后,如何知道消息真的到达了 RabbitMQ?
一、先介绍背景:消息到底可能丢在哪?
我最开始遇到的场景是:生产者调用接口提示"发送成功",但消费者没有收到消息。排查后发现,"发送成功"并不等于消息已经可靠进入队列。
RabbitMQ 一条消息大致会经历:
#mermaid-svg-rVaneqEgNaCPXpNw{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-rVaneqEgNaCPXpNw .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-rVaneqEgNaCPXpNw .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-rVaneqEgNaCPXpNw .error-icon{fill:#552222;}#mermaid-svg-rVaneqEgNaCPXpNw .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-rVaneqEgNaCPXpNw .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-rVaneqEgNaCPXpNw .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-rVaneqEgNaCPXpNw .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-rVaneqEgNaCPXpNw .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-rVaneqEgNaCPXpNw .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-rVaneqEgNaCPXpNw .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-rVaneqEgNaCPXpNw .marker{fill:#333333;stroke:#333333;}#mermaid-svg-rVaneqEgNaCPXpNw .marker.cross{stroke:#333333;}#mermaid-svg-rVaneqEgNaCPXpNw svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-rVaneqEgNaCPXpNw p{margin:0;}#mermaid-svg-rVaneqEgNaCPXpNw .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-rVaneqEgNaCPXpNw .cluster-label text{fill:#333;}#mermaid-svg-rVaneqEgNaCPXpNw .cluster-label span{color:#333;}#mermaid-svg-rVaneqEgNaCPXpNw .cluster-label span p{background-color:transparent;}#mermaid-svg-rVaneqEgNaCPXpNw .label text,#mermaid-svg-rVaneqEgNaCPXpNw span{fill:#333;color:#333;}#mermaid-svg-rVaneqEgNaCPXpNw .node rect,#mermaid-svg-rVaneqEgNaCPXpNw .node circle,#mermaid-svg-rVaneqEgNaCPXpNw .node ellipse,#mermaid-svg-rVaneqEgNaCPXpNw .node polygon,#mermaid-svg-rVaneqEgNaCPXpNw .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-rVaneqEgNaCPXpNw .rough-node .label text,#mermaid-svg-rVaneqEgNaCPXpNw .node .label text,#mermaid-svg-rVaneqEgNaCPXpNw .image-shape .label,#mermaid-svg-rVaneqEgNaCPXpNw .icon-shape .label{text-anchor:middle;}#mermaid-svg-rVaneqEgNaCPXpNw .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-rVaneqEgNaCPXpNw .rough-node .label,#mermaid-svg-rVaneqEgNaCPXpNw .node .label,#mermaid-svg-rVaneqEgNaCPXpNw .image-shape .label,#mermaid-svg-rVaneqEgNaCPXpNw .icon-shape .label{text-align:center;}#mermaid-svg-rVaneqEgNaCPXpNw .node.clickable{cursor:pointer;}#mermaid-svg-rVaneqEgNaCPXpNw .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-rVaneqEgNaCPXpNw .arrowheadPath{fill:#333333;}#mermaid-svg-rVaneqEgNaCPXpNw .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-rVaneqEgNaCPXpNw .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-rVaneqEgNaCPXpNw .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-rVaneqEgNaCPXpNw .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-rVaneqEgNaCPXpNw .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-rVaneqEgNaCPXpNw .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-rVaneqEgNaCPXpNw .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-rVaneqEgNaCPXpNw .cluster text{fill:#333;}#mermaid-svg-rVaneqEgNaCPXpNw .cluster span{color:#333;}#mermaid-svg-rVaneqEgNaCPXpNw div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-rVaneqEgNaCPXpNw .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-rVaneqEgNaCPXpNw rect.text{fill:none;stroke-width:0;}#mermaid-svg-rVaneqEgNaCPXpNw .icon-shape,#mermaid-svg-rVaneqEgNaCPXpNw .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-rVaneqEgNaCPXpNw .icon-shape p,#mermaid-svg-rVaneqEgNaCPXpNw .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-rVaneqEgNaCPXpNw .icon-shape .label rect,#mermaid-svg-rVaneqEgNaCPXpNw .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-rVaneqEgNaCPXpNw .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-rVaneqEgNaCPXpNw .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-rVaneqEgNaCPXpNw :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 发送失败
路由失败
服务重启丢失
消费异常
生产者 Producer
交换机 Exchange
队列 Queue
消费者 Consumer
Confirm 确认
Return 退回
持久化
手动 Ack
这张图的关键是:
持久化 主要解决消息进入 RabbitMQ 后,服务异常重启导致的数据丢失;
发送方确认 主要解决生产者不知道消息有没有到达 RabbitMQ 的问题。
二、用一个具体案例说明问题
我做了一个简单接口:
java
rabbitTemplate.convertAndSend("confirm_exchange", "confirm", "confirm test...");
我一开始只关注消费者有没有打印日志,后来故意把交换机名字写错:
java
rabbitTemplate.convertAndSend("confirm_exchange1", "confirm", "confirm test...");
结果消费者肯定收不到消息,但接口本身不一定能直观看出业务层面的可靠失败。
这时问题就来了:
| 场景 | 只做持久化能解决吗 | 需要什么机制 |
|---|---|---|
| RabbitMQ 重启后队列丢失 | 能,队列持久化 | Queue durable |
| RabbitMQ 重启后消息丢失 | 能,消息持久化 | Message persistent |
| 交换机不存在 | 不能 | ConfirmCallback |
| 路由键匹配不到队列 | 不能 | ReturnCallback |
| 消费者处理失败 | 不能 | 手动 Ack / Nack |
三、分析问题出现的原因
1. 队列持久化不等于消息持久化
这里很容易误解。我一开始以为:
java
QueueBuilder.durable("ack_queue").build();
通过durable建立持久化的队列,就能保证消息不丢。
实际上它只保证:RabbitMQ 重启后,队列这个元数据还在 。
如果消息本身不是持久化的,RabbitMQ 重启后消息仍然可能丢失。
2. 消息没到 RabbitMQ,持久化也没用
持久化的前提是消息已经进入 RabbitMQ。
如果生产者发送过程中网络异常、交换机不存在,消息根本没到 Broker,就谈不上落盘。
所以还需要发送方确认机制:
Queue Exchange Producer Queue Exchange Producer #mermaid-svg-SqLdED3qxr4WB4eJ{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-SqLdED3qxr4WB4eJ .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-SqLdED3qxr4WB4eJ .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-SqLdED3qxr4WB4eJ .error-icon{fill:#552222;}#mermaid-svg-SqLdED3qxr4WB4eJ .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-SqLdED3qxr4WB4eJ .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-SqLdED3qxr4WB4eJ .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-SqLdED3qxr4WB4eJ .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-SqLdED3qxr4WB4eJ .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-SqLdED3qxr4WB4eJ .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-SqLdED3qxr4WB4eJ .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-SqLdED3qxr4WB4eJ .marker{fill:#333333;stroke:#333333;}#mermaid-svg-SqLdED3qxr4WB4eJ .marker.cross{stroke:#333333;}#mermaid-svg-SqLdED3qxr4WB4eJ svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-SqLdED3qxr4WB4eJ p{margin:0;}#mermaid-svg-SqLdED3qxr4WB4eJ .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-SqLdED3qxr4WB4eJ text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-SqLdED3qxr4WB4eJ .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-SqLdED3qxr4WB4eJ .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-SqLdED3qxr4WB4eJ .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-SqLdED3qxr4WB4eJ .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-SqLdED3qxr4WB4eJ #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-SqLdED3qxr4WB4eJ .sequenceNumber{fill:white;}#mermaid-svg-SqLdED3qxr4WB4eJ #sequencenumber{fill:#333;}#mermaid-svg-SqLdED3qxr4WB4eJ #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-SqLdED3qxr4WB4eJ .messageText{fill:#333;stroke:none;}#mermaid-svg-SqLdED3qxr4WB4eJ .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-SqLdED3qxr4WB4eJ .labelText,#mermaid-svg-SqLdED3qxr4WB4eJ .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-SqLdED3qxr4WB4eJ .loopText,#mermaid-svg-SqLdED3qxr4WB4eJ .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-SqLdED3qxr4WB4eJ .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-SqLdED3qxr4WB4eJ .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-SqLdED3qxr4WB4eJ .noteText,#mermaid-svg-SqLdED3qxr4WB4eJ .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-SqLdED3qxr4WB4eJ .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-SqLdED3qxr4WB4eJ .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-SqLdED3qxr4WB4eJ .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-SqLdED3qxr4WB4eJ .actorPopupMenu{position:absolute;}#mermaid-svg-SqLdED3qxr4WB4eJ .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-SqLdED3qxr4WB4eJ .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-SqLdED3qxr4WB4eJ .actor-man circle,#mermaid-svg-SqLdED3qxr4WB4eJ line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-SqLdED3qxr4WB4eJ :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} alt路由成功路由失败 发送消息ConfirmCallback ack=true/false根据 routingKey 路由入队成功ReturnCallback 返回消息
关键节点解释:
ConfirmCallback关注消息有没有到达交换机。ReturnCallback关注消息到达交换机后,有没有路由到队列。- 两者配合,才能覆盖生产者到队列前的主要异常。
四、一步步给出解决方案
1. 交换机持久化
交换机持久化通过 durable(true) 设置。
java
@Bean("presExchange")
public DirectExchange presExchange(){
return ExchangeBuilder.directExchange(Constants.PRES_EXCHANGE)
.durable(false)
.build();
}
它解决的问题是:RabbitMQ 重启后,交换机不会消失。
2. 队列持久化
java
@Bean("presQueue")
public Queue presQueue(){
return QueueBuilder
.durable(Constants.PRES_QUEUE)
.build();
}
如果不设置队列持久化,RabbitMQ 重启后队列会被删除,消息自然也没有地方保存。
3. 建立绑定关系
java
@Bean("presBinding")
public Binding presBinding(@Qualifier("presQueue") Queue queue, @Qualifier("presExchange") Exchange exchange){
return BindingBuilder
.bind(queue)
.to(exchange)
.with("pres")
.noargs();
}
绑定队列和交换机, 同时设置routingKey "pres" ,noargs( )表示不添加额外的绑定参数
4. 消息持久化
使用 Java Client 时可以这样写:
java
@RequestMapping("/pres")
public String pres() {
Message message = new Message("Presistent test...".getBytes(), new MessageProperties());
//消息非持久化
//message.getMessageProperties().setDeliveryMode(MessageDeliveryMode.NON_PERSISTENT);
//消息持久化
message.getMessageProperties().setDeliveryMode(MessageDeliveryMode.PERSISTENT);
System.out.println(message);
rabbitTemplate.convertAndSend(Constants.PRES_EXCHANGE, "pres", message);
return "消息发送成功";
}
这里我使用了手动确认的机制,channel.basicNack(deliveryTag, false, false)在消息处理异常时不让他重新回到队列中
java
@Component
public class PresListener {
@RabbitListener(queues = Constants.PRES_QUEUE)
public void handMessage(Message message, Channel channel) throws Exception {
long deliveryTag = message.getMessageProperties().getDeliveryTag();
try {
System.out.printf("接收到消息 : %s , deliverTag : %d%n",
new String(message.getBody(), StandardCharsets.UTF_8),
deliveryTag);
System.out.println("业务逻辑处理");
System.out.println("业务处理完成");
channel.basicAck(deliveryTag, false);
} catch (Exception e) {
System.out.println("消息处理失败 :" + e.getMessage());
channel.basicNack(deliveryTag, false, false);
}
}
}
5. 测试,验证持久化是否生效
先查看消息的发送,消费功能是否正常:

随后我把@RabbitListener这个注解给注释掉,让消息在队列中存储不被消费,随后重启服务, 调用接口发送消息,看队列和消息是否还在

随后我把docker中的RabbitMQ服务重启
java
docker restart rabbitmq

这表明我们的消息持久化已经成功实现了
五、补充发送方确认代码示例
1. 开启 Confirm 模式
java
spring:
rabbitmq:
addresses: amqp://userName:password@RabbitMQ ip地址:5672/vhost
listener:
simple:
acknowledge-mode: manual
publisher-confirm-type: correlated
publisher-confirm-type: correlated 的作用是开启发送方确认,并允许通过 CorrelationData 关联消息。
2. 配置 ConfirmCallback
java
@Bean("confirmRabbitTemplate")
public RabbitTemplate confirmRabbitTemplate(ConnectionFactory connectionFactory) {
RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
rabbitTemplate.setConfirmCallback((correlationData, ack, cause) -> {
if (ack) {
System.out.printf("消息发送到交换机成功, id:%s%n", correlationData.getId());
} else {
System.out.printf("消息发送到交换机失败, id:%s, cause:%s%n",
correlationData.getId(), cause);
}
});
return rabbitTemplate;
}
发送消息:
java
@RequestMapping("/confirm")
public String confirm(){
// 1. 创建关联数据对象,ID为"1"
CorrelationData correlationData = new CorrelationData("1");
// 2. 使用带确认回调的 RabbitTemplate 发送消息
confirmRabbitTemplate.convertAndSend(Constants.CONFIRM_EXCHANGE, "confirm", "hello confirm", correlationData);
return "消息发送成功";
}
如果交换机不存在,ack 会是 false,cause 中会提示类似 no exchange。
测试交换机不存在

接下来先把exchange修改, 测试一下ConfirmCallback

可以看到交换机不存在直接报错了
3. 配置 ReturnCallback
Confirm 只能判断消息有没有到交换机。
如果交换机存在,但路由键写错了,消息仍然可能进不了队列。
这时需要 Return 模式:
java
@Bean("confirmRabbitTemplate")
public RabbitTemplate confirmRabbitTemplate(CachingConnectionFactory connectionFactory) {
RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
rabbitTemplate.setMandatory(true);
rabbitTemplate.setReturnsCallback(returned -> {
System.out.println("消息被退回:");
System.out.println("replyCode = " + returned.getReplyCode());
System.out.println("replyText = " + returned.getReplyText());
System.out.println("exchange = " + returned.getExchange());
System.out.println("routingKey = " + returned.getRoutingKey());
});
return rabbitTemplate;
}
测试路由失败:

交换机确认成功,但是没有队列绑定 confirm111,就会触发 ReturnCallback。
六、插入必要的 Mermaid 图示
下面这张图我用来梳理 RabbitMQ 持久化到底要配哪几层。
我一开始只记住了"队列要 durable",但后来发现,真正想降低 RabbitMQ 重启带来的消息丢失风险,需要把交换机、队列、消息三部分放在一起看。
#mermaid-svg-UdKm4ZTy1tbMJWBn{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-UdKm4ZTy1tbMJWBn .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-UdKm4ZTy1tbMJWBn .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-UdKm4ZTy1tbMJWBn .error-icon{fill:#552222;}#mermaid-svg-UdKm4ZTy1tbMJWBn .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-UdKm4ZTy1tbMJWBn .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-UdKm4ZTy1tbMJWBn .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-UdKm4ZTy1tbMJWBn .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-UdKm4ZTy1tbMJWBn .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-UdKm4ZTy1tbMJWBn .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-UdKm4ZTy1tbMJWBn .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-UdKm4ZTy1tbMJWBn .marker{fill:#333333;stroke:#333333;}#mermaid-svg-UdKm4ZTy1tbMJWBn .marker.cross{stroke:#333333;}#mermaid-svg-UdKm4ZTy1tbMJWBn svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-UdKm4ZTy1tbMJWBn p{margin:0;}#mermaid-svg-UdKm4ZTy1tbMJWBn .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-UdKm4ZTy1tbMJWBn .cluster-label text{fill:#333;}#mermaid-svg-UdKm4ZTy1tbMJWBn .cluster-label span{color:#333;}#mermaid-svg-UdKm4ZTy1tbMJWBn .cluster-label span p{background-color:transparent;}#mermaid-svg-UdKm4ZTy1tbMJWBn .label text,#mermaid-svg-UdKm4ZTy1tbMJWBn span{fill:#333;color:#333;}#mermaid-svg-UdKm4ZTy1tbMJWBn .node rect,#mermaid-svg-UdKm4ZTy1tbMJWBn .node circle,#mermaid-svg-UdKm4ZTy1tbMJWBn .node ellipse,#mermaid-svg-UdKm4ZTy1tbMJWBn .node polygon,#mermaid-svg-UdKm4ZTy1tbMJWBn .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-UdKm4ZTy1tbMJWBn .rough-node .label text,#mermaid-svg-UdKm4ZTy1tbMJWBn .node .label text,#mermaid-svg-UdKm4ZTy1tbMJWBn .image-shape .label,#mermaid-svg-UdKm4ZTy1tbMJWBn .icon-shape .label{text-anchor:middle;}#mermaid-svg-UdKm4ZTy1tbMJWBn .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-UdKm4ZTy1tbMJWBn .rough-node .label,#mermaid-svg-UdKm4ZTy1tbMJWBn .node .label,#mermaid-svg-UdKm4ZTy1tbMJWBn .image-shape .label,#mermaid-svg-UdKm4ZTy1tbMJWBn .icon-shape .label{text-align:center;}#mermaid-svg-UdKm4ZTy1tbMJWBn .node.clickable{cursor:pointer;}#mermaid-svg-UdKm4ZTy1tbMJWBn .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-UdKm4ZTy1tbMJWBn .arrowheadPath{fill:#333333;}#mermaid-svg-UdKm4ZTy1tbMJWBn .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-UdKm4ZTy1tbMJWBn .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-UdKm4ZTy1tbMJWBn .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-UdKm4ZTy1tbMJWBn .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-UdKm4ZTy1tbMJWBn .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-UdKm4ZTy1tbMJWBn .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-UdKm4ZTy1tbMJWBn .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-UdKm4ZTy1tbMJWBn .cluster text{fill:#333;}#mermaid-svg-UdKm4ZTy1tbMJWBn .cluster span{color:#333;}#mermaid-svg-UdKm4ZTy1tbMJWBn div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-UdKm4ZTy1tbMJWBn .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-UdKm4ZTy1tbMJWBn rect.text{fill:none;stroke-width:0;}#mermaid-svg-UdKm4ZTy1tbMJWBn .icon-shape,#mermaid-svg-UdKm4ZTy1tbMJWBn .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-UdKm4ZTy1tbMJWBn .icon-shape p,#mermaid-svg-UdKm4ZTy1tbMJWBn .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-UdKm4ZTy1tbMJWBn .icon-shape .label rect,#mermaid-svg-UdKm4ZTy1tbMJWBn .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-UdKm4ZTy1tbMJWBn .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-UdKm4ZTy1tbMJWBn .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-UdKm4ZTy1tbMJWBn :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} RabbitMQ 持久化配置
交换机持久化
队列持久化
消息持久化
声明交换机时设置 durable true
RabbitMQ 重启后交换机仍然存在
声明队列时使用 QueueBuilder.durable
RabbitMQ 重启后队列仍然存在
发送消息时设置 PERSISTENT
消息会尽量写入磁盘保存
注意事项
只持久化交换机不够
只持久化队列不够
只持久化消息也不够
持久化会降低一部分吞吐量
这张图里最关键的是中间三条线:
-
交换机持久化
解决的是 RabbitMQ 重启后,交换机元数据是否还存在的问题。
-
队列持久化
解决的是 RabbitMQ 重启后,队列本身是否还存在的问题。
-
消息持久化
解决的是消息进入队列后,是否尽量保存到磁盘的问题。
这里容易忽略的是:持久化不是只配一个地方,而是交换机、队列、消息要结合使用。
比如只写了队列持久化:
QueueBuilder.durable("confirm_queue").build();
这只能保证队列重启后还在,并不代表消息本身一定还在。
更完整的写法应该像这样:
@Bean
public Exchange confirmExchange() {
return ExchangeBuilder
.topicExchange("confirm_exchange")
.durable(true)
.build();
}
@Bean
public Queue confirmQueue() {
return QueueBuilder
.durable("confirm_queue")
.build();
}
发送关键消息时,再把消息本身设置为持久化:
MessageProperties properties = new MessageProperties();
properties.setDeliveryMode(MessageDeliveryMode.PERSISTENT);
Message message = new Message("important message".getBytes(), properties);
rabbitTemplate.convertAndSend("confirm_exchange", "confirm", message);
我自己的理解是:
交换机和队列持久化,保证"容器"重启后还在;消息持久化,保证"内容"尽量不丢。
七、说明验证方式
我一般按下面几个场景验证:
-
正常发送
观察 ConfirmCallback 是否
ack=true。 -
交换机写错
把
confirm_exchange改成confirm_exchange1,确认是否进入ack=false分支。 -
路由键写错
把
confirm改成confirm11,确认是否触发 ReturnCallback。 -
RabbitMQ 重启
发送持久化消息后重启 RabbitMQ,查看队列和消息是否还在。
-
管理页面观察
重点看队列中的
Ready、Unacked状态。
八、总结容易踩坑的地方
durable(true)只代表交换机或队列持久化,不代表消息一定持久化。- 消息持久化必须配合队列持久化,否则队列没了,消息也没地方恢复。
- ConfirmCallback 只确认消息是否到达 Exchange。
- ReturnCallback 才能发现消息是否无法路由到 Queue。
mandatory默认是false,不设置的话,路由失败的消息可能不会回调给生产者。- 所有消息都持久化会影响性能,关键业务才更适合强可靠配置。
- 持久化也不是 100% 绝对可靠,极端情况下消息还没落盘 RabbitMQ 就宕机,仍可能丢失。
九、几套可以直接复用的模板
1. 可靠投递配置模板
yml
spring:
rabbitmq:
addresses: amqp://user:password@host:5672/vhost
listener:
simple:
acknowledge-mode: manual
publisher-confirm-type: correlated
2. 持久化队列和交换机模板
java
@Bean
public Exchange exchange() {
return ExchangeBuilder.topicExchange("biz_exchange").durable(true).build();
}
@Bean
public Queue queue() {
return QueueBuilder.durable("biz_queue").build();
}
@Bean
public Binding binding(Exchange exchange, Queue queue) {
return BindingBuilder.bind(queue).to(exchange).with("biz.key").noargs();
}
3. 消息持久化模板
java
MessageProperties properties = new MessageProperties();
properties.setDeliveryMode(MessageDeliveryMode.PERSISTENT);
Message message = new Message("business message".getBytes(), properties);
rabbitTemplate.convertAndSend("biz_exchange", "biz.key", message);
4. 排查提示词模板
我现在 RabbitMQ 消息没有被消费者收到。
请按以下链路帮我排查:
1. 生产者是否成功发送到 Exchange
2. ConfirmCallback 是否 ack=true
3. routingKey 是否匹配队列绑定
4. ReturnCallback 是否被触发
5. Queue 和 Message 是否都开启持久化
6. 消费者是否开启手动 ack
结尾总结
- 我学习这部分最大的收获是:消息可靠性不是一个配置解决的,而是一条链路一起保证。
- 持久化解决的是 RabbitMQ 服务异常后数据尽量不丢的问题。
- ConfirmCallback 解决的是消息有没有到达交换机的问题。
- ReturnCallback 解决的是消息有没有成功路由到队列的问题。
- 只设置队列持久化不够,消息本身也要设置为持久化。
- 关键业务更推荐开启持久化、Confirm、Return、消费者手动 Ack。
- 可靠性和吞吐量需要权衡,不是所有消息都必须走最高可靠级别。