Nginx通过PHP向Unix域套接字传数据不完整致504超时排查
看起来你这问题是典型的大数据传输下的超时、缓冲区限制加代码逻辑的三重坑,我帮你一步步拆解排查:
1. 先搞定Nginx和PHP-FPM的超时配置
Nginx报的upstream timed out是核心信号——说明PHP-FPM进程处理请求的时间已经超过了Nginx的等待阈值,直接触发了504。
Nginx端调整:
打开你的Nginx配置(对应server或location块),增加/调整这些参数:# 把Nginx等待PHP-FPM响应的时间拉长,比如设为60秒,按需调整 fastcgi_read_timeout 60s; # 针对大请求,调整缓冲区大小,避免因缓冲区不足导致阻塞 fastcgi_buffers 8 16k; fastcgi_buffer_size 32k;因为你的上游是
fastcgi://unix:/run/php/php7.0-fpm.sock,所以重点调fastcgi_*开头的参数就行。PHP-FPM和PHP.ini调整:
打开PHP-FPM的池配置(比如/etc/php/7.0/fpm/pool.d/www.conf):# 单个PHP-FPM进程的最大执行时间,默认30秒,改大到和Nginx匹配 request_terminate_timeout = 60s再打开
php.ini:# PHP脚本本身的最大执行时间,同样改成60秒 max_execution_time = 60这俩参数不匹配的话,很容易一方先触发超时,导致请求中断。
2. Unix域套接字的缓冲区限制
UDS默认的发送/接收缓冲区大小确实有限制,当数据量超过默认值时,会导致阻塞甚至截断。
- PHP代码里直接调整socket缓冲区:
在你创建socket连接之后(或者调用send之前),加几行代码:
如果需要更大的缓冲区,得先调整系统参数(临时生效,重启后失效):// 设置发送缓冲区为2MB,按需调整 socket_set_option($this->socket, SOL_SOCKET, SO_SNDBUF, 2 * 1024 * 1024); // 设置接收缓冲区为2MB socket_set_option($this->socket, SOL_SOCKET, SO_RCVBUF, 2 * 1024 * 1024);
要永久生效的话,把这两行写入sysctl -w net.core.wmem_max=4194304 sysctl -w net.core.rmem_max=4194304/etc/sysctl.conf,再执行sysctl -p。
3. 你的PHP代码里藏着致命的循环问题
我看了你的send方法,这里有个很容易踩的坑:socket_read的循环逻辑错误。
PHP的socket_read用PHP_BINARY_READ模式时,当没有更多数据可读(但连接没关闭),会返回空字符串,而不是FALSE。你原来的循环条件while (($chunk = socket_read(...)) !== FALSE)会导致脚本一直空循环,直到触发超时——这大概率就是你遇到1.5MB就截断、Nginx返回504的原因!
修改后的循环逻辑应该是这样:
$response = ""; while (true) { $chunk = socket_read($this->socket, 2048, PHP_BINARY_READ); // 连接异常关闭,直接跳出 if ($chunk === FALSE) { break; } // 没有更多数据可读,跳出循环 if ($chunk === '') { break; } $response .= $chunk; // 检查是否收到结束符chr(27),收到就截断并跳出 if (substr($response, -1) == chr(27)) { $response = substr($response, 0, -1); // 去掉结束符 break; } }
另外还要注意:如果你的后端应用返回的结束符不在最后一个chunk的末尾,原来的substr($chunk, -1)判断会漏掉,所以改成检查整个$response的末尾更可靠。
4. 加日志定位问题
建议你在socket_read之后加几行日志,方便排查:
error_log("本次读取chunk大小: " . strlen($chunk)); error_log("当前总响应大小: " . strlen($response));
这样能清楚看到是数据真的被截断了,还是脚本卡在循环里超时了。
内容的提问来源于stack exchange,提问作者Sceptical Jule

