RabbitMQ 这玩意儿,我第一次接触是在一个订单系统里。当时项目已经上线了,突然有一天运营反馈说用户收到了重复的通知短信,一查日志发现生产者把消息发到了默认交换机,消费者那边又是自动确认,一重启服务消息就重新入队,再加上接口超时重试,一来二去就乱套了。那时候我才意识到,RabbitMQ 的文档虽然写得清楚,但真正要落地,靠的是把七种消息模型的边界搞清楚。
这篇东西我整理了挺久,从最基础的 Simple 模式一路写到工作队列、发布/订阅、路由、通配符、RPC 和发布确认机制,每条都配了原生 Java 客户端的可运行代码,也补充了平时生产环境里最容易被忽略的细节。适合刚把 RabbitMQ 环境跑起来、开始接触消息模型的初学者,也适合已经用过一阵子但想把模型选型逻辑彻底理清的同学参考。
1. 动手之前的必修课:Exchange、Queue、Binding 到底怎么协作
1.1 先把环境跑起来
不管你是 Windows 还是 macOS,我都不建议直接去官网下载安装包折腾 Erlang 和 RabbitMQ 的版本匹配问题。最省事的方式是用 Docker,一条命令就能把带管理界面的服务拉起来:
bash复制docker run -d --name rabbitmq \
-p 5672:5672 -p 15672:15672 \
-e RABBITMQ_DEFAULT_USER=admin \
-e RABBITMQ_DEFAULT_PASS=admin123 \
rabbitmq:3-management
5672 是 AMQP 协议端口,15672 是管理界面端口。如果你已经在用 Windows 原生方式部署,记得装完 RabbitMQ 之后手动开一下管理插件:
bash复制rabbitmq-plugins enable rabbitmq_management
默认账号 guest 只能从 localhost 访问,远程连管理界面或者代码连非本机地址,都需要自己建用户、配权限。这个坑我见过不少人踩,代码里连接报错 first login 失败,其实就是 guest 的访问限制闹的。
管理界面启动之后,http://localhost:15672 就能看到队列、交换机、绑定关系以及消息的实时流量。我强烈建议你在跑下面每个模式的时候,都打开 Exchange 和 Queues 两个标签页,肉眼看一遍消息是怎么流转的。这个习惯比看十篇文档都管用。
1.2 消息不是直接进队列的
RabbitMQ 最核心、也最容易被初学者忽略的设计是:生产者不直接把消息丢进队列,而是先发给交换机(Exchange),由交换机根据绑定关系(Binding)把消息路由到一个或多个队列。
你可以把交换机理解成快递中转站。快递员(生产者)只负责把包裹送到中转站,至于包裹最终是送到 A 小区还是 B 小区,取决于中转站的分拣规则(交换机类型 + 路由键)。如果中转站发现没有匹配的收货地址,包裹要么被退回(Return 回调),要么被悄悄丢弃——这取决于你发布消息时的 mandatory 参数。
对应到代码里有三个必须理解的概念:
- Exchange:接收生产者消息的入口,有 direct、fanout、topic、headers 四种类型。
- Queue:真正存储消息的地方,消费者从这里拉消息。
- Binding:把 Exchange 和 Queue 连起来的"分拣规则",绑定的时候要指定 BindingKey。
生产者发送消息时携带一个 RoutingKey,交换机拿着这个 RoutingKey 和所有队列的 BindingKey 做匹配,匹配上了就投递。这里最容易被绕晕的地方是 RoutingKey 和 BindingKey 的关系——除了 fanout 交换机不关心键值之外,rest 模式下两者配合决定了消息的去向。
1.3 RoutingKey 和 BindingKey 的约定与误会
我刚学的时候一直分不清 RoutingKey 和 BindingKey 是不是同一个东西。后来在实践中总结出一个朴素的记忆方式:
- 生产者视角叫 RoutingKey,是消息自带的"门牌号"。
- 消费者视角叫 BindingKey,是队列和交换机绑定时登记的"期望门牌号"。
- 两者匹配成功,消息就进队列;匹配失败,消息就流浪。
但这个说法只在 direct 交换机下最精确。到了 topic 交换机,BindingKey 变成带通配符的表达式,比如 order.* 能匹配 order.create 也能匹配 order.cancel,但匹配不了 order.create.success。到了 fanout 交换机,门牌号直接被无视,消息复制给所有绑定的队列。
所以在看任何一篇讲 RabbitMQ 模式的文章之前,先把 Exchange、Queue、Binding 三者的关系在脑子里转一遍,后面所有模式就都是"换不同的交换机类型 + 不同的绑定规则"而已。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. Simple 模式和工作队列:从单机通知到并行消费
2.1 Simple 模式:最朴素的队列模型
Simple 模式是最基础的模型,一个生产者、一个队列、一个消费者。生产端代码如下:
java复制ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
factory.setUsername("admin");
factory.setPassword("admin123");
try (Connection connection = factory.newConnection();
Channel channel = connection.createChannel()) {
// 声明队列:durable=true 表示队列持久化,重启不丢
channel.queueDeclare("simple_queue", true, false, false, null);
String message = "订单创建成功";
channel.basicPublish("", "simple_queue",
MessageProperties.PERSISTENT_TEXT_PLAIN,
message.getBytes(StandardCharsets.UTF_8));
System.out.println(" [x] Sent '" + message + "'");
}
注意生产者的 basicPublish 第一个参数传的是空字符串,这表示使用默认交换机。默认交换机是 direct 类型,它的特殊之处在于每个队列都会隐式绑定额外一个和队列同名的 BindingKey,所以你把 routingKey 写成队列名 "simple_queue",消息就能准确进队。
消费端代码同样简单:
java复制ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
channel.queueDeclare("simple_queue", true, false, false, null);
DeliverCallback deliverCallback = (consumerTag, delivery) -> {
String message = new String(deliv
