Ready消息只有几十条,Unacked却持续增长,重启consumer后短暂恢复。根因往往不是队列吞吐,而是consumer取到消息后调用外部ERP没有超时,prefetch额度被占满,消息既不ack也不回队列。

问题不在表面

先用一个真实失败样本复现,不要一上来全量重建。把请求、实体ID和时间记下来,后面的SQL与日志才能对得上。

几条命令足够定位

  • RabbitMQ管理页同时看messages_ready、messages_unacknowledged和consumer utilisation。
  • 把consumer PID对应的网络连接、PHP堆栈与外部接口响应时间对上。
rabbitmqctl list_queues name messages_ready messages_unacknowledged consumers
rabbitmqctl list_channels connection pid consumer_count messages_unacknowledged prefetch_count
bin/magento queue:consumers:list
timeout 5 curl -v https://erp.example.com/health

这里的重点不是把报错暂时消掉,而是让同一操作再执行一次仍保持幂等;因此修复后一定要做第二次重复执行测试。

改动落在哪里

给HTTP客户端配置连接超时和总超时,并对可重试错误做有限次数退避;不可重试的业务错误落失败表或死信队列。降低合理的prefetch,配合max-messages让进程定期退出释放内存。发布前先停止旧consumer,发布后由Supervisor逐个拉起,避免新旧代码并行消费。

实施时的顺序

  • 先做数据与配置备份,记录当前值。
  • 只改一个变量并重跑失败样本。
  • 确认新数据正确后,再处理历史受影响记录。
  • 最后恢复定时任务或缓存。

验收清单

  • Unacked能在超时窗口内回落
  • 断开ERP时消息按策略重试或进死信
  • 恢复ERP后积压被平稳消费
  • 同一业务单号保持幂等

验收不只看后台提示“成功”。我还会从前台/API再走一次真实链路,并在下一轮cron、索引或缓存命中后重复检查,确认不是短暂正常。

不要这样处理

直接purge只会丢Ready消息,既解决不了Unacked,也可能造成订单数据永久缺失。