AMQP 1.0
AMQP 1.0 自 RabbitMQ 4.0 起已获得 原生支持。
版本协商
RabbitMQ 开箱即用地原生支持 AMQP 1.0 和 AMQP 0.9.1,无需任何额外的插件。
默认情况下,RabbitMQ 监听端口 5672,接受 AMQP 1.0 和 AMQP 0.9.1 的 连接。
建立 TCP 或 TLS 连接并在发送任何 AMQP 帧之前,客户端会发送一个协议头,指示它希望使用 AMQP 1.0 还是 AMQP 0.9.1,如 第 2.2 节 版本协商中所述。
对于 AMQP 1.0 连接,RabbitMQ 要求使用简单身份验证和安全层(SASL),如 第 5.3 节 SASL 中所述。如果客户端未使用 SASL,RabbitMQ 将会拒绝该连接,如 图 2.13:协议 ID 拒绝示例所示。
身份验证选项
RabbitMQ 的 AMQP 1.0 实现支持多种身份验证方法
- 用户名和密码对,以及任何支持的 身份验证后端组合(最好与 TLS 结合使用以实现线上传输加密)
- JWT 令牌(OAuth 2)
- 通过
EXTERNAL身份验证机制 和rabbitmq_auth_mechanism_ssl插件进行 x.509 证书验证
协议互操作性
RabbitMQ 支持跨不同协议发布和消费消息,这需要 协议转换。
当使用 AMQP 1.0 发布消息时,所有目标队列类型(经典队列、法定队列和流)都会以原始 AMQP 1.0 格式存储消息。如果稍后使用 AMQP 1.0 消费该消息,则无需进行协议转换。此外,根据 AMQP 1.0 规范的要求,RabbitMQ 确保 裸消息的不可变性。这允许客户端不仅可以对消息体设置消息哈希、校验和和数字签名,还可以对 属性和 应用程序属性部分进行设置。
虚拟主机
RabbitMQ 支持通过 虚拟主机进行逻辑多租户。
如果连接应用程序未明确指定虚拟主机,则连接将使用 `rabbitmq.conf` 中配置的 `default_vhost` 。
default_vhost = /
AMQP 1.0 客户端可以通过在 open 帧中添加 `vhost:` 前缀到 `hostname` 字段的值,来连接到不同的 虚拟主机。
例如,要连接到名为 `tenant-1` 的虚拟主机,客户端会将 `hostname` 字段设置为 `vhost:tenant-1`。
地址
AMQP 1.0 地址决定了消息发送到哪里或从哪里消费。AMQP 1.0 规范并未定义 AMQP 地址指向哪个内部对象以及如何解析 AMQP 地址。不同的 AMQP 1.0 代理可以根据需要解释提供的地址。
RabbitMQ 实现了一个强大而灵活的 AMQP 0.9.1 模型,包括 交换机、队列和 绑定。因此,与 RabbitMQ 通信的 AMQP 客户端将消息发送到交换机,并从队列中消费消息。因此,RabbitMQ 理解和解析的 AMQP 地址包含交换机名称、队列名称和路由键。
RabbitMQ 4.0 引入了一种新的 RabbitMQ 特定 AMQP 地址格式,称为 v2。旧的 RabbitMQ 3.x 地址格式称为 v1。
AMQP 客户端应使用 v2 地址格式。
v1 地址格式在 RabbitMQ 4.0 中已弃用,未来 RabbitMQ 版本将不再支持。v1 格式是否仍受支持取决于 已弃用的功能标志 `amqp_address_v1`,该标志在 RabbitMQ 4.0 中的弃用阶段为 `permitted_by_default`。
v2 地址
本节定义了新的 v2 地址格式。
v2 目标地址
可能的 v2 目标地址格式为:
/exchanges/:exchange/:routing-key/exchanges/:exchange/queues/:queue<null>
前三种格式是 字符串。
第一种格式 ` /exchanges/:exchange/:routing-key` 会将给定 链接上的所有消息发送到交换机 `:exchange`,路由键为 `:routing-key`。
第二种格式 ` /exchanges/:exchange` 会将给定链接上的所有消息发送到交换机 `:exchange`,路由键为空字符串 `""`。这对于忽略路由键的交换机类型很有用,例如 扇形交换机 或 头交换机。
在上述两种格式中,不允许设置默认交换机 `""`。请改用第三种格式。
第三种格式 ` /queues/:queue` 会将给定链接上的所有消息发送到队列 `:queue`。
该队列必须存在。内部而言,此队列目标仍使用默认交换机。因此,用户需要对交换机 `amq.default` 具有写入权限。
前三种格式要求给定链接上的所有消息的目标地址相同。如果需要在同一链接上的不同消息设置不同的交换机、路由键或队列,请使用第四种格式。
第四种格式是 AMQP null 值。如 AMQP 扩展 使用 AMQP 匿名终结点进行消息路由中所述,每条消息的 属性部分的 `to` 字段必须设置。允许的 `to` 地址字符串必须具有相同的格式,即以下之一:
/exchanges/:exchange/:routing-key/exchanges/:exchange/queues/:queue
其中交换机必须存在。
如果消息 无法路由,例如因为没有队列绑定到目标交换机,RabbitMQ 会以 released 结果来处理该消息。
如果发布应用程序需要将消息发布(发送)到
- 单个目标:优先使用前三种字符串格式之一而不是第四种(null)格式,因为前三种格式提供更好的性能。
- 少量不同的目标:优先为每个目标打开一个链接,并使用前三种格式之一。
- 大量不同的目标:优先使用第四种(null)格式,在 `to` 字段中定义每个目标。
v2 源地址
唯一有效的 v2 源地址字符串格式为:
/queues/:queue
其中客户端从队列 `:queue` 消费消息。该队列必须存在。
百分比编码
v2 地址格式要求交换机名称、路由键和队列名称根据 RFC 3986 进行百分比编码。
例如,一个客户端想要发送到交换机 `amq.direct`,路由键为 `my-routing_key/123`,必须使用目标地址 `/exchanges/amq.direct/my-routing_key%2F123`。
请注意,v2 地址格式中的百分比编码必须应用于所有需要 `address` 的 AMQP 字段。
v1 地址
本节列出了 **已弃用** 的 v1 地址字符串格式。
v1 目标地址
/exchange/:exchange/:routing-key/exchange/:exchange/topic/:routing-key/amq/queue/:queue/queue/:queue:queue/queue
第一种格式 ` /exchange/:exchange/:routing-key` 会将给定链接上的所有消息发送到交换机 `:exchange`,路由键为 `:routing-key`。等效的 v2 格式是 ` /exchanges/:exchange/:routing-key`。
第二种格式 ` /exchange/:exchange` 会将给定链接上的所有消息发送到交换机 `:exchange`,同时路由键可以选填在消息 属性部分的 `subject` 字段中。在 v2 中,定义每条消息不同的路由键需要将目标地址设置为 AMQP null 值,并将消息的 `to` 字段设置为 ` /exchanges/:exchange/:routing-key`。
第三种格式 ` /topic/:routing-key` 会将给定链接上的所有消息发送到 RabbitMQ 的默认主题交换机 `amq.topic`,主题为 `routing-key`。在 v2 中,使用 ` /exchanges/amq.topic/:routing-key`。
第四种格式 ` /amq/queue/:queue` 会将给定链接上的所有消息发送到队列 `:queue`(更精确地说,内部是发送到默认交换机,路由键为 `:queue`)。队列 `:queue` 必须存在。在 v2 中,使用 ` /queues/:queue`。
第五种格式 ` /queue/:queue` 与第四种格式的语义类似。但是,RabbitMQ 会自动声明队列 `:queue`,即在队列不存在时创建该队列。队列永远不会被 RabbitMQ 自动删除。在 v2 中,使用 ` /queues/:queue`。RabbitMQ 4.0 允许 AMQP 客户端创建 RabbitMQ 拓扑,包括具有客户端定义的队列类型、属性和参数的队列。因此,RabbitMQ 本身无需为给定的队列目标地址格式自动声明特定队列。
第六种格式 `:queue` 对于第五种格式是冗余的。
第七种格式会将消息发送到消息 属性部分的 `subject` 字段中提供的队列。在 v2 中,要将消息发送到不同的队列,请将目标地址设置为 AMQP null 值,并将消息的 `to` 字段设置为 ` /queues/:queue`。
v1 源地址
/exchange/:exchange/:binding-key/topic/:binding-key/amq/queue/:queue/queue/:queue:queue
第一种格式 ` /exchange/:exchange/:binding-key` 会让 RabbitMQ 声明一个队列,并将该队列绑定到交换机 `:exchange`,绑定键为 `:binding-key`。然后从该队列消费消息。
第二种格式 ` /topic/:binding-key` 会让 RabbitMQ 声明一个队列,并将该队列绑定到默认主题交换机 `amq.topic`,主题过滤器为 `:binding-key`。然后从该队列消费消息。
第三种格式 ` /amq/queue/:queue` 会让 RabbitMQ 从队列 `:queue` 消费。队列 `:queue` 必须存在。
第四种格式 ` /queue/:queue` 会让 RabbitMQ 声明队列 `:queue` 并从中消费。
第五种格式 `:queue` 对于第四种格式是冗余的。
如前所述,RabbitMQ 4.0 允许 AMQP 客户端创建 RabbitMQ 拓扑,包括具有客户端定义的队列类型、属性和参数的队列。因此,RabbitMQ 本身无需为给定的队列源地址格式自动声明特定队列。在 v2 中,客户端应首先声明自己的队列和绑定,然后使用源地址 ` /queues/:queue` 进行连接,这将导致客户端从该队列进行消费。
消息注解
当消息传递给消费者时,RabbitMQ 至少设置以下两个 消息注解:
- `x-exchange` 设置为消息最初发布到的 交换机。
- `x-routing-key` 设置为消息最初发布的路由键。
这些消息注解的来源方式取决于消息最初如何发送到 RabbitMQ。例如,它们可能来自:
- AMQP 1.0 发布者连接到的 目标的 `address` 字段。
- AMQP 1.0 消息 属性部分的 `to` 字段。
- 如果消息最初使用 AMQP 0.9.1 发布,则是 AMQP 0.9.1 `basic.publish` 帧的 `exchange` 和 `routing_key` 字段。
- 如果消息最初使用 MQTT 发布,则是 MQTT PUBLISH 数据包的 主题名称。
- 如果消息最初使用 RabbitMQ Stream 协议发布,则是 流的名称。
但是,AMQP 1.0 客户端不应将带有 `x-exchange` 或 `x-routing-key` 消息注解的消息发布到 RabbitMQ。RabbitMQ 不会解释它们。相反,如果 AMQP 1.0 客户端想将消息重新发布到原始交换机并使用原始路由键,则应相应地设置 地址。
结果
一个 结果指示了接收方处理(消息)传递的结果。
下表描述了当客户端是发送方/发布者/生产者,而 RabbitMQ 作为接收方时的结果:
| AMQP 1.0 结果 | 等效 AMQP 0.9.1 帧 | 描述 |
|---|---|---|
| 已接受 | basic.ack | 消息路由到的 **所有** 队列都已接受该消息。例如,对于 法定队列,这意味着大多数法定队列副本已将消息写入磁盘。因此,发布者可以忘记/删除该消息。 |
| 已拒绝 | basic.nack | 消息路由到的至少一个队列已拒绝该消息。当 队列长度超过限制且队列的 溢出行为设置为 `reject-publish` 时,或者当目标 经典队列不可用时,会发生这种情况。 RabbitMQ 还会按照 使用 AMQP 匿名终结点进行消息路由中的规定拒绝消息,例如,如果消息 属性部分的 `to` 字段包含无效地址或定义了不存在的交换机。 |
| 已释放 | `basic.return` (后跟 `basic.ack` 或 `basic.nack`) | RabbitMQ 无法将消息路由到任何队列。这表明拓扑配置错误,例如目标交换机没有绑定匹配的队列。 |
| 已修改 | 目前,RabbitMQ 不会使用 `modified` 结果来处理消息。 |
下表描述了当客户端是接收方/消费者,而 RabbitMQ 作为发送方时的结果:
| AMQP 1.0 结果 | 等效 AMQP 0.9.1 帧 | 描述 |
|---|---|---|
| 已接受 | basic.ack | 消费者已成功处理消息。因此 RabbitMQ 可以删除该消息。 |
| 已拒绝 | `basic.nack` 或 `basic.reject`,并带 `requeue=false` | 消费者指示消息无效且无法处理。RabbitMQ 会将消息 发送到死信队列(如果未配置死信,则会丢弃消息)。 |
| 已释放 | `basic.nack` 或 `basic.reject`,并带 `requeue=true` | 消费者未处理消息。RabbitMQ 会将消息重新排队。该消息将被传递给同一或不同的消费者。 |
| 已修改 | 消费者未处理消息,但修改了 消息注解。 如果 `undeliverable-here=true`,RabbitMQ 会将消息发送到死信队列(如果未配置死信,则会丢弃消息)。 如果 `undeliverable-here=false`,RabbitMQ 会将消息重新排队。 请参阅 下文了解更多信息。 |
AMQP 1.0 vs. AMQP 0.9.1
顾名思义,AMQP 1.0 是更现代的协议。它是 ISO/IEC 19464 和 OASIS 标准,而 AMQP 0.9.1 不是官方标准。有关协议的更详细比较,请参阅我们的 AMQP 1.0 博客文章。
选择正确的协议取决于多种因素,包括:
- 功能需求:您是否需要 AMQP 1.0 或 AMQP 0.9.1 的特定功能。
- 互操作性:如果与其他消息代理的互操作性很重要,请注意,更多代理支持 AMQP 1.0 而不是 AMQP 0.9.1。
- 客户端库可用性:您的编程语言是否提供受支持的 客户端库。
AMQP 1.0 功能
本节列出了 RabbitMQ 在 AMQP 1.0 中独有支持,但在 AMQP 0.9.1 中不可用的功能:
- 细粒度流控,正如博客文章 AMQP 1.0 流控的十个优势中所解释的。
- 消费客户端应用程序可以动态地调整和优先处理它希望从特定源队列接收多少消息。
- 在同一 AMQP 连接上安全高效地同时进行发布和消费。
- 当一个目标队列过载时,发布者可以继续以高速向其他目标队列发送消息,消费者也可以在同一 AMQP 连接上以高速从其他源队列接收消息。
- 消费者可以停止或暂停,稍后恢复。
- 在 单个活动消费者之间优雅地切换,同时保持消息顺序。
- 源队列可以有效地告知消费者可用消息的大致数量。
- AMQP 过滤器表达式:RabbitMQ 在通过 AMQP 1.0 从流消费时实现 AMQP 过滤器表达式。
- 服务器端评估复杂的 SQL 表达式。
- 结合使用 Bloom 过滤器和 AMQP 过滤器表达式时,RabbitMQ 允许进行高效的块级过滤,然后进行精确的消息级过滤,以实现复杂的业务逻辑——所有这些都在服务器端完成。
- 通过仅分发客户端实际感兴趣的消息,减少 RabbitMQ 和客户端之间的网络流量。
- 允许多个并发客户端,每个客户端仅消费消息的子集,同时保持消息顺序。
- 队列局部性:RabbitMQ 可以向客户端提供最新的队列拓扑和领导者信息。
- 例如,RabbitMQ AMQP 1.0 Java 客户端可以利用这些信息,尝试从托管队列副本的 RabbitMQ 节点“本地”消费,并尝试“本地”发布到托管队列领导者的节点。
- 这可以减少集群内部流量,降低延迟并提高吞吐量。
- WebSocket:VMware Tanzu RabbitMQ 支持 AMQP 1.0 over WebSocket,允许在浏览器中运行的应用程序使用 AMQP 1.0 与 RabbitMQ 通信。
- Modified Outcome:允许法定队列的消费者在重新排队或将消息发送到死信队列时添加和修改 消息注解。
- Sender Settle Mode
mixed:允许发布者根据每条消息决定是否从代理接收 确认。 - 明确定义的 类型
- 更明确定义的 消息头
- 增强的消息完整性:客户端可以将消息哈希、校验和和数字签名设置在消息体以及 属性和 应用程序属性部分,因为裸消息是不可变的。
- 流消息保真度:在流中存储或检索消息时,由于流以 AMQP 1.0 编码格式存储消息,因此不会丢失头信息的保真度。
AMQP 0.9.1 功能
本节列出了 RabbitMQ 在 AMQP 0.9.1 中独有支持,目前在 AMQP 1.0 中不可用的功能:
- AMQP 0.9.1 Channel Interceptor:插件(例如 Sharding Plugin)可以拦截和修改帧,但这些功能 目前仅支持 AMQP 0.9.1。
- 管理 UI 中的消息速率:管理 UI 中显示 AMQP 0.9.1 连接的交换机和队列的消息速率。这些速率对于 AMQP 1.0 连接不可用。
- 事务:AMQP 0.9.1 提供有限支持,而 AMQP 1.0 目前不支持事务(如 局限性中所列)。
客户端
任何 AMQP 1.0 客户端都应该能够与 RabbitMQ 通信。Broadcom 的 RabbitMQ 团队开发了两个 专门针对 RabbitMQ 的 AMQP 1.0 客户端库。
有关更多信息,请参阅 AMQP 客户端库页面。
目前,AMQP 0.9.1 客户端生态系统更为广泛,Broadcom 的 RabbitMQ 团队支持更多的 AMQP 0.9.1 客户端库。
限制
RabbitMQ 不支持以下 AMQP 1.0 功能:
- “暂停”或“恢复” 链接,包括:
- `transfer` 帧中的 `aborted` 字段
- 事务
- TLS 安全层的协议头(图 5.1),包括协议 ID 为 2。相反,RabbitMQ 运行纯 TLS 服务器,因此实现了 第 5.2.1 节。
Modified Outcome
在 法定队列中支持使用 modified 结果修改消息注解,但在 经典队列中不支持。修改 流中的消息没有意义,因为流是一个不可变日志。
如果 `undeliverable-here` 字段为:
- `true`,经典队列和法定队列会将消息 发送到死信队列。如果未配置死信,则消息将被丢弃。
- `false`,经典队列和法定队列会将消息重新排队。
AMQP 1.0 Modified Outcome 博客文章 描述了使用场景。
`undeliverable-here` 的行为在未来的 RabbitMQ 版本中可能会发生变化。
例如,如果 `undeliverable-here = true`,未来队列可能会将消息重新排队,而不是发送到死信队列,同时确保消息不会再次传递给修改它的链接端点。