AMQP 1.0
从 RabbitMQ 4.0 开始,AMQP 1.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 发布消息时,所有目标队列类型 (经典队列、仲裁队列和流) 都以其原始 AMQP 1.0 格式存储消息。如果稍后使用 AMQP 1.0 消费该消息,则无需进行协议转换。此外,根据 AMQP 1.0 规范的要求,RabbitMQ 确保裸消息的不可变性。这允许客户端不仅在消息正文上,而且在属性和应用程序属性部分上设置消息哈希、校验和和数字签名。
虚拟主机
RabbitMQ 使用虚拟主机支持逻辑多租户。
如果连接应用程序未显式指定虚拟主机,则连接将使用在rabbitmq.conf中配置的default_vhost
。
default_vhost = /
AMQP 1.0 客户端可以通过在open帧中hostname
字段的值前添加vhost:
来连接到不同的虚拟主机。
例如,要连接到名为tenant-1
的虚拟主机,客户端将hostname
字段设置为vhost:tenant-1
。
地址
AMQP 1.0 地址确定消息发送到或从何处消费。AMQP 地址引用哪个内部对象以及如何解析 AMQP 地址并非由 AMQP 1.0 规范定义。不同的 AMQP 1.0 代理可以以不同的方式解释提供的地址。
RabbitMQ 实施了功能强大且灵活的AMQ 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 空值。如 AMQP 扩展使用 AMQP 匿名终结点进行消息路由中所述,必须设置每条消息的属性部分中的to
字段。允许的to
地址字符串必须具有相同的格式,即其中之一
/exchanges/:exchange/:routing-key
/exchanges/:exchange
/queues/:queue
其中交换机必须存在。
如果消息无法路由,例如,因为没有队列绑定到目标交换机,则 RabbitMQ 会使用released
结果来结算消息。
如果发布应用程序需要将消息发布(发送)到
- 单个目标:优选使用前三种字符串格式中的一个,而不是第四种(空)格式,因为前三种格式提供略微更好的性能
- 少量不同的目标:优选为每个目标打开一个链路,并使用前三种格式中的一个
- 大量不同的目标:优选使用第四种(空)格式,在
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空值,并将消息的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空值,并将消息的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作为接收方时的结果
AMQP 1.0 结果 | 等效的AMQP 0.9.1 帧 | 描述 |
---|---|---|
已接受 | basic.ack | 消息被路由到的所有队列都已接受该消息。例如,对于仲裁队列,这意味着大多数仲裁队列副本已将消息写入磁盘。因此,发布者可以忘记/删除该消息。 |
已拒绝 | basic.nack | 消息被路由到的至少一个队列拒绝了该消息。当队列长度超过并且队列的溢出行为设置为reject-publish 时,或者当目标经典队列不可用时,就会发生这种情况。RabbitMQ还会根据使用AMQP匿名终结点进行消息路由中指定的拒绝消息,例如,如果消息的属性部分的 to 字段包含无效地址或定义了不存在的交换机。 |
已释放 | basic.return (后跟basic.ack 或basic.nack ) | RabbitMQ无法将消息路由到任何队列。这表示拓扑配置错误,例如,当没有匹配的队列绑定到目标交换机时。 |
已修改 | 目前,RabbitMQ不会使用已修改的结果来确定消息。 |
下表描述了客户端作为接收方/消费者,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 与 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 0.9.1相比,更多代理支持AMQP 1.0。
- 客户端库可用性:您使用的编程语言是否提供了支持的客户端库。
AMQP 1.0 功能
本节列出了RabbitMQ仅在AMQP 1.0中支持的功能,这些功能在AMQP 0.9.1中不可用
- 细粒度流量控制,如博客文章AMQP 1.0 流量控制的十大优势中所述
- 使用客户端应用程序可以动态调整和优先级排序它希望从特定源队列接收的消息数量。
- 安全有效地使用单个AMQP连接进行发布和消费。
- 当一个目标队列过载时,发布者可以继续以高速向其他目标队列发送消息,并且消费者可以继续以高速从同一AMQP连接上的其他源队列接收消息。
- 可以停止或暂停消费者,并在以后恢复。
- 在保持消息顺序的同时,从一个单个活动消费者到下一个消费者的优雅切换。
- 源队列可以有效地通知消费者可用消息的近似数量。
- 队列位置:RabbitMQ可以向客户端提供最新的队列拓扑和领导者信息。
- 例如,RabbitMQ AMQP 1.0 Java客户端可以通过尝试从托管队列副本的RabbitMQ节点“本地”消费以及尝试“本地”发布到托管队列领导者的节点来利用此信息。
- 这可以导致更低的集群内流量,从而降低延迟并提高吞吐量。
- 发送方结算模式
mixed
:允许发布者根据每条消息来决定是否从代理接收确认。 - 已修改的结果:允许仲裁队列消费者在重新排队或死信消息时添加和修改消息注释。
- 定义良好的类型
- 定义更好的消息头
- 增强的消息完整性:客户端不仅可以设置消息主体上的消息哈希、校验和和数字签名,还可以设置属性和应用程序属性部分上的消息哈希、校验和和数字签名,因为裸消息是不可变的。
- 流消息保真度:从流存储或检索消息时,不会丢失标题保真度,因为流以AMQP 1.0编码格式存储消息。
AMQP 0.9.1 功能
本节列出了RabbitMQ仅在AMQP 0.9.1中支持的功能,这些功能目前在AMQP 1.0中不可用
- 事务:AMQP 0.9.1提供了有限的支持,而AMQP 1.0目前不支持事务(如限制中所列)。
- 直接回复:虽然AMQP 1.0客户端仍然可以通过声明回复队列来执行远程过程调用(RPC),但直接回复功能是AMQP 0.9.1独有的。
- OAuth 2.0 令牌刷新:AMQP 0.9.1客户端可以通过方法update-secret续订令牌。AMQP 1.0目前不支持令牌续订。当令牌过期时,AMQP 1.0连接将关闭。
- AMQP 0.9.1信道拦截器:插件(例如分片插件)拦截并修改帧,目前仅支持AMQP 0.9.1。
- 通过管理UI交付指标,包括消息速率:如弃用公告中所述,应使用Prometheus。
- 检查AMQP 0.9.1信道详细信息:这可以通过管理UI或使用CLI工具来完成。目前无法检查AMQP 1.0会话和链路详细信息。
客户端
任何AMQP 1.0客户端都应该能够与RabbitMQ通信。博通公司的RabbitMQ团队开发了两个专门用于RabbitMQ的AMQP 1.0客户端库
目前,AMQP 0.9.1客户端生态系统更为广泛,博通公司的RabbitMQ团队支持更多数量的AMQP 0.9.1客户端库。
限制
RabbitMQ不支持以下AMQP 1.0功能
- "暂停"或"恢复"链路,包括
- 传输帧中的
aborted
字段 - 源和目标中的
dynamic
字段:客户端可以改为通过HTTP over AMQP在连接链路之前动态创建服务器拓扑(交换机、队列、绑定)。 - 事务
- TLS安全层的协议头(图5.1),包括协议ID为2。相反,RabbitMQ运行纯TLS服务器,因此实现了第5.2.1节。
修改的结果
使用modified结果修改消息注释在仲裁队列中受支持,但在经典队列中不受支持。考虑到流是不可变的日志,因此在流中修改消息没有意义。
如果字段undeliverable-here
为
true
,经典队列和仲裁队列将死信该消息。如果未配置死信,则该消息将被丢弃。false
,经典队列和仲裁队列将重新入队该消息。
undeliverable-here
的行为可能会在未来的RabbitMQ版本中发生变化。
例如,如果undeliverable-here = true
,将来队列可能会重新入队该消息,而不是将该消息死信,同时确保该消息不会重新传递到修改链路端点。