
WebSocket长连接在长周期运行后出现FD(文件描述符)句柄泄露,本质是连接未被彻底清理,导致操作系统级资源持续累积。Linux默认单进程FD上限通常为1024或65536(取决于ulimit设置),一旦耗尽,新连接将直接失败——表现为socket() failed: Too many open files或服务静默拒绝接入。
确认是否真存在FD泄漏
不要凭猜测修复。先验证:
- 实时查看某WebSocket进程的FD数量:
ls -l /proc/<pid>/fd/ | wc -l</pid> - 对比启动初期与运行24小时后的数值,若持续增长且不回落,基本可判定泄漏
- 用
lsof -p <pid> | grep socket</pid>观察是否有大量处于can't identify protocol或IPv4但无对应业务连接ID的残留句柄
核心泄漏点:close事件中资源未解绑
绝大多数FD泄漏源于onClose回调里只做了日志打印,却遗漏关键释放动作。必须确保以下三项全部执行:
-
显式关闭底层socket:Swoole中
$server->close($fd)已隐含此操作,但自研阻塞socket或使用Ratchet时,需手动调用fclose($socket)或$connection->close() -
清除关联定时器:如心跳
tick、超时检测after等,务必用Swoole\Timer::clear($timerId)或对应框架API销毁 -
解除事件监听与闭包引用:避免因闭包持有
$connection或$server实例,导致PHP GC无法回收对象(尤其在Swoole常驻进程中)
预防性加固措施
光靠onClose不够,需多层兜底:
-
设置连接空闲超时:Swoole中启用
$server->set(['heartbeat_idle_time' => 600, 'heartbeat_check_interval' => 30]),自动踢出僵死连接 -
定期扫描强制清理:在
WorkerStart中启动定时任务,遍历$server->connections,对!$server->isEstablished($fd)的句柄主动close -
限制单进程最大连接数:通过
$server->set(['max_conn' => 8192])配合系统级ulimit -n,防止雪崩式耗尽
系统级防护与监控
把防御延伸到OS层面:
- 启动服务前统一设置:
ulimit -n 65536 && ./start.sh - 用
systemd托管时,在service文件中添加LimitNOFILE=65536 - 接入Prometheus+Node Exporter,监控
process_open_fds{job="websocket"}指标,异常增长时自动告警











