跳到主要内容

在此,我们列出了 0-9-1 规范中的错误和歧义,并提供了我们的解释和更正。

1 “消费者”的使用

“消费者”一词的使用相当随意。一般来说,我们将其解释为“客户端”,除非在明确指代三元组 {connection, channel, consumer-tag} 的情况下。

第一个通用含义的例子是在 channel.flow 中:“此方法要求对等方暂停或重启消费者发送的内容数据流。”

第二个特定用法的例子是在 basic.consume 中:“请求独占消费者访问权限,意味着只有此消费者可以访问队列。”

(这真的应该在后续的规范修订中清理。)

2 requeue=false 的 basic.recover

basic.recover 的节是这样说的

“此方法要求服务器重新传送指定通道上的所有未确认消息。可能会重新传送零个或多个消息。”

以及

“如果 [requeue] 为零,则消息将重新传送给原始接收者。如果此位为 1,则服务器将尝试重新将消息放入队列,然后可能将其传递给备用订阅者。”

这些没有考虑到

  • 作为 basic.get 的结果而移交的消息;或者,
  • 自那时起已取消的消费者。

RabbitMQ 不会尝试重新传送作为 basic.get 响应发送的消息,并且不会尝试查明消费者是否已被取消。

(可能对此规范不足的最佳解决方案是删除 requeue 标志,而只是始终重新排队。)

3 字段类型

Tony 发送给 AMQ 开发列表

The changes from 0-9's table field type definitions (S,I,D,T,F,V) to 0-9-1's type definitions (t,b,B,U,u,I,i,L,l,f,d,D,s,S,A,T,F,V) are not, as far as I can tell, the changes that were discussed in the 0-9-1 SIG.

The types mentioned in the final spec do not line up with any of the variants discussed in the 0-9-1 SIG. In particular, they are gratuitously INCOMPATIBLE with the Qpid-java 0-9 field type extensions, which are also implemented by the RabbitMQ client libraries and the RabbitMQ broker.

The final word I can find on the subject by trawling my archive is a message from John O'Hara (attached). Unless there was subsequent discussion that I've missed, I would have expected the final version of the 0-9-1 spec to at least be based on the list John provided.

Here's a tabular summary of the state of things:

0-9 0-9-1 Qpid/Rabbit Type Remarks

t t Boolean b b Signed 8-bit B B Unsigned 8-bit U s Signed 16-bit (A1) u u Unsigned 16-bit I I I Signed 32-bit i i Unsigned 32-bit L l Signed 64-bit (B) l Unsigned 64-bit f f 32-bit float d d 64-bit float D D D Decimal s Short string (A2) S S S Long string A A Array (C) T T T Timestamp (u64) F F F Nested Table V V V Void x Byte array (D)

Remarks:

A1, A2: Notice how the types CONFLICT here. In Qpid and Rabbit, 's' means a signed 16-bit integer; in 0-9-1, it means a short string.

B: Notice how the signednesses CONFLICT here. In Qpid and Rabbit, 'l' means a signed 64-bit integer; in 0-9-1, it means an unsigned 64-bit integer.

C: I cannot find any discussion about the addition of this. Is my archive missing a few messages?

D: John [O'Hara]objected to this when John proposed a list himself. I believe it to be vital: byte arrays are not strings. Furthermore, Qpid and Rabbit already have code deployed that uses this type specifier.

RabbitMQ 继续使用第三列中的标签。

其他注意事项

  • 小数编码:“它们被编码为一个八位字节,表示位数,后跟一个长有符号整数”,但语法与此相矛盾,并指出:“decimal-value = scale long-uint”。我们将十进制值视为有符号整数。

4 field-array

协议语法具有 field-array 的产生式,它出现在 field-value 的产生式的右侧。

field-array = long-int *field-value

此处没有对此的解释,就像对 field-table 的解释一样。特别是,没有任何解释 long-int 值代表什么。RabbitMQ 使用编码字段值的总字节长度,类似于 field-table;它也将其视为 long-uint,因为那是 field-table 使用的(假设“long-int”是笔误)。

5 独占和自动删除删除

AMQP 0-8 和 0-9-1 都有关于删除独占和自动删除队列的规则。但是,它们没有指定这是否同步发生;即,在代理响应其对等方之前。

(实际上 0-8 说服务器应该等待“适当的时间”)。

RabbitMQ 现在同步删除自动删除队列和基本取消队列、通道关闭和连接关闭,并同步删除后两者的独占队列。

如果满足以下条件,则不会删除自动删除队列

  • 它没有消费者 - 使用 basic.get 消费不算作有消费者
  • 消费者未能发送 basic.cancel(例如,消费者崩溃)

6 队列和交换“等效”和“预先存在”规则

“如果未设置 [passive] 并且队列存在,则服务器必须检查现有队列是否具有相同的 durable、exclusive、auto-delete 和 arguments 字段值。如果请求的队列与这些字段匹配,则服务器必须使用 Declare-Ok 响应,如果不匹配,则必须引发通道异常。”

交换声明也是如此。

这些规则未能指定确切的错误代码。RabbitMQ 使用“precondition-failed”。

此外,exchange.declare 规范包含一个单独的“类型化”规则,涉及使用不同类型重新声明。该规则是多余的,因为它被等效规则所包含,并且指定了“not-allowed”的连接错误。应删除该规则。

7 独占规则 (amqp0-9-1.xml:1503)

“一个客户端声明一个独占队列。不同连接上的第二个客户端尝试声明、绑定、消费、清除、删除或声明同名的队列。”

“声明”出现了两次,而 basic.get 和 queue.unbind 完全没有出现(basic.cancel 在任何情况下都只在通道本地)。RabbitMQ 将排他性扩展到 queue.declare(包括被动声明)、queue.bind、queue.unbind、queue.purge、queue.delete、basic.consume 和 basic.get。

8 独占和其他错误

一般来说,RabbitMQ 将排他性优先于其他事项;即,它首先检查这一点,即使存在其他问题(如不等效),也会发送 resource-locked。

9 被动和 no-wait (amqp0-9-1.xml:1214,1423)

“.. 具有 passive 和 no-wait 的声明无效。”

但是,在独占规则 (amqp0-9-1.xml:1486) 中明确禁止被动声明已存在且对另一个连接独占的队列,并且被动声明不存在的队列或交换应发送通道异常。RabbitMQ 忽略上面引用的句子;即,即使使用 no-wait,如果被动声明了对另一个连接独占的队列或不存在的队列或交换,它也会发送通道异常。

10 channel-max 和 frame-max 的特殊情况值

connection.tune 中 channel-max 字段的规范指出“零表示未指定限制”,frame-max 也存在类似的特殊情况;但是,在 tune-ok 中,“上限”规则指出:“如果客户端指定的通道最大值[/frame-max]高于服务器提供的值,则服务器必须关闭连接,而无需尝试协商关闭”。

如果服务器在 tune 中发送零,则这是不明确的:如果字面上理解该规则,则不允许客户端在 tune-ok 中发送除零以外的任何值,因此它无法协商降低限制。

RabbitMQ 不限制 channel-max,并将 tune-ok 中的任何数字都视为有效。它确实限制了 frame-max,并检查 tune-ok 中发送的值是否小于或等于。

11 frame-max、方法和内容头帧

规范指出 frame-max(在 connection.tune 和 connection.tune-ok 中协商)给出了总帧大小的上限;即,包括帧头和帧尾八位字节。但是,无法跨多个帧拆分方法或内容头。RabbitMQ 当前忽略方法和内容头的 frame-max。

12 心跳格式

语法具有以下产生式

heartbeat = %d8 %d0 %d0 frame-end

这不符合帧格式。RabbitMQ、Qpid 和 OpenAMQ 都发送八个字节,遵循帧格式

frame-type (octet) = %d8 channel (short) = %d0 %d0 payload (long) = %d0 %d0 %d0 %d0 frame-end (octet) = %xCE

13 心跳监控

规范说

The client should start sending heartbeats after receiving a Connection.Tune method, and start monitoring heartbeats after receiving Connection.Open.

但当然,客户端发送 Connection.Open。

14 默认交换机

规范对默认交换机有点含糊不清:它说

The server MUST NOT allow clients to access the default exchange except by specifying an empty exchange name in the Queue.Bind and content Publish methods.

但是,0-10 规范使事情变得更加严格

The default exchange MUST NOT be accessible to the client except by specifying an empty exchange name in a content publish command (such as message.transfer). That is, the server must not let clients explicitly bind, unbind, delete, or make any other reference to this exchange.

RabbitMQ 阻止对默认交换机的除发布之外的所有访问。这包括交换机重新声明和队列绑定到队列的重新声明。对于交换机到交换机的绑定,源或目标都不允许是默认交换机。

15 EBNF 语法

EBNF 语法(第 32 页)已损坏8*OCTET应该是8 OCTET,根据语法描述8*OCTET表示至少 8 个八位字节(参见项目符号“运算符“*”...”)。

16 字符串 vs 字节

规范对 utf-8 字符串与原始字节感到困惑。例如,什么是“字符”?有效的代码点?八位字节?相等意味着什么?相同的字节?在某种范式中相同?格式错误的 utf-8 如何处理?第 31 页上的语法表明字符串只是原始字节(即,可以包含无效的 utf-8),但规范的其他部分似乎与此相矛盾(例如,4.2.5.3,第 35 页)

17 缺少 312 (“No route”) 通道异常

第 1.2 节应该定义异常 312 “No route”,它曾经存在于 0-9 中,并且是 RabbitMQ 在“mandatory”消息无法传递到任何队列时通过“basic.return”返回的内容。

18 502 通道异常

队列名称下的第 1.1 节指出“如果客户端未声明队列,并且该方法需要队列名称,则将导致 502(语法错误)通道异常”。这实际上是连接异常。

19 auto-delete vs exclusive

第 4.5 节指出“当服务器关闭连接时,它会删除该连接拥有的任何自动删除”。这不是自动删除的含义 - 这应该大概说“...删除拥有的任何独占队列...”。

20 amq.* 交换机的持久性

规范没有说明标准交换机是否应该是持久的。RabbitMQ 将它们声明为持久的。不幸的是,Qpid Java 客户端强制将它们声明为非持久的(通过 N 个级别的抽象),从而几乎完全破坏了互操作性。

21 拒绝具有未知传递标签的消息

规范没有说明当客户端发送带有未知传递标签的 basic.reject 时应该发生什么。服务器可能应该以与 basic.ack 相同的方式处理此问题,即引发 precondition_failed 错误。

22 心跳错误

在第 4.2.7 节中,规范指出“接收到无效心跳帧的对等方必须引发回复代码为 501(帧错误)的连接异常。”目前尚不清楚如何将心跳帧识别为心跳帧并同时无效。

23 必需的 server-properties

connection.start 的 xml 规范指出 server-properties 字段“应至少包含以下字段:'host',指定服务器主机名或地址 ...”。'host' 字段已添加到 0-9 的此列表中;它在 0-8 中不存在。它是唯一一个其值不是由实现自由选择的字段。此外,完全不清楚此处应包含哪些信息以及客户端可以从中学习到什么。主机名可能无法唯一标识代理。并且它可能无法通过 DNS 解析。防火墙和代理的存在也使得 IP 地址也是如此。此外,包含此信息可能会向应该保持隐藏的客户端泄露代理网络基础设施的详细信息。

因此,此字段不应是必需的。RabbitMQ 不提供它。

24 所有同步命令上的 'nowait'

所有同步命令都应该有一个 'nowait' 标志,使其异步,除非在某些特定情况下这样做没有意义。目前违反此规则的是:“queue.unbind”、“basic.qos”、“basic.recover”(它有同步和异步变体)、“tx.select”、“tx.commit”、“tx.rollback”。在这些命令中,“queue.unbind”是最明显的偏离既定模式的命令。

25 弃用自动删除交换机

0-9-1 中弃用了“exchange.declare”上的“auto-delete”标志。自动删除交换机实际上非常有用,因此应恢复此标志。

另请注意,规范仍然引用自动删除交换机:“交换机可以是持久的、临时的或自动删除的。”(第 26 页)。

RabbitMQ 支持自动删除交换机。

26 弃用内部交换机

0-9-1 中弃用了“exchange.declare”上的“internal”标志。内部交换机(0-8 将其定义为用于内部布线的交换机,客户端不允许直接发布到该交换机)实际上非常有用,特别是与交换机到交换机绑定等扩展结合使用时。因此,应恢复此标志。

另请注意,基本发布规则 (02) 仍然引用内部交换机:“如果交换机被声明为内部交换机...”

27 基本内容字段说明引用队列

基本内容字段上的 XML 注释指出,“delivery-mode”适用于“实现持久性的队列”。这是具有误导性的(因为它提到了队列)并且是循环的。delivery-mode 控制消息在代理重启后是否仍然存在。

同样,注释指出“priority”适用于“实现优先级的队列”。代理可以在消息处理的所有阶段(而不仅仅是在排队时)考虑优先级。

并且从外观上看,基本内容定义的垂直对齐方式略有偏差。

28 basic.recover 同步性

尽管 basic.recover 是作为 basic.recover-async 的同步版本引入的,但在 XML 规范中未将其标记为同步。

29 心跳帧类型不一致

PDF 将心跳帧类型指定为 4。XML 将其指定为 8。

30 durable vs exclusive

规范没有定义如果客户端尝试声明一个既是持久的又是独占的队列会发生什么。RabbitMQ 会将声明此类队列的请求视为对独占瞬态队列的请求。

获取帮助和提供反馈

如果您对此指南的内容或与 RabbitMQ 相关的任何其他主题有疑问,请随时使用 GitHub Discussions 或我们的社区 Discord 服务器提问。

帮助我们改进文档 <3

如果您想为站点贡献改进,其源代码可在 GitHub 上获取。只需 fork 存储库并提交拉取请求即可。谢谢!

© . All rights reserved.