理解 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

消息的头部(headers)和属性(properties)本质上是键/值对。它们之间的区别在于,头部由 AMQP 规范定义,而属性可以包含任意的、应用程序特定的信息。实际的消息内容只是一系列字节,所以如果你想在消息中传递文本,那么应该标准化编码方式。UTF-8 是一个不错的选择。你可以在消息头部指定内容类型和编码,但显然这并不是特别常见。

这与绑定有什么关系?其中一个标准头部叫做路由键 (routing-key)代理就是用它来将消息与队列匹配的。每个队列指定一个“绑定键”,如果该键与路由键 (routing-key)头部的值匹配,队列就会收到消息。

由于交换机类型的概念,事情变得稍微复杂。AMQP 规范定义了以下四种类型

交换机类型 行为
Direct (直接) 绑定键必须与路由键完全匹配——不支持通配符。
Topic (主题) 与 Direct 相同,但绑定键中允许使用通配符。'#' 匹配零个或多个用点分隔的单词,而 '*' 精确匹配一个这样的单词。
Fanout (扇出) 路由键和绑定键被忽略——所有发布的消息都会发送到所有绑定的队列。
Headers (头部)

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

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

现在考虑一条路由键为 "NYSE.TECH.MSFT" 的消息。鉴于消息将发送到 topic 交换机,哪些绑定键会匹配它?

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

这就是所有要点。每个队列支持多个消费者以及每个交换机支持多个队列提供了灵活性。事实上,一个队列甚至可以绑定到多个交换机。现在让我们看看其中的一些场景。

RPC (远程过程调用)

AMQP 代理可以充当客户端和服务之间的 RPC 机制。一般的设置是这样的,使用一个 direct 交换机

rabbit-rpc

一般的顺序如下

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

从客户端的角度来看,调用可以是阻塞的或非阻塞的。然而,实现其中一种的难易程度取决于所使用的客户端库。

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

至于回复队列,它通常由客户端创建,然后客户端填充reply_to头部。另外,虽然你可以为回复使用与请求不同的交换机,但更常见的是请求和回复都使用同一个交换机。

发布/订阅 (Pub/Sub)

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

rabbit-pub-sub

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

工作分发

想象你的应用程序有一堆需要执行的任务。使用 AMQP,你可以连接多个消费者,使得每个任务只分配给其中一个消费者。发布者不关心是哪个消费者完成了工作,只关心工作被完成。这就是工作分发。

配置它非常简单,如下图所示

rabbit-work

因此,你有一个绑定到交换机的队列,多个消费者共享该队列。无论有多少消费者,这种设置保证了只有一位消费者处理特定的消息。

这些是 AMQP 代理的三种主要使用模式。虽然我分别描述了它们,但将它们组合起来使用是相当普遍的。例如,在 RPC 模式中,可以有多个服务共享同一个队列(工作分发)。如何配置交换机和队列完全取决于你,现在你应该对如何为你的情况找到合适的设置有了足够的了解。

如果你想进一步深入了解 AMQP,可以查阅规范本身,特别是关于 General Architecture(总体架构)的部分。要开始使用 RabbitMQ,只需访问其网站

获取 Spring 资讯

订阅 Spring 资讯,保持联系

订阅

领先一步

VMware 提供培训和认证,助你加速提升。

了解更多

获取支持

Tanzu Spring 通过单一订阅,为 OpenJDK™、Spring 和 Apache Tomcat® 提供支持和二进制文件。

了解更多

近期活动

查看 Spring 社区的所有近期活动。

查看全部