Skip to content

RabbitMQ 路由与分发

本页结论:消息永远先到达 Exchange,再由 Binding 决定进入哪些队列;五种 Exchange 类型的路由差异决定了竞争消费、广播与模式匹配三种分发形态。

适用场景

  • 任务分发(一个队列、多个竞争消费者)。
  • 事件广播(一条事件复制给多个独立订阅)。
  • 按业务键路由(地区、租户、事件子类型)。

核心模型:Exchange → Binding → Queue

  • 同一条消息可以进入多个队列 → 各队列的订阅者都能收到(广播)。
  • 同一个队列的多个消费者 → 每条消息只给其中一个(竞争消费,Work Queue)。
  • 广播与竞争消费可以同时存在:先复制到队列,再在队列内竞争。

五种 Exchange 的路由差异

类型路由依据典型用法
Default(匿名,"")routing key = 队列名,直接投递本仓库 basic / retry-dlq 实验用它
Directrouting key 与 binding key 完全相等按明确类别分发,如 order.created
Topicrouting key 按段匹配,* 一段、# 零或多段层级事件路由,本仓库 routing 实验
Fanout忽略 routing key,投给全部绑定队列纯广播
Headers按消息 headers 键值匹配(x-match=all/any)少用:性能差、难维护,多数场景 Topic 可替代

Topic 匹配要点(routing 实验验证)

binding命中 order.created命中 order.created.eu
order.created
order.#
order.created.eu

# 可匹配零段:order.# 也能命中 order。绑定 # 的队列会收到所有消息,常用作审计/全量订阅。

动手验证:

bash
npm run lab -- rabbitmq routing

三队列分别收到 2/3/1 条,与上表一致。

verifiedrabbitmq / routingbroker 4.1.4 · java-amqp-client-5.34.0
镜像rabbitmq:4.1.4-management@sha256:294b01e1796a8acede4619f32a1c394fae1f8021e57986ea01aad38dc2a4f502
捕获时间2026-08-19T07:59:13.973Z
耗时 / 退出码17233 ms / exit 0
断言
confirmed3
received:orders.routing.created2
unique:orders.routing.created2
depthAfter:orders.routing.created0
received:orders.routing.all3
unique:orders.routing.all3
depthAfter:orders.routing.all0
received:orders.routing.eu1
unique:orders.routing.eu1
depthAfter:orders.routing.eu0
归一化日志
[producer] timestamp=<ts> level=INFO service=order-service product=rabbitmq lab=routing messageId=mid-1 eventType=order.created schemaVersion=1 aggregateId=order-1001 traceId=trace-1 correlationId=order-1001 destination=orders.events routingKey=order.created durationMs=<ms> status=confirmed
[producer] timestamp=<ts> level=INFO service=order-service product=rabbitmq lab=routing messageId=mid-2 eventType=order.created schemaVersion=1 aggregateId=order-1002 traceId=trace-2 correlationId=order-1002 destination=orders.events routingKey=order.created durationMs=<ms> status=confirmed
[producer] timestamp=<ts> level=INFO service=order-service product=rabbitmq lab=routing messageId=mid-3 eventType=order.created schemaVersion=1 aggregateId=order-1003 traceId=trace-3 correlationId=order-1003 destination=orders.events routingKey=order.created.eu durationMs=<ms> status=confirmed
[producer] timestamp=<ts> level=INFO service=order-service product=rabbitmq lab=routing destination=orders.events confirmed=3 status=done
[assert] confirmed=3 PASS
[consumer] timestamp=<ts> level=INFO service=order-service product=rabbitmq lab=routing messageId=mid-1 eventType=order.created schemaVersion=1 aggregateId=order-1001 traceId=trace-1 correlationId=order-1001 destination=orders.routing.created consumer=consumer-1 attempt=1 redelivered=false status=received
[consumer] timestamp=<ts> level=INFO service=order-service product=rabbitmq lab=routing messageId=mid-1 eventType=order.created schemaVersion=1 aggregateId=order-1001 traceId=trace-1 correlationId=order-1001 destination=orders.routing.created attempt=1 status=business_committed
[consumer] timestamp=<ts> level=INFO service=order-service product=rabbitmq lab=routing messageId=mid-2 eventType=order.created schemaVersion=1 aggregateId=order-1002 traceId=trace-2 correlationId=order-1002 destination=orders.routing.created consumer=consumer-1 attempt=1 redelivered=false status=received
[consumer] timestamp=<ts> level=INFO service=order-service product=rabbitmq lab=routing messageId=mid-2 eventType=order.created schemaVersion=1 aggregateId=order-1002 traceId=trace-2 correlationId=order-1002 destination=orders.routing.created attempt=1 status=business_committed
[consumer] timestamp=<ts> level=INFO service=order-service product=rabbitmq lab=routing queue=orders.routing.created received=2 status=done
[assert] received:orders.routing.created=2 PASS
[assert] unique:orders.routing.created=2 PASS
[assert] depthAfter:orders.routing.created=0 PASS
[consumer] timestamp=<ts> level=INFO service=order-service product=rabbitmq lab=routing messageId=mid-1 eventType=order.created schemaVersion=1 aggregateId=order-1001 traceId=trace-1 correlationId=order-1001 destination=orders.routing.all consumer=consumer-1 attempt=1 redelivered=false status=received
[consumer] timestamp=<ts> level=INFO service=order-service product=rabbitmq lab=routing messageId=mid-1 eventType=order.created schemaVersion=1 aggregateId=order-1001 traceId=trace-1 correlationId=order-1001 destination=orders.routing.all attempt=1 status=business_committed
[consumer] timestamp=<ts> level=INFO service=order-service product=rabbitmq lab=routing messageId=mid-2 eventType=order.created schemaVersion=1 aggregateId=order-1002 traceId=trace-2 correlationId=order-1002 destination=orders.routing.all consumer=consumer-1 attempt=1 redelivered=false status=received
[consumer] timestamp=<ts> level=INFO service=order-service product=rabbitmq lab=routing messageId=mid-2 eventType=order.created schemaVersion=1 aggregateId=order-1002 traceId=trace-2 correlationId=order-1002 destination=orders.routing.all attempt=1 status=business_committed
[consumer] timestamp=<ts> level=INFO service=order-service product=rabbitmq lab=routing messageId=mid-3 eventType=order.created schemaVersion=1 aggregateId=order-1003 traceId=trace-3 correlationId=order-1003 destination=orders.routing.all consumer=consumer-1 attempt=1 redelivered=false status=received
[consumer] timestamp=<ts> level=INFO service=order-service product=rabbitmq lab=routing messageId=mid-3 eventType=order.created schemaVersion=1 aggregateId=order-1003 traceId=trace-3 correlationId=order-1003 destination=orders.routing.all attempt=1 status=business_committed
[consumer] timestamp=<ts> level=INFO service=order-service product=rabbitmq lab=routing queue=orders.routing.all received=3 status=done
[assert] received:orders.routing.all=3 PASS
[assert] unique:orders.routing.all=3 PASS
[assert] depthAfter:orders.routing.all=0 PASS
[consumer] timestamp=<ts> level=INFO service=order-service product=rabbitmq lab=routing messageId=mid-3 eventType=order.created schemaVersion=1 aggregateId=order-1003 traceId=trace-3 correlationId=order-1003 destination=orders.routing.eu consumer=consumer-1 attempt=1 redelivered=false status=received
[consumer] timestamp=<ts> level=INFO service=order-service product=rabbitmq lab=routing messageId=mid-3 eventType=order.created schemaVersion=1 aggregateId=order-1003 traceId=trace-3 correlationId=order-1003 destination=orders.routing.eu attempt=1 status=business_committed
[consumer] timestamp=<ts> level=INFO service=order-service product=rabbitmq lab=routing queue=orders.routing.eu received=1 status=done
[assert] received:orders.routing.eu=1 PASS
[assert] unique:orders.routing.eu=1 PASS
[assert] depthAfter:orders.routing.eu=0 PASS
npm run lab -- rabbitmq routing

负载分发:Prefetch 的作用

多个竞争消费者时,RabbitMQ 默认轮询分发(round-robin)。若不设 Prefetch,Broker 可能一次把大量消息推给单个消费者,导致其他消费者空闲、该消费者积压。正确姿势:

java
channel.basicQos(1); // 未 ACK 消息最多 1 条,处理完再发下一条

本仓库所有 Demo 均设置 prefetch=1,保证「慢消费者不多拿」。

常见误区

  • 「Producer 直接发给队列」——除了 Default Exchange,消息都先经过 Exchange;发到未声明的 Exchange 会关 Channel。
  • 「绑定可以解绑就没事了」——绑定关系本身是持久状态,声明幂等但删除绑定会改变流量走向,变更需当发布对待。
  • 「routing key 就是 eventType」——本仓库约定 routing key 表达路由意图(可与 eventType 一致),队列名不应承载业务语义(规格 §5.3)。
  • 「Fanout + 单队列 = 广播」——广播要求每个订阅者有自己的队列;共用一个队列就退化成竞争消费。

官方资料

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