bigywater.tech
常见报错 · 57

RabbitMQ PRECONDITION_FAILED - inequivalent arg:整数 5000 和字符串 "5000" 报 received '5000' but current is '5000',只关闭 channel,改成 quorum 只能删除重建(5 条消息丢失)

重新部署后 RabbitMQ 客户端报 “PRECONDITION_FAILED - inequivalent arg”,几乎总是因为改了队列声明的某个参数。这篇用 RabbitMQ 4.4.0 在本机逐个复现:哪些改动会触发、报错文字怎么读(包括值一样还报错的情形)、出错对 channel 和未确认消息的影响,以及两种修法各自的代价。

406
所有 inequivalent arg 报错的 reply code;只关闭 channel,连接和别的 channel 不受影响
5000 vs 5000
整数与字符串类型不同时,报错里两边的值看起来一样
5 → 0
把 classic 队列改成 quorum 只能删除重建,队列里的 5 条消息被丢弃

典型报错

实测 · 本机执行输出
  x-message-ttl 60000 → 5000:
      ChannelClosedByBroker: (406, "PRECONDITION_FAILED - inequivalent arg 'x-message-ttl' for queue 'q_ttl' in vhost '/': received '5000' but current is '60000'")
  原来没有 x-message-ttl,现在加上:
      ChannelClosedByBroker: (406, "PRECONDITION_FAILED - inequivalent arg 'x-message-ttl' for queue 'q_ttl2' in vhost '/': received the value '5000' of type 'signedint' but current is none")
  原来有 x-message-ttl,现在不带参数:
      ChannelClosedByBroker: (406, "PRECONDITION_FAILED - inequivalent arg 'x-message-ttl' for queue 'q_ttl3' in vhost '/': received none but current is the value '60000' of type 'signedint'")
  参数完全相同(对照):第二次声明成功(等价)

再次声明必须与现有队列等价,否则 406。

哪些改动会触发

实测 · 本机执行输出
  durable True → False:
      ChannelClosedByBroker: (406, "PRECONDITION_FAILED - inequivalent arg 'durable' for queue 'q_dur' in vhost '/': received 'false' but current is 'true'")
  auto_delete False → True:
      ChannelClosedByBroker: (406, "PRECONDITION_FAILED - inequivalent arg 'auto_delete' for queue 'q_ad' in vhost '/': received 'true' but current is 'false'")
  classic → quorum(x-queue-type):
      ChannelClosedByBroker: (406, "PRECONDITION_FAILED - inequivalent arg 'x-queue-type' for queue 'q_type' in vhost '/': received 'quorum' but current is 'classic'")
  quorum → classic:
      ChannelClosedByBroker: (406, "PRECONDITION_FAILED - inequivalent arg 'x-queue-type' for queue 'q_type2' in vhost '/': received 'classic' but current is 'quorum'")
  x-max-length 100 → 200:
      ChannelClosedByBroker: (406, "PRECONDITION_FAILED - inequivalent arg 'x-max-length' for queue 'q_ml' in vhost '/': received '200' but current is '100'")
  x-dead-letter-exchange dlx1 → dlx2:
      ChannelClosedByBroker: (406, "PRECONDITION_FAILED - inequivalent arg 'x-dead-letter-exchange' for queue 'q_dlx' in vhost '/': received 'dlx2' but current is 'dlx1'")
  x-message-ttl 5000(整数)→ "5000"(字符串):
      ChannelClosedByBroker: (406, "PRECONDITION_FAILED - inequivalent arg 'x-message-ttl' for queue 'q_ty' in vhost '/': received '5000' but current is '5000'")
  交换机:
  exchange direct → topic:
      ChannelClosedByBroker: (406, "PRECONDITION_FAILED - inequivalent arg 'type' for exchange 'ex1' in vhost '/': received 'topic' but current is 'direct'")
  exchange durable True → False:
      ChannelClosedByBroker: (406, "PRECONDITION_FAILED - inequivalent arg 'durable' for exchange 'ex2' in vhost '/': received 'false' but current is 'true'")
  exchange 声明为 internal 前后:
      ChannelClosedByBroker: (406, "PRECONDITION_FAILED - inequivalent arg 'internal' for exchange 'ex3' in vhost '/': received 'true' but current is 'false'")

durable、auto_delete、队列类型、最大长度、死信交换机、交换机类型等;值相同但类型不同也算不同。

出错后的影响

实测 · 本机执行输出
  同一个 channel 上收到 3 条且没有 ack;(ready, unacked) = (0, 3)
  在这个 channel 上用不同参数重新声明 -> ChannelClosedByBroker(406)
  channel.is_closed = True,connection.is_open = True;(ready, unacked) = (3, 0)(没 ack 的消息回到了队列)
  已关闭的 channel 再声明 -> ChannelWrongStateError: Channel is closed.
  同一个连接上新开一个 channel -> 正常

只关闭 channel,未 ack 的消息回队列;声明和消费最好分开 channel。

passive 声明

实测 · 本机执行输出
  queue_declare(q_ttl, passive=True)(队列带 x-message-ttl=60000,这里没写参数)-> 成功,messages = 0
  不存在的队列 passive 声明 -> (404, "NOT_FOUND - no queue 'no_such_queue' in vhost '/'")

只检查存在性,不比较参数;不存在是 404。

policy

实测 · 本机执行输出
  $ rabbitmqctl set_policy ttl_pol "^q_pol$" '{"message-ttl":60000}' --apply-to queues
    Setting policy "ttl_pol" for pattern "^q_pol$" to "{"message-ttl":60000}" with priority "0" for vhost "/" ...
  客户端 queue_declare(q_pol) 不带任何参数 -> 成功
  rabbitmqctl list_queues name policy effective_policy_definition 里 q_pol 那行:q_pol | ttl_pol | [{<<"message-ttl">>, 60000}]
  $ rabbitmqctl set_policy ttl_pol "^q_pol$" '{"message-ttl":5000}' --apply-to queues
    Setting policy "ttl_pol" for pattern "^q_pol$" to "{"message-ttl":5000}" with priority "0" for vhost "/" ...
  改成 5000 之后:q_pol | ttl_pol | [{<<"message-ttl">>, 5000}]
  客户端再次 queue_declare(q_pol)(仍不带参数)-> 仍然成功

把可变参数交给 policy,客户端声明不带参数,可随时改。

删除重建

实测 · 本机执行输出
  classic 队列 q_mig 里有 5 条消息(ready)
  声明为 quorum -> (406, "PRECONDITION_FAILED - inequivalent arg 'x-queue-type' for queue 'q_mig' in vhost '/': received 'quorum' but cu")
  queue_delete 返回 message_count = 5(这些消息被丢弃)
  重建成 quorum 之后消息数 = 0

不可变属性只能删除重建,消息会丢。

分步演示

声明冲突
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64;报错文本都是真实复现时原样捕获的(命令行工具统一用 LC_ALL=C 取英文原文,路径换成 );所有服务都是本机上自己起的、只监听 127.0.0.1,不访问外网;计时用 time.perf_counter,数字每次运行会有小幅抖动。本篇用 Homebrew RabbitMQ 4.4.0(单节点,AMQP 127.0.0.1:9504,管理插件开启)、pika 1.4.4;每个例子用独立的队列/交换机名;rabbitmqctl 输出里的路径与主机名已换成占位符
    • 没有验证:Spring AMQP、Java / Go 客户端里 PRECONDITION_FAILED 的异常类型与自动重新声明的行为;集群与镜像队列;vhost 级别的默认队列类型(default_queue_type)对重新声明的影响;Shovel / 联邦做不停机迁移的具体步骤;x-expires、x-overflow、x-single-active-consumer 等其它参数的等价性比较;不同 RabbitMQ 版本的报错文字(这里只有 4.4.0)
    京ICP备2026067502号-1京公网安备11010502063487号