前置阅读:01 核心概念篇——需要先知道交换机、队列、绑定是什么。
RabbitMQ 官方教程把常见用法归纳成五种消息模型,本质上就是**”几类交换机 × 几个消费者”的组合**:
| 模型 | 交换机 | 消费者数 | 一句话 |
|---|---|---|---|
| 简单队列 | 默认 | 1 | 一对一发消息 |
| 工作队列 | 默认 | N | 一条消息只给一个消费者,分摊干活 |
| 发布订阅 | fanout | N | 广播,人人有份 |
| 路由模式 | direct | N | 按键精确投递 |
| 主题模式 | topic | N | 按通配符投递 |
一、简单队列
一对一,上一篇文章的 Hello World 就是它,不再重复。它的问题是队列积压时没有横向扩容能力——引出工作队列。
二、工作队列(Work Queue):一条消息只给一个人
场景:注册消息堆积了 1 万条,一个消费者处理不过来。好消息是:发短信这件事一条消息只需要做一次,所以我们起 N 个消费者共同消费同一个队列。
1 | // 生产者:连发 20 条消息 |
1 | // 消费者 1 和消费者 2 跑同一份代码,改下名字即可 |
轮询分发:默认的”平均主义”
跑一下会发现:消费者 1 分到任务 1、3、5、7…,消费者 2 分到 2、4、6、8…,严格一人一半,谁也别想多拿。
这就是 RabbitMQ 的默认分发策略——轮询(Round-Robin)。它不管消费者忙不忙:哪怕消费者 1 正在处理 3 秒的大任务,队列照样把下一条塞给它,而旁边的消费者 2 早就闲得发慌。结果就是忙的更忙、闲的更闲,整体吞吐被拖慢。
公平分发:能者多劳
解法是两行配置,缺一不可:
1 | // 每个消费者最多持有 1 条"未确认"的消息,处理完 ack 之前队列不再派新活 |
原理:basicQos(1) 告诉队列”没收到我的 ack 之前别派新消息”,而 ack 只有处理完才发。于是谁先干完谁先领下一条——处理快的消费者自然拿得多,这就是能者多劳(Fair Dispatch)。
注意一个坑:手动 ack 忘了调用 basicAck 的话,消息会一直处于 unacked 状态,队列不会派新消息(basicQos(1) 时直接卡死),积压在管理界面上一眼就能看出来。忘了 ack 的排查方法:Queues 页签看 Unacked 数量。
三、发布订阅(fanout):人人有份
场景:用户注册成功后,邮件服务和短信服务都要收到通知。一条消息要给多个队列各投一份——这就是 fanout 交换机的广播能力。
1 | // 生产者:声明交换机并发消息,不再声明队列 |
1 | // 消费者(邮件服务):自己声明队列,并绑定到交换机 |
短信服务的代码一模一样,只是队列名换成 sms.queue。每个消费者声明自己的队列再绑定到同一个交换机,fanout 会把消息复制到所有绑定的队列。
和”生产者发 20 条消息给 2 个消费者各 10 条”的工作队列对比:工作队列是竞争关系(一条消息只给一个人),发布订阅是订阅关系(一条消息每人一份)。
四、路由模式(direct):按键精确投递
场景:支付结果消息有 pay.success 和 pay.fail 两种,退款服务只关心失败,发货服务只关心成功。fanout 做不到选择性投递,上 direct:
1 | // 生产者:routing key 区分消息类型 |
1 | // 发货服务:绑定 key 为 pay.success,只有成功的消息会进来 |
direct 的规则就一条:binding key 和 routing key 完全相等才投递。一个队列也可以绑多个 key(比如风控服务两个都绑,成功失败都要看)。
五、主题模式(topic):通配符投递
场景:消息的 routing key 变成了多级结构 商品.事件.地区,比如 order.paid.gz、order.cancelled.sh、user.created.gz。需求开始花哨:
- 华南的日志服务要所有
*.gz - 订单服务要所有
order.# - 只关心订单取消:
order.cancelled.*
这种”按层级通配”的需求用 topic:
1 | // 生产者 |
1 | // 订单服务:绑定 order.#,收所有订单相关消息 |
匹配规则(. 分隔的单词为最小单位):
| binding key | 能匹配 | 不能匹配 |
|---|---|---|
order.* |
order.paid |
order.paid.gz(* 只顶一个词) |
order.# |
order.paid、order.paid.gz |
—— |
#.gz |
order.paid.gz、gz |
a.sh |
# |
任意 key(等价于 fanout) | —— |
两个特例值得记:# 单独使用等价于 fanout 广播;不含任何通配符的 topic 绑定(如 order.paid)等价于 direct 精确匹配。所以 topic 是 direct 和 fanout 的超集,业务系统拿不准时直接用 topic 不亏。
六、五种模型怎么选?
按需求倒着推:
- 一条消息只处理一次、想横向扩容 → 工作队列(默认交换机 + 多消费者 +
basicQos) - 一条消息多个系统都要一份 → fanout
- 按类型精确分流 → direct
- 按多级结构灵活订阅 → topic
下一篇解决更要命的问题:这些消息模型里,消息丢了怎么办?——生产者确认、持久化、手动 ack、死信队列,把可靠性一口气讲完。