PHP调用msg_send()报错11资源暂时不可用问题求解决方案
错误码11含义
CentOS 7系统下错误码11对应EAGAIN,字面意思是资源暂时不可用,结合你的场景就是System V消息队列已被写满,你调用msg_send时设置了非阻塞模式(第5个参数为false),所以队列满时直接返回该错误,后续所有写入都会失败直到队列有空闲空间。
核心触发原因
你提供的消费端代码存在致命语法错误:msg_receive的flags参数你使用了MSG_IPC_NOWAIT && MSG_NOERROR,这里的&&是PHP的逻辑与运算符,最终返回的是布尔值1(true),仅对应MSG_IPC_NOWAIT的常量值,你想要的位或运算符|被错写为逻辑与&&,导致MSG_NOERROR完全没有生效。MSG_NOERROR的作用是当消息长度超过你设置的1024字节阈值时自动截断消息并正常返回,没有这个参数的情况下,超过长度的消息会一直留在队列中不会被消费,时间一长队列就被无法消费的消息占满,就触发了发送端的错误。
1. 优先修复消费端参数错误
将消费端msg_receive的flags参数替换为位或运算符,同时补充错误日志方便排查问题:
<?php // 把 && 改成 | $update = msg_receive($q, 0, $messagetype, 1024, $message, true, MSG_IPC_NOWAIT | MSG_NOERROR, $error); // 补充错误处理 if (!$update && $error != 0) { echo 'Consume error: ' . $error . PHP_EOL; } if ($update) { // Do stuff } usleep(1000000);
如果你的JSON序列化后长度可能超过1024字节,建议同步调大msg_receive的第四个参数(最大接收长度),避免消息被截断。
2. 生产端增加重试和兜底逻辑
发送端遇到错误码11时可以增加重试机制,避免偶发队列高峰直接报错:
<?php $maxRetry = 3; $retryCount = 0; $sendSuccess = false; while ($retryCount < $maxRetry && !$sendSuccess) { $sendSuccess = msg_send($q, MESSAGE_TYPE_EXECUTION, $update, true, false, $error); if ($error === 11) { // 队列满,等待500ms后重试 usleep(500000); $retryCount++; continue; } if ($error !== 0) { // 其他错误直接跳出,记录日志即可 break; } } // 重试失败可以写入本地日志/文件兜底,避免消息丢失 if (!$sendSuccess) { file_put_contents('/var/log/msg_fail_' . date('Ymd') . '.log', $update . PHP_EOL, FILE_APPEND); }
3. 调整消息队列容量(可选)
CentOS 7默认单个System V消息队列的最大容量为16384字节,如果你的消息量较大,可以调整内核参数扩容:
- 临时生效(重启后恢复):执行
echo 1048576 > /proc/sys/kernel/msgmnb,将单队列最大容量调整为1MB - 永久生效:编辑
/etc/sysctl.conf,添加一行kernel.msgmnb = 1048576,保存后执行sysctl -p生效
4. 日常排查手段
出现异常时可以先执行ipcs -q查看当前系统所有消息队列的使用情况,确认对应队列的已用字节数、消息数是否已经达到上限,也可以在代码中调用msg_stat_queue($q)获取当前队列的实时状态做监控告警。
如果生产端消息生成速度长期大于消费端的处理速度,即便修复了参数错误,队列还是会逐渐被占满,需要根据业务情况优化消费逻辑的处理速度,或者增加多个消费进程提升消费能力。
内容的提问来源于stack exchange,提问作者MrG

