开头
我一开始学习 RabbitMQ 高级特性时,最大的困惑不是"怎么发消息",而是:
消息确实发出去了,但它一定成功了吗?
多个消费者一起消费时,为什么有的很忙、有的很闲?
秒杀场景下,MQ 会不会把后端服务直接压垮?
这篇文章我就围绕 RabbitMQ 高级特性中的几个实际问题来整理:事务、消息分发、限流、负载均衡。
一、先介绍背景:不是会发消息就够了
普通的 RabbitMQ 入门代码一般是:
java
rabbitTemplate.convertAndSend("exchange", "routingKey", "hello rabbitmq");
但项目里真正麻烦的是这些问题:
| 问题 | 具体表现 | 对应特性 |
|---|---|---|
| 消息发送一半失败 | 第一条消息发出去了,第二条代码异常 | 事务 |
| 多消费者消费不均衡 | 慢消费者堆积,快消费者空闲 | 消息分发 |
| 瞬时流量太大 | 消费端一次拿太多消息 | 限流 |
| 快慢消费者能力不同 | 希望谁空闲谁多干活 | prefetch 负载均衡 |
二、一个具体案例:我以为多个消费者会自动均衡
我一开始以为,只要启动两个消费者,RabbitMQ 就会自动判断谁处理得快,然后把更多消息发给快的消费者。
但默认情况下,RabbitMQ 更像是"轮询分发":
消息1 -> 消费者A
消息2 -> 消费者B
消息3 -> 消费者A
消息4 -> 消费者B

问题是,如果消费者 B 很慢,消息还是可能提前分给它,导致 B 手里堆着未确认消息,而 A 可能已经空了。
三、问题原因分析
1. 事务解决什么问题?
事务解决的是:一组消息发送操作要么都成功,要么都失败。
例如下面这个场景:
java
rabbitTemplate.convertAndSend("", "trans_queue", "trans test 1...");
int a = 5 / 0;
rabbitTemplate.convertAndSend("", "trans_queue", "trans test 2...");
如果没有事务,第一条消息可能已经进队列了,后面代码异常也不会影响它。
如果加上事务,异常发生后,前面发送的消息也会回滚。
2. 消息分发为什么会不公平?
RabbitMQ 默认会把队列中的消息分发给不同消费者,但它不会主动关心消费者当前处理速度。
也就是说,它不是天然的"谁快谁多拿",而是需要我们通过 prefetch 来限制消费者手里最多能拿多少未确认消息。
四、一步步给出解决方案
1. 开启 RabbitMQ 事务
先配置事务管理器,并让 RabbitTemplate 使用事务信道:
java
@Configuration
public class TransactionConfig {
@Bean
public RabbitTransactionManager transactionManager(
CachingConnectionFactory connectionFactory) {
return new RabbitTransactionManager(connectionFactory);
}
@Bean
public RabbitTemplate rabbitTemplate(
CachingConnectionFactory connectionFactory) {
RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
rabbitTemplate.setChannelTransacted(true);
return rabbitTemplate;
}
}
再声明队列:
java
@Bean("transQueue")
public Queue transQueue() {
return QueueBuilder.durable("trans_queue").build();
}
生产者示例:
java
@Transactional
@RequestMapping("/send")
public String send() {
rabbitTemplate.convertAndSend("", "trans_queue", "trans test 1...");
int a = 5 / 0;
rabbitTemplate.convertAndSend("", "trans_queue", "trans test 2...");
return "发送成功";
}
我这里容易忽略的一点是:
只设置 setChannelTransacted(true) 不够,业务方法还要加 @Transactional。
图 1:事务提交与回滚流程
用途:帮助理解为什么加了事务后,异常会导致已发送消息回滚。
#mermaid-svg-S2TqjsTbnPwz3Zd0{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-S2TqjsTbnPwz3Zd0 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-S2TqjsTbnPwz3Zd0 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-S2TqjsTbnPwz3Zd0 .error-icon{fill:#552222;}#mermaid-svg-S2TqjsTbnPwz3Zd0 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-S2TqjsTbnPwz3Zd0 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-S2TqjsTbnPwz3Zd0 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-S2TqjsTbnPwz3Zd0 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-S2TqjsTbnPwz3Zd0 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-S2TqjsTbnPwz3Zd0 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-S2TqjsTbnPwz3Zd0 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-S2TqjsTbnPwz3Zd0 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-S2TqjsTbnPwz3Zd0 .marker.cross{stroke:#333333;}#mermaid-svg-S2TqjsTbnPwz3Zd0 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-S2TqjsTbnPwz3Zd0 p{margin:0;}#mermaid-svg-S2TqjsTbnPwz3Zd0 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-S2TqjsTbnPwz3Zd0 .cluster-label text{fill:#333;}#mermaid-svg-S2TqjsTbnPwz3Zd0 .cluster-label span{color:#333;}#mermaid-svg-S2TqjsTbnPwz3Zd0 .cluster-label span p{background-color:transparent;}#mermaid-svg-S2TqjsTbnPwz3Zd0 .label text,#mermaid-svg-S2TqjsTbnPwz3Zd0 span{fill:#333;color:#333;}#mermaid-svg-S2TqjsTbnPwz3Zd0 .node rect,#mermaid-svg-S2TqjsTbnPwz3Zd0 .node circle,#mermaid-svg-S2TqjsTbnPwz3Zd0 .node ellipse,#mermaid-svg-S2TqjsTbnPwz3Zd0 .node polygon,#mermaid-svg-S2TqjsTbnPwz3Zd0 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-S2TqjsTbnPwz3Zd0 .rough-node .label text,#mermaid-svg-S2TqjsTbnPwz3Zd0 .node .label text,#mermaid-svg-S2TqjsTbnPwz3Zd0 .image-shape .label,#mermaid-svg-S2TqjsTbnPwz3Zd0 .icon-shape .label{text-anchor:middle;}#mermaid-svg-S2TqjsTbnPwz3Zd0 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-S2TqjsTbnPwz3Zd0 .rough-node .label,#mermaid-svg-S2TqjsTbnPwz3Zd0 .node .label,#mermaid-svg-S2TqjsTbnPwz3Zd0 .image-shape .label,#mermaid-svg-S2TqjsTbnPwz3Zd0 .icon-shape .label{text-align:center;}#mermaid-svg-S2TqjsTbnPwz3Zd0 .node.clickable{cursor:pointer;}#mermaid-svg-S2TqjsTbnPwz3Zd0 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-S2TqjsTbnPwz3Zd0 .arrowheadPath{fill:#333333;}#mermaid-svg-S2TqjsTbnPwz3Zd0 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-S2TqjsTbnPwz3Zd0 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-S2TqjsTbnPwz3Zd0 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-S2TqjsTbnPwz3Zd0 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-S2TqjsTbnPwz3Zd0 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-S2TqjsTbnPwz3Zd0 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-S2TqjsTbnPwz3Zd0 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-S2TqjsTbnPwz3Zd0 .cluster text{fill:#333;}#mermaid-svg-S2TqjsTbnPwz3Zd0 .cluster span{color:#333;}#mermaid-svg-S2TqjsTbnPwz3Zd0 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-S2TqjsTbnPwz3Zd0 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-S2TqjsTbnPwz3Zd0 rect.text{fill:none;stroke-width:0;}#mermaid-svg-S2TqjsTbnPwz3Zd0 .icon-shape,#mermaid-svg-S2TqjsTbnPwz3Zd0 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-S2TqjsTbnPwz3Zd0 .icon-shape p,#mermaid-svg-S2TqjsTbnPwz3Zd0 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-S2TqjsTbnPwz3Zd0 .icon-shape .label rect,#mermaid-svg-S2TqjsTbnPwz3Zd0 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-S2TqjsTbnPwz3Zd0 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-S2TqjsTbnPwz3Zd0 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-S2TqjsTbnPwz3Zd0 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 否
是
请求进入 Producer
开启 RabbitMQ 事务信道
发送消息 1
业务代码是否异常
发送消息 2
事务提交
消息进入队列
事务回滚
消息不进入队列
关键点:
事务把多次发送动作包在一起,只要中间异常,就不会出现"只成功一半"的情况。
2. 配置消费端限流
配置文件中设置手动确认和 prefetch:
spring:
rabbitmq:
listener:
simple:
acknowledge-mode: manual
prefetch: 5
这里的意思是:
每个消费者最多同时持有 5 条未确认消息
消费者代码:
java
@RabbitListener(queues = "qos.queue")
public void listenerQueue(Message message, Channel channel) throws Exception {
long deliveryTag = message.getMessageProperties().getDeliveryTag();
System.out.printf("接收到消息: %s, deliveryTag: %d%n",
new String(message.getBody(), "UTF-8"),
deliveryTag);
// 手动确认
channel.basicAck(deliveryTag, false);
}
如果我把 basicAck 注释掉,那么消费者最多只会收到 5 条消息,剩下的消息会停留在队列中。
图 2:prefetch 限流过程
用途:说明为什么设置 prefetch=5 后,消费者不会一次性拿走所有消息。
#mermaid-svg-KsSP2v8wa0SAsRZ2{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-KsSP2v8wa0SAsRZ2 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-KsSP2v8wa0SAsRZ2 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-KsSP2v8wa0SAsRZ2 .error-icon{fill:#552222;}#mermaid-svg-KsSP2v8wa0SAsRZ2 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-KsSP2v8wa0SAsRZ2 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-KsSP2v8wa0SAsRZ2 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-KsSP2v8wa0SAsRZ2 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-KsSP2v8wa0SAsRZ2 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-KsSP2v8wa0SAsRZ2 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-KsSP2v8wa0SAsRZ2 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-KsSP2v8wa0SAsRZ2 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-KsSP2v8wa0SAsRZ2 .marker.cross{stroke:#333333;}#mermaid-svg-KsSP2v8wa0SAsRZ2 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-KsSP2v8wa0SAsRZ2 p{margin:0;}#mermaid-svg-KsSP2v8wa0SAsRZ2 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-KsSP2v8wa0SAsRZ2 .cluster-label text{fill:#333;}#mermaid-svg-KsSP2v8wa0SAsRZ2 .cluster-label span{color:#333;}#mermaid-svg-KsSP2v8wa0SAsRZ2 .cluster-label span p{background-color:transparent;}#mermaid-svg-KsSP2v8wa0SAsRZ2 .label text,#mermaid-svg-KsSP2v8wa0SAsRZ2 span{fill:#333;color:#333;}#mermaid-svg-KsSP2v8wa0SAsRZ2 .node rect,#mermaid-svg-KsSP2v8wa0SAsRZ2 .node circle,#mermaid-svg-KsSP2v8wa0SAsRZ2 .node ellipse,#mermaid-svg-KsSP2v8wa0SAsRZ2 .node polygon,#mermaid-svg-KsSP2v8wa0SAsRZ2 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-KsSP2v8wa0SAsRZ2 .rough-node .label text,#mermaid-svg-KsSP2v8wa0SAsRZ2 .node .label text,#mermaid-svg-KsSP2v8wa0SAsRZ2 .image-shape .label,#mermaid-svg-KsSP2v8wa0SAsRZ2 .icon-shape .label{text-anchor:middle;}#mermaid-svg-KsSP2v8wa0SAsRZ2 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-KsSP2v8wa0SAsRZ2 .rough-node .label,#mermaid-svg-KsSP2v8wa0SAsRZ2 .node .label,#mermaid-svg-KsSP2v8wa0SAsRZ2 .image-shape .label,#mermaid-svg-KsSP2v8wa0SAsRZ2 .icon-shape .label{text-align:center;}#mermaid-svg-KsSP2v8wa0SAsRZ2 .node.clickable{cursor:pointer;}#mermaid-svg-KsSP2v8wa0SAsRZ2 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-KsSP2v8wa0SAsRZ2 .arrowheadPath{fill:#333333;}#mermaid-svg-KsSP2v8wa0SAsRZ2 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-KsSP2v8wa0SAsRZ2 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-KsSP2v8wa0SAsRZ2 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-KsSP2v8wa0SAsRZ2 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-KsSP2v8wa0SAsRZ2 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-KsSP2v8wa0SAsRZ2 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-KsSP2v8wa0SAsRZ2 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-KsSP2v8wa0SAsRZ2 .cluster text{fill:#333;}#mermaid-svg-KsSP2v8wa0SAsRZ2 .cluster span{color:#333;}#mermaid-svg-KsSP2v8wa0SAsRZ2 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-KsSP2v8wa0SAsRZ2 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-KsSP2v8wa0SAsRZ2 rect.text{fill:none;stroke-width:0;}#mermaid-svg-KsSP2v8wa0SAsRZ2 .icon-shape,#mermaid-svg-KsSP2v8wa0SAsRZ2 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-KsSP2v8wa0SAsRZ2 .icon-shape p,#mermaid-svg-KsSP2v8wa0SAsRZ2 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-KsSP2v8wa0SAsRZ2 .icon-shape .label rect,#mermaid-svg-KsSP2v8wa0SAsRZ2 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-KsSP2v8wa0SAsRZ2 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-KsSP2v8wa0SAsRZ2 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-KsSP2v8wa0SAsRZ2 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 是
否
队列中有 20 条消息
消费者拉取消息
未确认消息数量 < prefetch?
继续投递
消费者持有 5 条未确认消息
RabbitMQ 暂停投递
消费者 basicAck
未确认数量减少
关键点:
prefetch 控制的是"未确认消息数量",所以它必须配合手动 ACK 才有意义。
五、补充代码示例
1. 交换机、队列、绑定配置
java
@Configuration
public class QosConfig {
@Bean("qosExchange")
public Exchange qosExchange() {
return ExchangeBuilder
.directExchange("qos.exchange")
.durable(true)
.build();
}
@Bean("qosQueue")
public Queue qosQueue() {
return QueueBuilder
.durable("qos.queue")
.build();
}
@Bean("qosBinding")
public Binding qosBinding(
@Qualifier("qosExchange") Exchange exchange,
@Qualifier("qosQueue") Queue queue) {
return BindingBuilder
.bind(queue)
.to(exchange)
.with("qos")
.noargs();
}
}
2. 一次发送 20 条消息
java
@RequestMapping("/qos")
public String qos() {
for (int i = 0; i < 20; i++) {
rabbitTemplate.convertAndSend(
"qos.exchange",
"qos",
"qos test..." + i
);
}
return "发送成功";
}
六、负载均衡:让快消费者多处理
如果有两个消费者,一个很快,一个很慢,我更推荐把 prefetch 设置为 1:
spring:
rabbitmq:
listener:
simple:
acknowledge-mode: manual
prefetch: 1
意思是:
一个消费者处理并确认完上一条消息之前,不再给它新消息
慢消费者示例:
java
@RabbitListener(queues = "qos.queue")
public void listenerQueue2(Message message, Channel channel) throws Exception {
long deliveryTag = message.getMessageProperties().getDeliveryTag();
System.out.printf("消费者2接收到消息: %s, deliveryTag: %d%n",
new String(message.getBody(), "UTF-8"),
deliveryTag);
Thread.sleep(100);
channel.basicAck(deliveryTag, false);
}
图 3:prefetch=1 实现相对公平分发
用途:展示快消费者为什么能拿到更多消息。
慢消费者 快消费者 RabbitMQ Queue 慢消费者 快消费者 RabbitMQ Queue #mermaid-svg-hpxuaTTNpdyPvg91{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-hpxuaTTNpdyPvg91 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-hpxuaTTNpdyPvg91 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-hpxuaTTNpdyPvg91 .error-icon{fill:#552222;}#mermaid-svg-hpxuaTTNpdyPvg91 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-hpxuaTTNpdyPvg91 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-hpxuaTTNpdyPvg91 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-hpxuaTTNpdyPvg91 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-hpxuaTTNpdyPvg91 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-hpxuaTTNpdyPvg91 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-hpxuaTTNpdyPvg91 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-hpxuaTTNpdyPvg91 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-hpxuaTTNpdyPvg91 .marker.cross{stroke:#333333;}#mermaid-svg-hpxuaTTNpdyPvg91 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-hpxuaTTNpdyPvg91 p{margin:0;}#mermaid-svg-hpxuaTTNpdyPvg91 .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-hpxuaTTNpdyPvg91 text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-hpxuaTTNpdyPvg91 .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-hpxuaTTNpdyPvg91 .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-hpxuaTTNpdyPvg91 .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-hpxuaTTNpdyPvg91 .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-hpxuaTTNpdyPvg91 #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-hpxuaTTNpdyPvg91 .sequenceNumber{fill:white;}#mermaid-svg-hpxuaTTNpdyPvg91 #sequencenumber{fill:#333;}#mermaid-svg-hpxuaTTNpdyPvg91 #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-hpxuaTTNpdyPvg91 .messageText{fill:#333;stroke:none;}#mermaid-svg-hpxuaTTNpdyPvg91 .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-hpxuaTTNpdyPvg91 .labelText,#mermaid-svg-hpxuaTTNpdyPvg91 .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-hpxuaTTNpdyPvg91 .loopText,#mermaid-svg-hpxuaTTNpdyPvg91 .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-hpxuaTTNpdyPvg91 .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-hpxuaTTNpdyPvg91 .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-hpxuaTTNpdyPvg91 .noteText,#mermaid-svg-hpxuaTTNpdyPvg91 .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-hpxuaTTNpdyPvg91 .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-hpxuaTTNpdyPvg91 .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-hpxuaTTNpdyPvg91 .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-hpxuaTTNpdyPvg91 .actorPopupMenu{position:absolute;}#mermaid-svg-hpxuaTTNpdyPvg91 .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-hpxuaTTNpdyPvg91 .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-hpxuaTTNpdyPvg91 .actor-man circle,#mermaid-svg-hpxuaTTNpdyPvg91 line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-hpxuaTTNpdyPvg91 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 投递消息1投递消息2basicAck 消息1投递消息3basicAck 消息3投递消息4basicAck 消息2投递消息5
关键点:
不是 RabbitMQ 主动识别"谁快",而是 prefetch=1 + 手动 ACK 让快消费者更快释放窗口。
七、验证方式
验证事务
- 不加
@Transactional - 调用
/producer/trans - 观察队列,第一条消息可能发送成功
- 加上
@Transactional - 再次调用接口,异常后消息整体回滚

不添加事务时, 即使消息的发送过程中出现了异常,对于异常前的任务逻辑仍然会进行
接下来先清空队列中的消息, 添加事务机制 @Transactional

验证限流
- 设置
prefetch: 5 - 注释掉
channel.basicAck - 发送 20 条消息
- 控制台只会打印 5 条
- 管理台中能看到 15 条 ready,5 条 unacked

验证负载均衡
- 设置
prefetch: 1 - 启动两个消费者
- 其中一个消费者加
Thread.sleep(100) - 发送 20 条消息
- 观察日志,快消费者会处理更多消息

八、容易踩坑总结
| 坑点 | 现象 | 解决方式 |
|---|---|---|
只配事务模板,不加 @Transactional |
异常后消息仍可能发送 | 方法上加事务注解 |
只设置 prefetch,但自动 ACK |
限流效果不明显 | 改成手动 ACK |
忘记 basicAck |
消息一直 unacked | 业务成功后确认 |
prefetch=0 |
没有限流上限 | 设置具体数值 |
| 多个消费者 deliveryTag 重复 | 以为消息重复 | deliveryTag 是按 Channel 独立计数 |
| 慢消费者堆积 | 快消费者没活干 | 使用 prefetch=1 |
九、可复用模板
1. 事务发送模板
java
@Transactional
public void sendInTransaction() {
rabbitTemplate.convertAndSend("", "queue.name", "message 1");
rabbitTemplate.convertAndSend("", "queue.name", "message 2");
}
2. 手动 ACK 消费模板
java
@RabbitListener(queues = "queue.name")
public void consume(Message message, Channel channel) throws Exception {
long deliveryTag = message.getMessageProperties().getDeliveryTag();
try {
// 业务处理
channel.basicAck(deliveryTag, false);
} catch (Exception e) {
//multiple : false , requeue : true
channel.basicNack(deliveryTag, false, true);
}
}
3. 限流配置模板
spring:
rabbitmq:
listener:
simple:
acknowledge-mode: manual
prefetch: 5
4. 负载均衡配置模板
spring:
rabbitmq:
listener:
simple:
acknowledge-mode: manual
prefetch: 1
结尾总结
这次学习下来,我对 RabbitMQ 高级特性的理解更清楚了:
- 事务解决的是"消息发送要么都成功,要么都失败"的问题。
- RabbitMQ 默认分发不等于真正的负载均衡。
prefetch控制的是消费者手里的未确认消息数量。- 限流必须配合手动 ACK,否则效果会打折。
prefetch=5适合限制单个消费者的处理压力。prefetch=1更适合快慢消费者并存的负载均衡场景。deliveryTag是 Channel 级别的,多个消费者看到重复编号是正常的。- 真正写代码时,不要只看消息有没有发出去,还要看异常、确认、堆积和消费能力。