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) 后,谁处理完谁拿新消息,效率直接翻倍。

避坑指南:我曾经在生产环境忘记设置消息持久化,结果 RabbitMQ 重启后,队列里的任务全丢了。记得把队列和消息都设为持久化:
// 队列持久化
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.paidlog.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 模式 无(默认交换机) 请求/响应模式 远程调用、结果返回

选型时我有个习惯:先问自己三个问题。消息需要被多个消费者处理吗?需要筛选吗?需要返回结果吗?回答完这三个问题,模式基本就定了。

核心总结:六种模式不是孤立的,它们可以组合使用。我在一个大型项目中,同时用了工作队列处理任务、主题模式做数据分发、RPC 模式做配置查询。关键是理解每种模式的本质——交换机类型和绑定关系决定了消息的流向。

好了,RabbitMQ 的工作模式就讲到这里。下一章咱们聊聊 RabbitMQ 的高级特性,比如死信队列、延迟队列这些实战利器。到时候我会分享更多踩坑经验,敬请期待。