一台 4 核 8G 的机器上跑 PHP-FPM,ss -tan 里 CLOSE_WAIT 状态两万多条,句柄数很快顶到 ulimit,PHP 报 too many open files,重启 FPM 后半小时又涨回原样。

一开始怀疑是「curl 句柄用完没释放,长轮询场景下越攒越多」,查了一圈,方向不对。

排查方向其实有限,先把可能性排一排。PHP 代码里新建的 Redis 连接在异常分支没走到 close,对象回收前句柄一直挂着。少数情况下是上游把连接断了,应用侧没读到 EOF,连接停在半关闭状态不进入关闭流程。

少数情况下是连接池没有最大空闲数和回收时间,闲置连接一直不回收。

上来先看这些连接属于哪个进程

ss -tanp state close-wait | awk '{print $1,$5,$6}' | head -20

接着统计每个远端地址占了多少条

ss -tan state close-wait | awk '{print $5}' | cut -d: -f1 | sort | uniq -c | sort -rn | head

再往下看进程当前的句柄总量

ls /proc/$(pidof php-fpm | awk '{print $1}')/fd | wc -l

缩短空闲连接超时,让半关闭连接尽快被回收

redis-cli config set timeout 30 && sleep 10 && ss -tan state close-wait | wc -l

恢复的判定其实很直接。close-wait 状态降到一千以下,进程句柄数随请求量上下波动而不再单调上涨,错误日志里不再出现 too many open files。

CLOSE_WAIT 是本地没关,TIME_WAIT 是本地先关,方向相反;调内核参数对它没用,得改代码里的关闭逻辑。改动前把原配置备份一份,出问题能马上回滚,这比任何技巧都实用。

作者 9823189