了解AMQP,RabbitMQ使用的协议

工程 | Peter Ledbrook | 2010年6月14日 | ...

更新 我修改了第一段,以澄清RabbitMQ与JMS之间的关系。

RabbitMQ是一个轻量级、可靠、可扩展且可移植的消息代理。但与许多Java开发人员熟悉的的消息代理不同,它不基于JMS。取而代之的是,您的应用程序通过一种平台中立的、线级别的协议(高级消息队列协议,AMQP)与其进行通信。幸运的是,已经有一个Java客户端库,并且SpringSource正在致力于一流的Spring和Grails集成,所以不必担心使用RabbitMQ时需要进行低级操作。您甚至可以找到提供JMS接口的AMQP客户端库。但AMQP在操作上与JMS有足够大的差异,这可能会让习惯于JMS模型的Java开发人员感到困惑。

为了缓解过渡的痛苦,本文将探讨AMQP的基础概念以及三个常见的用例。到那时,您应该能充分理解如何配置RabbitMQ并通过Spring和Grails提供的API使用它。

交换器、队列和绑定

像任何消息系统一样,AMQP是一个涉及发布者和消费者的消息协议。发布者产生消息,消费者接收并处理它们。消息代理(如RabbitMQ)的职责是确保来自发布者的消息能够发送到正确的消费者。为此,代理使用了两个关键组件:交换器和队列。下图展示了它们如何连接发布者和消费者。

rabbit-basics

正如您所见,设置非常简单。发布者将消息发送到命名交换器,而消费者从队列中拉取消息(或者根据配置,队列将消息推送到消费者)。当然,首先需要建立连接,那么发布者和消费者如何互相发现呢?通过交换器的名称。通常,发布者或消费者会创建一个具有给定名称的交换器,然后公开该名称。如何公开取决于具体情况,例如可以将其包含在公共API文档中,或者将其发送给已知的客户端。

消息如何从交换器路由到队列?这是一个好问题。首先,队列必须附加到给定的交换器。通常,消费者会创建队列并同时将其附加到交换器。其次,交换器接收到的消息必须与队列进行匹配,这个过程称为“绑定”。

为了理解绑定,了解AMQP消息的结构很有帮助。

rabbit-message

消息的头和属性本质上是键值对。它们之间的区别在于,头由AMQP规范定义,而属性可以包含任意的、特定于应用程序的信息。实际的消息内容只是一个字节序列,所以如果您想在消息中传递文本,应该统一使用一种编码。UTF-8是一个不错的选择。如果您愿意,可以在消息头中指定内容类型和编码,但这似乎并不特别常见。

这与绑定有什么关系?一个标准头称为routing-key,代理就是利用它来匹配消息到队列。每个队列都指定一个“绑定键”,如果该键与routing-key头的值匹配,则该队列将接收到消息。

交换器类型的概念使情况稍微复杂化。AMQP规范定义了以下四种类型:

交换器类型 行为
Direct 绑定键必须与路由键完全匹配,不支持通配符。
Topic 与Direct相同,但允许在绑定键中使用通配符。'#'匹配零个或多个点分隔的单词,'*'匹配恰好一个这样的单词。
Fanout 路由键和绑定键被忽略——所有发布的消息都会发送到所有绑定的队列。
Headers

更新 我修正了关于通配符的信息,它们基于点分隔的单词或术语。

例如,假设一个发布者向名为“Stocks”的主题交换器发送一个路由键为“NYSE”的消息。如果一个消费者创建了一个附加到“Stocks”的队列,并且其绑定键为“#”、“*”或“NYSE”,那么该消费者将收到该消息,因为所有这三个绑定键都匹配“NYSE”。然而,如果消息发布到一个直连交换器,并且绑定键是“#”或“*”,那么消费者将不会收到消息,因为这些字符被视为字面量,而不是通配符。有趣的是,“#.#”也会匹配“NYSE”,尽管路由键中没有点。

现在考虑一个路由键为“NYSE.TECH.MSFT”的消息。给定消息将发送到一个主题交换器,哪些绑定键会匹配它?

绑定键 匹配?
NYSE.TECH.MSFT
#
NYSE.#
*.*
NYSE.*
NYSE.TECH.*
NYSE.*.MSFT

基本上就这么简单。通过支持每个队列的多个消费者和每个交换器的多个队列来提供灵活性。事实上,一个队列甚至可以绑定到多个交换器。现在让我们看看其中的一些场景。

RPC

AMQP代理可以充当客户端和服务之间的RPC机制。一般的设置如下,使用直连交换器。

rabbit-rpc

一般的流程如下:

  1. 客户端向队列发送消息,指定:(a)与服务匹配的路由键;以及(b)用于接收响应的队列名称。
  2. 交换器将消息传递给服务的队列(在本例中为“ops_q”)。
  3. 队列将消息推送到服务,服务执行一些工作,然后发送响应消息回交换器,并指定一个匹配回复队列的路由键。
  4. 客户端从回复队列中获取响应消息。

从客户端的角度来看,调用可以是阻塞的或非阻塞的。但是,实现哪种方式的容易程度取决于所使用的客户端库。

RPC场景的关键是确保客户端和服务使用相同的交换器进行初始请求,并且客户端知道要为路由键指定什么。

至于回复队列,它通常由客户端创建,然后客户端会适当地填充reply_to头。此外,虽然您可以使用与请求不同的交换器来处理回复,但更常见的是为请求和回复使用同一个交换器。

发布/订阅

JMS具有主题队列的概念,可以确保发布者的消息发送给所有订阅者。在AMQP中,您可以通过将多个队列绑定到一个交换器来轻松实现相同的行为,如下所示:

rabbit-pub-sub

更妙的是,队列可以通过绑定键过滤它们接收的消息。如果一个消费者想要接收所有消息,它可以指定绑定键为“#”——正如前面提到的“匹配任意数量的单词”通配符。对于普通开发人员来说,有点令人困惑的是,“*”匹配零个或一个(点分隔的)单词。

工作分配

想象一下您的应用程序有一堆需要执行的任务。通过AMQP,您可以连接多个消费者,使每个任务仅发送给其中一个消费者。发布者不在乎哪个消费者完成了工作,只在乎工作是否完成。这就是工作分配。

配置起来非常简单,如本图所示:

rabbit-work

所以您有一个队列绑定到交换器,有多个消费者共享这个队列。这种设置可以保证即使有多个消费者,也只有一个消费者会处理给定的消息。

以上是AMQP代理的三个主要使用模式。尽管我分别描述了它们,但将它们组合起来的情况也很常见。例如,在RPC模式中,您可以让多个服务共享同一个队列(工作分配)。最终由您来决定如何配置交换器和队列,现在您应该已经有了足够的理解,可以为您的具体情况找出合适的设置。

如果您想深入了解AMQP,请查看规范本身,特别是“General Architecture”(通用架构)部分。要开始使用RabbitMQ,只需访问其网站

获取 Spring 新闻通讯

通过 Spring 新闻通讯保持联系

订阅

领先一步

VMware 提供培训和认证,助您加速进步。

了解更多

获得支持

Tanzu Spring 提供 OpenJDK™、Spring 和 Apache Tomcat® 的支持和二进制文件,只需一份简单的订阅。

了解更多

即将举行的活动

查看 Spring 社区所有即将举行的活动。

查看所有