队列后台或数据库里看到大量 in progress,不能直接把状态改回 new。某些消息可能正在处理大文件或调用外部接口;强制重置会让第二个消费者同时执行,造成重复扣库存、重复发邮件或重复推送 ERP。

用时间线判断“慢”还是“死”

记录一条消息 ID,在一分钟内连续取两次状态,同时确认消费者 PID 是否还存在:

ps -eo pid,lstart,etime,cmd | grep '[q]ueue:consumers:start'
php bin/magento queue:consumers:list

SELECT message_id, queue_id, status, updated_at
FROM queue_message_status
WHERE status = 3
ORDER BY updated_at ASC
LIMIT 30;

不同版本或队列适配器的状态值可能不同,先在当前库抽样 new、complete、retry 的记录核对含义。不要把网上看到的数字直接用于 UPDATE。

消费者还活着:找到它正在等待什么

PID=12345
ps -p "$PID" -o pid,etime,%cpu,%mem,stat,cmd
sudo lsof -p "$PID" | tail -n 30
sudo strace -p "$PID" -tt -f -o /tmp/consumer.strace

短时间采样后停止 strace。若反复停在网络 connect/read,检查外部 API 超时;若等待 MySQL 锁,运行 SHOW PROCESSLIST 和 SHOW ENGINE INNODB STATUSG;若 CPU 持续很高,则从消费者日志和 profiler 找循环。不要在未取证前 kill -9。

进程已消失:核对 Supervisor 与消费者退出策略

supervisorctl status
journalctl -u supervisor --since '30 minutes ago'
grep -R "queue:consumers:start" -n /etc/supervisor* /etc/systemd/system 2>/dev/null

消费者使用 --max-messages 正常退出后应由 Supervisor 重新拉起。若配置了 autorestart=false,队列会停在无人处理的状态。推荐显式配置进程数、最大消息数与停止等待时间,并让进程以 Magento 文件所有者运行。

业务代码必须让失败可重试

自定义消费者中捕获所有异常然后只写日志,会让框架误以为消息处理成功;相反,无限抛出同一异常又会形成重试风暴。外部写操作应使用业务唯一键实现幂等,例如以 order_increment_id + event_type 建唯一索引。超时要抛出可重试异常,数据永久不合法则进入失败队列或告警。

SELECT message_id, topic_name, body
FROM queue_message
WHERE message_id = 123456G

消息 body 可能含客户信息或令牌,排查输出不要发到公共工单。先在隔离环境用同一 payload 调用消费者,确认异常是否稳定复现。

什么时候可以恢复卡住的消息

只有同时满足以下条件才处理状态:原消费者 PID 已不存在;外部系统确认没有成功执行;业务动作具备幂等保护;已备份对应消息与状态行。优先使用队列适配器提供的重试机制,不直接改数据库。恢复后只启动一个消费者观察首批消息,确认 complete 数增加、in progress 最老时间向前移动,再恢复正常并发。

watch -n 5 "ps -ef | grep '[q]ueue:consumers:start'"
tail -f var/log/system.log var/log/exception.log

最终验证不只是“积压数量下降”,还要抽查订单、邮件或 ERP 是否出现重复副作用,这才算真正恢复。