worker退出前必须显式释放数据库连接和文件句柄,因frankenphp worker长期运行,不释放会导致连接池耗尽、文件锁堆积、内存缓慢增长;需在每次请求回调末尾设$pdo=null、调用$mysqli->close()和fclose()、执行db::purge()及gc_collect_cycles(),禁用静态缓存与register_shutdown_function。

Worker退出前必须显式释放数据库连接和文件句柄
FrankenPHP Worker 是长期运行的进程,传统 FPM 下由 PHP 自动回收的资源(如 PDO 连接、打开的文件)在 Worker 模式下会持续存在,直到进程终止。不主动释放会导致连接池耗尽、文件锁堆积、内存缓慢增长。
常见错误现象:PDOException: SQLSTATE[HY000] [2002] Connection refused 或 Too many open files 错误反复出现;netstat -an | grep :3306 显示 ESTABLISHED 连接数持续上升。
- 所有
PDO实例应在每次请求处理后设为null:例如$pdo = null;,不能只靠作用域结束 - 若使用 MySQLi,必须调用
$mysqli->close();仅unset($mysqli)不够 - 用
fopen()打开的文件,必须配对调用fclose();避免在循环中反复fopen()而不关 - ORM(如 Laravel 的
DBfacade)需手动触发连接释放:DB::purge('default')或DB::disconnect()
每次请求回调后必须触发垃圾回收
PHP 的 GC 不会自动在 Worker 循环中高频运行,尤其当大量临时对象(如数组、JSON 解析结果)被创建时,内存不会及时归还给系统,最终触发 OOM。
关键点在于:GC 必须在 frankenphp_handle_request() 回调执行完毕后立即调用,而不是放在循环开头或末尾。
- 在回调函数末尾加
gc_collect_cycles();—— 这是强制清理未引用对象的最有效方式 - 配合
gc_enable();开头确保 GC 已启用(虽然默认开启,但 Worker 脚本常被 CLI 启动,保险起见显式调用) - 避免依赖
__destruct()或register_shutdown_function():它们在 Worker 生命周期中不可靠,可能不执行
不能在 Worker 入口缓存用户态数据
Worker 进程复用意味着所有 static、global 或闭包外的变量都会跨请求残留。这不是“退出前释放”的问题,而是根本不能让它存在。
典型泄漏源:static $cache = [];、static $user = null;、全局单例中保存了请求相关状态。
- 把这类缓存逻辑移到请求上下文内(如传入
$request对象中) - 框架集成时,确认容器服务(如 Laravel 的 Service Container)没有绑定 request-scoped 实例到 singleton scope
- 检查日志写入器是否复用了同一个
StreamHandler实例而未刷新缓冲区 - 禁用
opcache.enable_cli=1(CLI 模式下默认关闭),防止 OPcache 缓存污染
Worker 进程退出时无法靠 register_shutdown_function 收尾
很多人试图用 register_shutdown_function() 做最后清理,但它在 FrankenPHP Worker 场景下基本无效:进程可能被信号(如 SIGTERM)直接终止,或因配置的 max_requests 达限而被主进程优雅杀掉,此时 shutdown 函数不触发。
真正可靠的退出时机只有两个:一是你主动控制的循环退出(极少用),二是每次请求回调结束后的清理点。
- 不要在
register_shutdown_function()里做连接释放、文件关闭等关键操作 - 如果真需要进程级终态逻辑(如上报统计),改用
pcntl_signal()捕获SIGTERM并配合pcntl_signal_dispatch(),但注意这会增加复杂度且非必需 - 更务实的做法是:把所有资源释放逻辑下沉到每次请求回调末尾,让“退出前”这件事消失——因为每次请求结束就是一次微型退出
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











