Skip to content

工作队列(竞争消费)

本页结论:工作队列(Work Queue)里每条消息只会被同组中的一个消费者处理,横向扩容靠「加消费者」而不是「加队列」。分发形状由 prefetch / 分区分配决定,失败边界由 ACK / offset 提交时机决定:未被确认的消息会被重投,因此消费端必须按幂等消费设计。

适用场景

  • 任务分发:同类任务(扣库存、发通知、生成报表)由一组消费者实例分摊处理。
  • 削峰:生产速率瞬时高于消费能力时,消息在 Broker 排队,消费者按自己的能力领取(见背压与积压)。
  • 弹性伸缩:高峰期加消费者实例,低谷期缩容,无需改动生产者。

核心模型

竞争边界是「同一个队列 / 同一个消费组」:

  • 队列/消费组内部是竞争消费——一条消息只到一个消费者;
  • 不同队列/不同订阅之间是复制分发——那是发布订阅的语义。

这是消息模型中「点对点」的统一抽象,四个产品只是实现机制不同。

四产品实现对照

产品竞争边界分配机制并行度上限
RabbitMQ同一队列的多个消费者Broker 按 prefetch 与消费者状态推送消费者数(受 prefetch 影响)
Kafka同一 consumer group分区在组内分配,一个分区只归一个消费者分区数(多余消费者空闲)
RocketMQ同一 consumer group(集群模式 Clustering)message queue 在组内均分message queue 数
Pulsar同一 Shared 订阅Broker 轮询/按 key 分发给订阅内消费者消费者数(分区 topic 还受分区数影响)

分发形状:prefetch 与分区

  • RabbitMQbasic.qos(prefetch=N) 决定每个消费者最多同时持有 N 条未确认消息。prefetch 过大(如默认 250 以上)会把消息压在少数「快连接」的消费者上,出现忙闲不均;任务处理时间差异大时建议小 prefetch(如 1~10),让 Broker 按完成节奏补发。
  • Kafka / RocketMQ:并行度上限是分区 / message queue 数。消费者数超过分区数,多出来的实例分不到分区,完全空闲。扩容消费者前要先确认分区数够不够(见容量规划)。
  • 顺序约束:同一分区/队列内按写入顺序投递。需要「同一 orderId 有序」时,竞争消费与顺序语义要一起设计——用 aggregateId 作分区/路由键(见顺序语义)。

失败边界与重投

竞争消费不改变投递语义:

  • RabbitMQ 消费者崩溃,未 ACK 的消息重新入队并投给其他消费者——本仓库实验可复现(见消费者崩溃与重投)。
  • Kafka 消费者崩溃,从上次已提交 offset 起重读,重投单位是「一批」而不是一条(见 Kafka 可靠性)。

结论:at-least-once 下业务必须预期重复,竞争消费越容易崩溃重启,越需要幂等消费兜底。

保证成立的条件 / 不保证什么

  • 条件:手动确认(ACK / 手动提交 offset)+ 合理 prefetch/分区数 + 消费者处理失败有明确的 nack/重试路径(见重试与 DLQ)。
  • 不保证:消息恰好被处理一次;消费者间负载均衡绝对均匀;队列中消息的持久性(取决于 Broker 持久化配置,见各产品可靠性页)。

常见误区

  • 「消费者越多吞吐越高」——Kafka/RocketMQ 受分区数限制,超过后加消费者无效;RabbitMQ 加到争抢大于收益后吞吐反而下降。
  • 「工作队列就是发布订阅」——工作队列一条消息只到一个消费者;要每个下游都收到同一事件,用发布订阅
  • 「消费者拿到消息就算成功」——成功以「业务提交 + 确认」为准,见投递语义

官方资料

以统一实验验证消息系统语义边界