不能直接用php-amqplib的blocking模式,因为Webman是常驻内存的异步框架,blocking模式(如basic_get轮询)会阻塞整个事件循环,导致HTTP请求、WebSocket连接、定时器全部停摆;必须使用basic_consume推模式配合回调,且回调内不可有同步阻塞操作。

为什么不能直接用 php-amqplib 的 blocking 模式
Webman 是常驻内存的异步框架,所有 Worker 进程共享事件循环;若在 onWorkerStart 里用 pika.BlockingConnection 或 AMQPStreamConnection 配合 basic_get() 轮询,会阻塞整个事件循环——一个消费者卡住,所有 HTTP 请求、WebSocket 连接、定时器全停摆。
必须使用 AMQP 的 basic_consume() + 回调模式,并确保回调内不出现同步阻塞操作(如未设超时的 file_get_contents、sleep()、直连 PDO)。
- blocking 模式只适合 CLI 脚本,不适合 Webman 的 Worker 生命周期
-
basic_get()是拉模式,无法触发事件循环,且不支持手动 ACK - 真正可用的是
basic_consume()推模式,配合 Workerman 的Event::add()底层驱动(workbunny/rabbitmq已封装好)
workbunny/rabbitmq 和原生 php-amqplib 封装怎么选
多数业务场景直接用 workbunny/rabbitmq:它把连接池、通道复用、心跳保活、异常重建、优雅关闭都封装进插件生命周期里,RabbitMQ::producer()->publish() 和 RabbitMQ::consumer()->start() 即可开跑。
只有当你需要精细控制以下行为时,才值得自己封装 php-amqplib:
- 自定义重试策略(比如按错误类型决定是否
nack(requeue=false)进入死信) - 动态调整
basic_qos预取值(例如根据 CPU 使用率实时降为 1) - 在
onError中区分AMQPConnectionClosedException和AMQPChannelClosedException做不同恢复动作 - 要求每条消息处理完后主动调用
$channel->wait()同步等待 ACK 确认(极少数强一致性场景)
消费者进程启动后消息不消费,常见排查点
不是配置写错,而是运行时状态没对齐。先确认三件事:
Webman 2.2.0版本强化了 TCP/UDP 服务支持,优化路由组管理,并增强异步任务处理能力。结合协程与连接池技术,Webman 能轻松应对高并发场景,适用于网站、接口服务、即时通讯、物联网及游戏开发,兼具高性能、灵活扩展与稳定可靠,是多场景 PHP 服务开发的理想选择。
- 执行
ps aux | grep consumer,看进程 TIME 是否在增长;如果恒为00:00:00,说明根本没进入消费循环 - 检查
config/rabbitmq.php中'vhost'是否带前导/(正确是'vhost' => '/',不是'vhost' => '') - 登录 RabbitMQ 管理界面
http://localhost:15672,看对应队列的Unacked数是否为 0 —— 如果一直是 0,说明basic_consume()根本没注册成功或被 silent fail 了 - 在消费者
handle()方法开头加file_put_contents('/tmp/handle.log', "hit\n", FILE_APPEND),确认回调是否被触发
最常被忽略的是:消费者类没继承 workbunny\rabbitmq\Consumer,或 handle() 方法签名漏了 $channel 参数,导致后续无法 ack(),RabbitMQ 自动断连。
ACK 失败后消息卡死,怎么避免“假死”积压
一旦某条消息处理失败又没 nack(),它会一直留在 Unacked 状态,RabbitMQ 默认不会重发,也不会超时释放——看起来像“卡死”,其实是协议设计如此。
必须做两件事:
- 在消费者
handle()内部包try/catch,确保无论成功失败都调用$channel->ack($msg->getDeliveryTag())或$channel->nack(...) - 在
onWorkerStart初始化连接后,立刻调用$channel->basic_qos(0, 1, false)(预取数=1),防止一条卡住,整批消息全堵在内存里 - 别依赖 RabbitMQ 的
delivery_mode=2就以为万无一失——队列本身也得声明为durable=true,交换机和绑定关系也要持久化,否则服务重启后队列消失,消息直接丢弃
真实线上环境,哪怕只有一条消息因 PHP Fatal Error 没走到 ACK,就可能让整个消费者线程僵住十几分钟——因为心跳默认 580 秒,期间新消息进不来,旧消息出不去。所以预取限流 + 强制 ACK/NACK + 日志兜底,三者缺一不可。

















