4. RabbitMQ 工作模式:从简单到复杂,一网打尽
RabbitMQ 的工作模式,说白了就是消息从生产者到消费者的几种典型路径。我刚开始接触时也觉得模式太多记不住,后来在项目中一个个用过来,才发现每种模式都有它不可替代的场景。今天咱们就把这六种模式掰开揉碎讲清楚。
4.1 简单模式:Hello World 的起点
这是最基础的模式,一个生产者,一个消费者,通过一个队列通信。就像两个人用一根管子传纸条,简单直接。
我记得第一次用 RabbitMQ 做 demo 时,就是这种模式。代码写起来特别清爽:
// 生产者
channel.queueDeclare("hello", false, false, false, null);
channel.basicPublish("", "hello", null, "Hello World".getBytes());
// 消费者
channel.basicConsume("hello", true, (consumerTag, delivery) -> {
String message = new String(delivery.getBody(), "UTF-8");
System.out.println("收到消息: " + message);
}, consumerTag -> {});
嗯,这里要注意:简单模式虽然简单,但生产环境几乎不用。为什么?因为一旦消费者挂了,消息就丢了。它只适合做快速验证或学习入门。
4.2 工作队列模式:让消费者们一起干活
工作队列模式,说白了就是多个消费者抢着处理同一个队列里的消息。我在项目中遇到过这样的场景:后台需要处理大量图片压缩任务,单机处理太慢,于是用工作队列模式,起了 5 个消费者并行处理。
关键配置在于消息的 prefetch 设置:
// 告诉 RabbitMQ,一次只给消费者发一条消息
channel.basicQos(1);
如果不设这个,默认是轮询分发,每个消费者平均分配消息。但实际中,有的消息处理快,有的处理慢。我曾经踩过这个坑——没设 prefetch,结果一个消费者忙死,另一个闲死。设了 basicQos(1) 后,谁处理完谁拿新消息,效率直接翻倍。
// 队列持久化
channel.queueDeclare("task_queue", true, false, false, null);
// 消息持久化
channel.basicPublish("", "task_queue",
MessageProperties.PERSISTENT_TEXT_PLAIN, message.getBytes());
4.3 发布/订阅模式:一呼百应
这种模式用到了交换机(Exchange)。生产者把消息发给交换机,交换机再广播给所有绑定的队列。你想想看,这不就是广播嘛!
我做过一个实时告警系统,服务器出问题时,需要同时通知短信、邮件、钉钉三个渠道。用发布/订阅模式最合适:
// 声明交换机,类型为 fanout(广播)
channel.exchangeDeclare("alerts", BuiltinExchangeType.FANOUT);
// 每个消费者创建自己的临时队列,绑定到交换机
String queueName = channel.queueDeclare().getQueue();
channel.queueBind(queueName, "alerts", "");
这里有个细节:每个消费者都创建了独立的临时队列。这样即使某个消费者挂了,也不影响其他消费者接收消息。我在项目中就靠这个特性,实现了告警通道的独立运维。
4.4 路由模式:精准投递
路由模式比发布/订阅多了个路由键(Routing Key)。交换机根据路由键,把消息发给匹配的队列。说白了,就是带筛选的广播。
举个例子,日志系统里,我只想收集 ERROR 级别的日志,不想看 INFO 的。用路由模式:
// 声明 direct 类型交换机
channel.exchangeDeclare("logs_direct", BuiltinExchangeType.DIRECT);
// 生产者发送时指定路由键
channel.basicPublish("logs_direct", "error", null, message.getBytes());
// 消费者只绑定自己关心的路由键
channel.queueBind(queueName, "logs_direct", "error");
channel.queueBind(queueName, "logs_direct", "warning");
我个人习惯用路由模式来做业务消息的分类。比如订单系统里,创建订单、支付成功、退款成功,分别用不同的路由键,不同消费者各取所需。
4.5 主题模式:模糊匹配的艺术
主题模式是路由模式的升级版。路由键支持通配符:* 匹配一个单词,# 匹配零个或多个单词。
我记得有个项目需要处理不同地区的订单数据。比如华东区的订单要存到上海机房,华北区的存到北京机房。用主题模式:
// 声明 topic 类型交换机
channel.exchangeDeclare("orders_topic", BuiltinExchangeType.TOPIC);
// 生产者发送消息,路由键如 "order.shanghai.created"
channel.basicPublish("orders_topic", "order.shanghai.created", null, message.getBytes());
// 上海机房消费者:匹配所有上海相关的订单
channel.queueBind(queueName, "orders_topic", "order.shanghai.*");
// 全国订单统计消费者:匹配所有订单
channel.queueBind(queueName, "orders_topic", "order.#");
order.beijing.paid、log.error.shanghai。
4.6 RPC 模式:让消息中间件当传话筒
RPC 模式有点特殊,它让 RabbitMQ 实现了远程过程调用。客户端发请求,服务端处理完再返回结果。
实现原理其实不复杂:客户端发消息时带上两个特殊属性——replyTo(回调队列)和 correlationId(请求ID)。服务端处理完后,把结果发到回调队列,客户端根据 correlationId 匹配对应的请求。
// 客户端发送 RPC 请求
String corrId = UUID.randomUUID().toString();
String replyQueueName = channel.queueDeclare().getQueue();
AMQP.BasicProperties props = new AMQP.BasicProperties.Builder()
.correlationId(corrId)
.replyTo(replyQueueName)
.build();
channel.basicPublish("", "rpc_queue", props, message.getBytes());
// 等待响应
BlockingQueue<String> response = new ArrayBlockingQueue<>(1);
channel.basicConsume(replyQueueName, true, (consumerTag, delivery) -> {
if (delivery.getProperties().getCorrelationId().equals(corrId)) {
response.offer(new String(delivery.getBody(), "UTF-8"));
}
}, consumerTag -> {});
嗯,这里要提醒一下:RPC 模式虽然好用,但别滥用。我见过有人把所有内部服务调用都改成 RabbitMQ RPC,结果延迟高得离谱。它适合异步场景,不适合高频、低延迟的调用。
4.7 模式对比与选型建议
| 模式 | 交换机类型 | 核心特点 | 适用场景 |
|---|---|---|---|
| 简单模式 | 无(默认交换机) | 一对一,最基础 | 学习入门、快速验证 |
| 工作队列 | 无(默认交换机) | 任务分发,负载均衡 | 耗时任务并行处理 |
| 发布/订阅 | Fanout | 广播给所有消费者 | 事件通知、日志广播 |
| 路由模式 | Direct | 按路由键精确匹配 | 日志分级、消息分类 |
| 主题模式 | Topic | 按通配符模糊匹配 | 多维度消息路由 |
| RPC 模式 | 无(默认交换机) | 请求/响应模式 | 远程调用、结果返回 |
选型时我有个习惯:先问自己三个问题。消息需要被多个消费者处理吗?需要筛选吗?需要返回结果吗?回答完这三个问题,模式基本就定了。
好了,RabbitMQ 的工作模式就讲到这里。下一章咱们聊聊 RabbitMQ 的高级特性,比如死信队列、延迟队列这些实战利器。到时候我会分享更多踩坑经验,敬请期待。