[RabbitMQ][官方文档翻译]Publish/Subscribe 发布订阅模式
译文
在上一教程中我们创建了一个工作队列。工作队列是假设每一任务能被精准得发送给一个消费者。在此教程中我们会做完全不同的事情——我们将给多个消费者发送同一条消息。这个模式被称为“发布/订阅”。为了说明这个模式,我们将搭建一个简易的日志系统。该系统由两个程序组成—— 一个发出消息,另一个接收并打印消息。
在我们的日志系统中,每一个在运行中的接收程序都将接收到消息。这样我们就可以运行一个接收程序然后将日志信息直接保存到硬盘,与此同时我们将运行另一个接收程序然后将日志打印到屏幕上。
本质上,发布的日志消息将被广播给所有接收程序。
上述教程中我们向一个队列发送消息并接收消息。现在是时候介绍RabbitMQ中完整的消息传递模型了。
让我们快速复习一下上述教程:
- 生产者是一个能发送消息的用户应用。
- 队列是存储消息的缓存器。
- 消费者是一个能接收消息的用户应用。
RabbitMQ中消息传递模型的核心思想是 生产者从不直接向队列发送任何消息。实际上,生产者通常完全不知道一条消息是否会被发送给任何队列。
生产者只能发送消息给交换器。交换器是非常简单的。它一边从生产者接收消息,一边向队列发送消息。交换器必须准确知晓如何处理所接收到的消息——应该被附加到一个特定队列?应该被附加到大部分队列?还是应该被丢弃?这取决于所定义的交换规则。
以下是一些常用的交换规则:direct、topic、headers 和 fanout。我们将专注于最后一个规则 fanout。
让我们创建一个fanout类型的交换器,并取名为logs:
channel.exchangeDeclare("logs", "fanout");
fanout规则非常简单。我们能从名字(扇出)上猜知,fanout交换器会向所有他所知晓的队列广播消息。这正是我们的日志系统所需要的。
列出交换器:
在服务器上使用 rabbitmqctl 指令列出所有交换器:
sudo rabbitmqctl list_exchanges
交换器列表中会有一些名为 amq.* 的交换器,和默认的无名交换器。这些交换器是默认创建的,但是目前您可能不太需要使用它们。
匿名交换器
前述教程中我们完全不知晓交换器是什么,但仍然能够向队列发送消息。那可能是因为我们使用了默认的交换器,我们用空字符串标识("")。
回忆我们此前如何发布一条消息:
channel.basicPublish("", "hello", null, message.getBytes());
第一个参数即是交换器的名称。空字符串意味着使用默认得或者匿名交换器:消息被路由到由routingKey指定名称的队列(如果存在)。
现在我们可以向我们自定义的交换器发送消息:
channel.basicPublish( "logs", "", null, message.getBytes());
临时队列
您可能还记得我们之前使用过的具有特定名称的队列。能够命名队列对我们来说至关重要——我们需要将工作人员指向同一个队列。当您想再生产者和消费之间共享队列时,为队列命名很重要。
但我们的日志系统不需要这样。我们希望获取到所有日志消息,而不仅是其中的一部分。我们也对当前传送的消息感兴趣,而不是对旧消息感兴趣。 为了实现这一需求,我们需要做以下两件事:
首先,每当我们连接至RabbitMQ时,我们都需要一个新的空队列。为此我们需要创建一个随机名称的队列,或者,更好的是,让RabbitMQ服务器为我们选择一个随机队列名。
其次,一旦我们断开消费者的连接,该队列将被自动删除。
在Java客户端中,无参调用queueDeclare()方法,我们就可以创建一个带有自动生成名称的、非持久化的、exclusive、自动删除的队列:
channel.basicPublish( "logs", "", null, message.getBytes());
你可以从guide on queues中学习到 exclusive 参数的其他内容,以及其他队列配置信息。
绑定
我们已经创建了一个fanout类型的交换器和一个队列。现在我们需要告诉交换器去发送消息给我们的队列。交换器和队列之间的关系叫绑定。
channel.queueBind(queueName, "logs", "");
至此,logs交换器会发送消息给我们的队列。
列出绑定
您猜对了,您可以通过以下指令列出所有正在使用的绑定关系:
sudo rabbitmqctl list_bindings
组合上述代码
发送消息的生产者程序与前面的教程并没有太大的不同。最重要的改变是我们现在想给logs交换器发送消息而不是匿名交换器。我们需要在发送消息时提供一个routingKey,但是当交换器类型是fanout时,routingKey可以忽略。以下是 EmitLog.java 程序代码(生产者端):
https://github.com/rabbitmq/rabbitmq-tutorials/blob/master/java/EmitLog.java
如您所见,建立连接之后我们声明了交换器。这一步非常重要,因为给一个不存在的交换器发送消息是被禁止的。
如果一个交换器还没有被任何队列绑定,则交换器所接收到的消息将被丢弃,但是问题不大:如果没有消费者在监听,我们可以很安全地丢弃掉这些消息。
以下是ReceiveLogs.java程序代码(消费者端):
https://github.com/rabbitmq/rabbitmq-tutorials/blob/master/java/ReceiveLogs.java
作者:javanoob0660
出处:https://www.cnblogs.com/javanoob0660/articles/16420868.html
版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通