[RabbitMQ] 高级特性实战:从消息堆积问题到事务、限流和负载均衡解决方案

开头

我一开始学习 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 让快消费者更快释放窗口。


七、验证方式

验证事务

  1. 不加 @Transactional
  2. 调用 /producer/trans
  3. 观察队列,第一条消息可能发送成功
  4. 加上 @Transactional
  5. 再次调用接口,异常后消息整体回滚

不添加事务时, 即使消息的发送过程中出现了异常,对于异常前的任务逻辑仍然会进行

接下来先清空队列中的消息, 添加事务机制 @Transactional

验证限流

  1. 设置 prefetch: 5
  2. 注释掉 channel.basicAck
  3. 发送 20 条消息
  4. 控制台只会打印 5 条
  5. 管理台中能看到 15 条 ready,5 条 unacked

验证负载均衡

  1. 设置 prefetch: 1
  2. 启动两个消费者
  3. 其中一个消费者加 Thread.sleep(100)
  4. 发送 20 条消息
  5. 观察日志,快消费者会处理更多消息

八、容易踩坑总结

坑点 现象 解决方式
只配事务模板,不加 @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 高级特性的理解更清楚了:

  1. 事务解决的是"消息发送要么都成功,要么都失败"的问题。
  2. RabbitMQ 默认分发不等于真正的负载均衡。
  3. prefetch 控制的是消费者手里的未确认消息数量。
  4. 限流必须配合手动 ACK,否则效果会打折。
  5. prefetch=5 适合限制单个消费者的处理压力。
  6. prefetch=1 更适合快慢消费者并存的负载均衡场景。
  7. deliveryTag 是 Channel 级别的,多个消费者看到重复编号是正常的。
  8. 真正写代码时,不要只看消息有没有发出去,还要看异常、确认、堆积和消费能力。