这里列出了 0-9-1 规范中的错误和歧义,并提供了我们的解释和更正。
1 “消费者”的使用
“消费者”一词使用相当随意。一般来说,我们将它解释为“客户端”,除非在明显专门指代三元组 {连接、通道、消费者标签} 的情况下。
第一个泛指含义的一个例子是在 channel.flow 中:“此方法要求对等方暂停或重新启动由消费者发送的内容数据流。”
第二个特定用法的例子是在 basic.consume 中:“请求独占消费者访问,这意味着只有此消费者可以访问队列。”
(此问题应在规范的后续修订版中得到真正解决。)
2 basic.recover 且 requeue=false
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 一样。特别是,没有任何内容解释长整型值代表什么。RabbitMQ 使用编码字段值的总字节长度,类似于 field-table;它还将其视为长无符号整数,因为 field-table 使用的是长无符号整数(假设“长整型”是错别字)。
5 独占和自动删除删除
AMQP 0-8 和 0-9-1 都有一些关于删除独占和自动删除队列的规则。但是,它们没有指定是否同步发生;即,在代理响应其对等方之前。
(实际上,0-8 指出服务器应该等待“礼貌的时间”)。
RabbitMQ 现在与 basic.cancel、通道关闭和连接关闭同步删除自动删除队列,并与后两者同步删除独占队列。
如果以下情况,则不会删除自动删除队列
- 它没有消费者 - 使用 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)
“一个客户端声明了一个独占队列。另一个连接上的第二个客户端尝试声明、绑定、使用、清除、删除或声明同名的队列。”
“declare”出现两次,而 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)
“.. 同时使用被动和 no-wait 的声明无效。”
但是,独占规则 (amqp0-9-1.xml:1486) 明确禁止被动声明存在且独占于另一个连接的队列,并且被动声明不存在的队列或交换机应发送通道异常。RabbitMQ 忽略了上面引用的句子;即,即使使用 no-wait,如果被动声明了独占于另一个连接的队列或不存在的队列或交换机,它也会发送通道异常。
10 channel-max 和 frame-max 的特殊情况值
connection.tune 中 channel-max 字段的规范指出“零表示没有指定限制”,并且 frame-max 也有类似的特殊情况;但是,在 tune-ok 中,“上限”规则指出:“如果客户端指定的 channel max [/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 防止除发布以外的所有对默认交换机的访问。这包括交换机重新声明和对队列的 queue.bind 重新声明。对于交换机到交换机的绑定,源和目标都不允许是默认交换机。
15 EBNF 语法
EBNF 语法(第 32 页)已损坏8*OCTET应为8 OCTET,根据语法描述8*OCTET表示至少 8 个八位字节(请参阅要点“运算符“*”…”)。
16 字符串与字节
规范对 utf-8 字符串与原始字节感到困惑。例如,什么是“字符”?有效的代码点?八位字节?相等意味着什么?相同的字节?在某种规范形式下相同?如何处理格式错误的 utf-8?第 31 页上的语法表明字符串只是原始字节(即可以包含无效的 utf-8),但规范的其他部分似乎与之矛盾(例如,4.2.5.3,第 35 页)
17 缺少 312(“无路由”)通道异常
第 1.2 节应该定义一个异常 312“无路由”,该异常曾经存在于 0-9 中,并且是 RabbitMQ 在“强制”消息无法传递到任何队列时通过 'basic.return' 发送回的内容。
18 502 通道异常
第 1.1 节在 queue-name 下写道:“如果客户端未声明队列,并且该方法需要队列名称,则将导致 502(语法错误)通道异常”。这实际上是连接异常。
19 自动删除与独占
第 4.5 节写道“当服务器关闭连接时,它会删除该连接拥有的任何自动删除对象”。这与自动删除的含义不符——此处应该写成“……删除该连接拥有的任何独占队列……”。
20 amq.* 交换机的持久性
规范中没有说明标准交换机是否应该持久化。RabbitMQ 将其声明为持久化的。不幸的是,Qpid Java 客户端强制将其声明为非持久化的(通过 N 个抽象层),从而几乎完全破坏了互操作性。
21 使用未知投递标签拒绝消息
规范中没有说明当客户端发送带有未知投递标签的 basic.reject 时应该发生什么。服务器可能应该以与 basic.ack 相同的方式处理此情况,即引发 precondition_failed 错误。
22 心跳错误
在第 4.2.7 节中,规范写道“接收无效心跳帧的端点必须引发连接异常,回复代码为 501(帧错误)”。目前尚不清楚如何将心跳帧识别为心跳帧并同时将其识别为无效帧。
23 服务器所需的属性
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' 最明显地偏离了既定的模式。
在 0-9-1 中,'exchange.declare' 上的 'auto-delete' 标志被弃用。自动删除交换机实际上非常有用,因此应该恢复此标志。
另请注意,规范仍然引用了自动删除交换机:“交换机可以是持久化的、临时的或自动删除的。”(第 26 页)。
RabbitMQ 支持自动删除交换机。
26 内部交换机的弃用
在 0-9-1 中,'exchange.declare' 上的 'internal' 标志被弃用。内部交换机(0-8 将其定义为用于内部连接的交换机,客户端不允许直接发布到这些交换机)实际上非常有用,尤其是在与交换机到交换机绑定等扩展结合使用时。因此应该恢复此标志。
另请注意,基本发布规则 (02) 仍然引用了内部交换机:“如果交换机被声明为内部交换机……”
27 基本内容字段说明引用队列
basic 内容字段上的 XML 注释指出“delivery-mode”用于“实现持久性的队列”。这具有误导性(因为它提到了队列)并且是循环的。delivery-mode 控制消息在代理重新启动后是否仍然存在。
类似地,注释指出“priority”用于“实现优先级的队列”。代理可以在消息处理的所有阶段考虑优先级,而不仅仅是在入队时。
而且,从美观的角度来看,基本内容定义的垂直对齐略有偏差。
28 basic.recover 同步性
尽管 basic.recover 被引入作为 basic.recover-async 的同步版本,但在 XML 规范中没有将其标记为同步。
29 心跳帧类型不一致
PDF 将心跳帧类型指定为 4。XML 将其指定为 8。
30 持久化与独占
规范没有定义如果客户端尝试声明一个既持久又独占的队列会发生什么。RabbitMQ 会将声明此类队列的请求视为对独占瞬态队列的请求。
获取帮助和提供反馈
如果您对本指南的内容或与 RabbitMQ 相关的任何其他主题有任何疑问,请随时使用 GitHub 讨论 或我们的社区 Discord 服务器 提问。
帮助我们改进文档 <3
如果您想为网站做出改进,其源代码可在 GitHub 上获取。只需分叉存储库并提交拉取请求即可。谢谢!