更新 我更改了第一段以阐明 RabbitMQ 和 JMS 之间的关系。
RabbitMQ 是一款轻量级、可靠、可扩展且可移植的消息代理。但与 Java 开发人员熟悉的许多消息代理不同,它不是基于 JMS 的。相反,您的应用程序通过平台中立的线级协议与之通信:高级消息队列协议 (AMQP)。幸运的是,已经有一个 Java 客户端库,并且 SpringSource 正在开发一流的 Spring 和 Grails 集成 - 所以不用担心需要执行低级操作来使用 RabbitMQ。您甚至可以找到公开 JMS 接口的 AMQP 客户端库。但 AMQP 在操作上与 JMS 有很大不同,因此可能会给习惯了 JMS 模型的 Java 开发人员带来麻烦。
为了简化过渡,我将在本文中介绍支持 AMQP 的基本概念以及三种常见的用法场景。到最后,您将有足够好的理解来配置 RabbitMQ 并通过 Spring 和 Grails 提供的 API 使用它。
交换机、队列和绑定
与任何消息传递系统一样,AMQP 是一种消息协议,处理发布者和消费者。发布者生成消息,消费者获取并处理它们。消息代理(例如 RabbitMQ)的工作是确保发布者的消息传递到正确的消费者。为了做到这一点,代理使用两个关键组件:交换机和队列。下图显示了它们如何将发布者连接到消费者
如您所见,设置非常简单。发布者将消息发送到命名交换机,消费者从队列中提取消息(或根据配置,队列将消息推送到消费者)。当然,必须首先建立连接,那么发布者和消费者如何发现彼此?通过交换机的名称。通常,发布者或消费者会使用给定名称创建交换机,然后公开该名称。发布方式取决于具体情况,但可能会将其放在公共 API 文档中或发送给已知的客户端。
消息如何从交换机路由到队列?好问题。首先,队列必须附加到给定的交换机。通常,消费者会创建队列并同时将其附加到交换机。其次,交换机接收到的消息必须与队列匹配 - 此过程称为“绑定”。
要理解绑定,了解 AMQP 消息的结构很有用
消息的头部和属性基本上都是键值对。它们之间的区别在于,头部由 AMQP 规范定义,而属性可以包含任意、特定于应用程序的信息。实际的消息内容只是一系列字节,因此,如果您想在消息中传递文本,则应标准化编码。UTF-8 是一个不错的选择。如果需要,您可以在消息头部指定内容类型和编码,但这似乎并不常见。
这与绑定有什么关系?标准头部之一称为路由键(routing-key)代理使用它来将消息与队列匹配。每个队列都指定一个“绑定键”,如果该键与路由键(routing-key)头部的值匹配,则队列接收该消息。
交换机类型的概念使事情略微复杂化。AMQP 规范定义了以下四种类型
交换机类型(Exchange type) |
行为(Behaviour) |
直连型(Direct) |
绑定键必须与路由键完全匹配 - 不支持通配符。 |
主题型(Topic) |
与直连型相同,但绑定键中允许使用通配符。“#”匹配零个或多个点分隔的单词,“*”匹配恰好一个这样的单词。 |
扇出型(Fanout) |
路由键和绑定键被忽略 - 所有发布的消息都发送到所有绑定的队列。 |
头部型(Headers) |
|
更新 我更正了有关通配符的信息,通配符基于点分隔的单词或术语工作。
例如,假设发布者将路由键为“NYSE”的消息发送到名为“Stocks”的主题型交换机。如果消费者创建一个附加到“Stocks”的队列,并使用“#”、“*”或“NYSE”作为绑定键,那么该消费者将收到该消息,因为所有三个绑定键都与“NYSE”匹配。但是,如果消息发布到直连型交换机,则如果绑定键为“#”或“*”,则消费者将不会收到该消息,因为这些字符被视为字面量,而不是通配符。有趣的是,“#.#”也将匹配“NYSE”,尽管路由键没有点。
现在考虑一个路由键为“NYSE.TECH.MSFT”的消息。鉴于该消息将发送到主题型交换机,哪些绑定键将与之匹配?
绑定键(Binding key) |
匹配?(Match?) |
NYSE.TECH.MSFT |
是(Yes) |
# |
是(Yes) |
NYSE.# |
是(Yes) |
*.* |
否(No) |
NYSE.* |
否(No) |
NYSE.TECH.* |
是(Yes) |
NYSE.*.MSFT |
是(Yes) |
这就是全部内容。每个队列支持多个消费者,每个交换机支持多个队列,从而提供了灵活性。实际上,单个队列甚至可以绑定到多个交换机。现在让我们来看一些这样的场景。
远程过程调用(RPC)
AMQP 代理可以充当客户端和服务之间的 RPC 机制。使用直连型交换机,一般设置如下所示
一般顺序如下
- 客户端将消息发送到队列,指定:(a) 与服务匹配的路由键;以及 (b) 从中获取响应的队列名称。
- 交换机将消息传递到服务的队列(在本例中为“ops_q”)。
- 队列将消息推送到服务,服务执行一些工作,并将响应消息发送回交换机,并指定与回复队列匹配的路由键。
- 客户端从回复队列中获取响应消息。
从客户端的角度来看,调用可以是阻塞的或非阻塞的。但是,执行哪种操作的难易程度取决于所使用的客户端库。
RPC 场景的关键是确保客户端和服务使用相同的交换机进行初始请求,并且客户端知道为路由键指定什么。
至于回复队列,它通常由客户端创建,然后客户端填充reply_to头部。此外,尽管您可以使用与请求不同的交换机进行回复,但通常使用相同的交换机进行请求和回复。
发布/订阅(Pub(lish)/Sub(scribe))
JMS 有主题队列的概念,它确保发布者的消息发送到所有订阅者。通过将多个队列绑定到交换机,您可以在 AMQP 中轻松实现相同的功能,如下所示
更好的是,队列可以通过绑定键过滤它们接收的消息。如果消费者想要接收所有消息,则可以指定“#”作为绑定键 - “匹配任意数量的单词”通配符。对于普通开发人员来说,令人困惑的是,“*”匹配零个或一个(点分隔的)单词,如前所述。
工作分配(Work distribution)
假设您有一个应用程序,其中有很多需要执行的作业。使用 AMQP,您可以连接多个消费者,以便每个作业都发送到其中一个消费者,并且只发送到一个消费者。发布者不关心哪个消费者执行工作,只关心工作是否完成。这就是工作分配。
配置它非常简单,如下图所示
因此,您有一个绑定到交换机的队列,多个消费者共享该队列。此设置保证只有一个消费者处理给定的消息,无论有多少消费者。
这些是 AMQP 代理的三种主要使用模式。尽管我分别描述了每种模式,但将它们组合起来是相当常见的。例如,在 RPC 模式中,您可以让多个服务共享同一个队列(工作分配)。如何配置交换机和队列完全取决于您,现在您应该有足够的了解来确定适合您情况的设置。
如果您想进一步了解 AMQP,请查看规范本身,特别是关于通用架构的部分。要开始使用 RabbitMQ,只需访问其网站。