frankenphp worker模式下必须手动清理static和全局变量,因其跨请求持续存在易致污染;需显式释放数据库连接、清空orm缓存、重置服务容器实例,并避免preload阶段误设$globals或ob_start。

worker 模式下必须手动清理 static 和全局变量
FrankenPHP 在 worker 模式中不会自动重置 static 属性或 $GLOBALS,这些值会跨请求持续存在。比如 static $counter = 0; 在第 100 个请求时可能已是 99,而不是你期望的 0。
常见误操作是把用户身份、临时配置、缓存键前缀等塞进 static 或 $GLOBALS —— 这在 FPM 下没事,在 worker 下就是污染源。
- 检查所有
static $xxx声明,确认是否真需跨请求保留;否则改用函数参数、依赖注入或php.Context传参 - 避免在入口文件或 preload 阶段写
$GLOBALS['config'] = loadConfig();,应改为每次请求内按需加载 - 用
var_dump(array_keys($GLOBALS));在请求末尾快速扫一眼有没有意外挂载的变量
数据库连接和 ORM 实体管理器要显式释放
Worker 模式下 PDO 连接对象不销毁,EntityManager 的一级缓存也不清空,久而久之内存暴涨、事务状态错乱、脏读频发。
这不是“连接池没配好”的问题,而是代码没主动切断引用。
- Laravel:在请求结束前调用
DB::disconnect()(非持久连接)或$db->reconnect()(持久连接异常后) - Symfony + Doctrine:每个事务块结尾加
$entityManager->clear();,尤其在批量操作后 - 禁用
PDO::ATTR_PERSISTENT除非你有完整连接池逻辑;否则直接设为false更可控 - 检查
netstat -an | grep :3306,若连接数随请求数线性上涨,说明连接未释放
输出缓冲和事件监听器不能靠“自动结束”
ob_start() 本身不会跨请求残留,但如果你在 preload 阶段误调用、或在回调里漏掉 ob_end_flush(),会导致后续请求响应头错乱、内容重复、甚至 fatal error。
同样,闭包监听器(function () use ($bigObject) { })会强持有大对象,removeListener() 后仍不释放。
- 所有
ob_start()必须在请求处理逻辑内调用,且严格配对ob_end_flush()或ob_end_clean() - 检查
ob_get_level()—— 请求入口处应为 0;若 > 0,说明上一个请求没清理干净 - 注册监听器优先用
[$this, 'method']或static function,避免闭包捕获容器或实体 - 在
kernel.terminate中调用gc_collect_cycles(),并清空已知大数组(如临时结果集)
框架级服务容器需重置已解析实例
像 Laravel 的 $app->make('request') 或 Symfony 的 $container->get('http_kernel'),一旦解析就会被容器缓存。worker 模式下这个缓存不会自动失效,导致后续请求拿到旧 Request 对象或错误路由上下文。
这不是 bug,是设计使然 —— 容器默认单例,你得告诉它“这次我要新实例”。
- Laravel:在每次请求后调用
$app->resetResolvedInstances();和$app->forgetInstance('request'); - Symfony:禁用
debug-pack和profiler-pack,它们会在 worker 生命周期内不断膨胀监听器和日志处理器 - ThinkPHP:调用
think\Container::clear();清空服务缓存 - 别依赖
session_reset()或curl_reset()来“重置状态”——它们只作用于特定资源,对容器无影响
真正容易被忽略的是:opcache 和 APCu 缓存也跨请求生效。如果你业务里写了 apcu_fetch('config'),就得自己管它的更新时机 —— FrankenPHP 不负责判断这个值该不该过期。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











