Webman集成RabbitMQ必须分离生产者与消费者角色:HTTP控制器仅负责publish(需delivery_mode=2+持久化队列/交换机),消费者须为独立CLI进程并由Supervisor守护,且必须手动ACK、启用心跳、使用连接池,否则必然丢消息或断连。

Webman 集成 RabbitMQ 不能靠“写完代码跑起来”就完事,必须拆开生产者和消费者两个角色,否则必然卡死、丢消息、连不上。
为什么 Webman 的消费者不能写在 HTTP 请求里
很多人一上来就在控制器里调用 basic_consume,然后 while ($channel->is_open()) { $channel->wait(); } —— 这等于让一个 PHP-FPM 或 Webman 的 HTTP Worker 进程长期阻塞,后果是:
- HTTP 请求超时(Nginx 默认 60s,PHP 脚本也常设 max_execution_time)
- 连接无法释放,RabbitMQ 报
CHANNEL_ERROR - expected 'channel.open'或直接断连 - 同一队列被多个请求进程同时消费,触发重复处理(比如发了两封邮件)
- 根本没法用 Supervisor 管理,进程生命周期失控
正确做法是:HTTP 入口只负责发消息(basic_publish),消费者必须是独立 CLI 命令,由 php webman start:consumer 或 php app/command/ConsumerCommand.php 启动,并交由 Supervisor 守护。
php-amqplib 生产者必须设 delivery_mode => 2
不加这个参数,消息只存在内存,RabbitMQ 重启后全丢。这不是“可能丢”,而是“一定丢”。尤其在订单、支付、审核等关键路径上,等于没走队列——只是把同步阻塞换成了不可靠异步。
立即学习“PHP免费学习笔记(深入)”;
-
delivery_mode => 1:仅适合埋点、日志等可丢失场景 -
delivery_mode => 2:必须搭配队列声明时的durable => true,即$channel->queue_declare('order_queue', false, true, false, false) - 交换机也要持久化:
$channel->exchange_declare('order_exchange', 'direct', false, true, false) - 消息体建议用
json_encode($data)+content_type => 'application/json',方便下游校验结构
漏掉任意一环,都可能导致服务重启后消息蒸发,而日志里还显示“发送成功”。
消费者进程必须手动 ACK,且不能跳过异常分支
默认 auto_ack = true 是开发模式的陷阱。上线后必须关掉,改用手动 $msg->ack() 或 $msg->nack(['requeue' => true])。否则:
- 消费者崩溃或处理中途失败,消息直接从队列消失(因为已被 auto-ack)
- 没做
try/catch包裹业务逻辑,PHP Fatal Error 会导致整个 Worker 进程退出,未处理消息永久丢失 - 重试时没控制
requeue,可能形成无限循环消费,打爆数据库
标准写法骨架:
$channel->basic_consume('queue_name', '', false, false, false, false, function ($msg) {
try {
$data = json_decode($msg->body, true);
// 执行业务逻辑
processOrder($data);
$msg->ack();
} catch (\Exception $e) {
// 记录错误日志
error_log("Consume failed: " . $e->getMessage());
// 可选:延迟重入队(需额外实现)或丢进死信队列
$msg->nack(['requeue' => false]);
}
});
连接池和心跳不是可选项,是生产环境底线
Webman 常驻内存,但 RabbitMQ 连接会因网络抖动、防火墙超时、服务端主动踢出而断开。不处理这些,消费者就变成“随机失联”。
- 用
AMQPStreamConnection时,必须传'heartbeat' => 30参数,否则默认 0(禁用心跳),多数云厂商防火墙 60s 断空闲连接 - 单连接扛不住高并发,要用连接池(如
workbunny/rabbitmq提供的RabbitMQPool),或自己基于Container::set()维护复用实例 - onWorkerStart 中建连,onWorkerStop 中显式
$channel->close()和$connection->close(),避免句柄泄漏 - Supervisor 配置里加
autorestart=true和startsecs=5,确保进程崩溃后能拉起
最容易被忽略的是:本地开发时一切正常,一上生产环境就隔几小时断一次——八成是心跳没开,或者没配 Supervisor 自动恢复。



















