frankenphp启用worker模式后需手动清理static变量、全局变量、数据库连接等,因其跨请求持续存在易致污染;必须通过frankenphp_handle_request包裹逻辑,并在每次请求后显式重置状态、释放资源、触发gc。

用 curl + 状态标记验证跨请求污染
FrankenPHP 启用 worker 模式后,PHP 进程常驻,全局变量、静态属性、服务容器实例都会跨请求保留。这不是 bug,而是特性——但若你没清理好状态,就会出现 A 请求写入的值被 B 请求读到。最直接的验证方式是构造一个带可观察状态的脚本,用连续 curl 触发。
在 public/test-state.php 中写:
<?php static $counter = 0; $counter++; echo "counter: $counter, pid: " . getmypid() . "\n"; ?>
然后执行:
-
curl http://localhost:8000/test-state.php→ 输出counter: 1, pid: 12345 - 立刻再跑一次:
curl http://localhost:8000/test-state.php→ 若输出counter: 2, pid: 12345,说明状态复用成功;若仍是counter: 1,说明没进 worker 模式或入口未调用frankenphp_handle_request()
注意:getmypid() 返回的是 FrankenPHP 主进程 PID(不是子 worker),它不变才对;如果每次 curl 都返回不同 PID,说明根本没常驻,还在 CLI 模式下跑。
检查 Laravel/Symfony 单例 ID 是否稳定
框架级单例是否真正复用,比静态变量更关键。很多问题出在日志实例、DB 连接、缓存客户端被意外复用却未重置。
在 Laravel 中,加一个测试路由:
Route::get('/debug/container', function () {
return response()->json([
'app_id' => spl_object_id(app()),
'log_id' => spl_object_id(app('log')),
'db_id' => spl_object_id(app('db')),
'booted' => app()->isBooted(),
]);
});
用 for i in {1..10}; do curl -s http://localhost:8000/debug/container | jq '.app_id'; done 查看输出。所有请求返回的 app_id 必须完全一致——不一致说明框架被重复初始化,worker 模式未生效;一致但 log_id 或 db_id 变动,则说明对应服务提供者里写了 new 实例或未正确绑定单例。
模拟并发请求看连接池是否泄漏
数据库/Redis 连接池若没在请求结束时释放,会在后续请求中复用旧连接,导致事务残留、SSL 上下文错乱、甚至认证失败。
写一个最小测试脚本 test-pool.php:
<?php $db = new PDO('sqlite:/tmp/test.db');
$stmt = $db->prepare("SELECT random()");
$stmt->execute();
echo "random: " . $stmt->fetchColumn() . "\n";
?>
启动后,用 wrk -t2 -c10 -d5s http://localhost:8000/test-pool.php 压测。同时开另一个终端运行:
-
lsof -i -P -n | grep :8000 | wc -l—— 查看 ESTABLISHED 连接数是否稳定(FrankenPHP 下应始终为 1~2 条) -
ss -tulnp | grep frankenphp—— 看是否有大量 TIME_WAIT 或未关闭的 socket
如果连接数随并发线程线性增长,或压测结束后仍残留大量连接,说明 PDO/Redis 客户端未走连接池复用逻辑,或框架未在请求结束时调用 close()。
$_SERVER 和超全局变量是否被污染
很多人忽略:worker 模式下 $_GET、$_POST、$_COOKIE 不会自动清空。若你在某次请求里改了 $_SERVER['HTTP_X_REAL_IP'],下个请求可能还带着它。
快速验证方法:在入口或中间件加一段调试代码:
var_dump([
'REQUEST_URI' => $_SERVER['REQUEST_URI'] ?? null,
'HTTP_USER_AGENT' => $_SERVER['HTTP_USER_AGENT'] ?? null,
'_GET' => $_GET,
'_POST' => $_POST,
]);
然后用两个不同参数的 curl 分别请求:
curl "http://localhost:8000/test.php?a=1"curl "http://localhost:8000/test.php?b=2"
如果第二个请求的输出里还出现 a=1,说明 $_GET 没被重置——这通常是因为你绕过了框架的请求生命周期,直接用了 $_GET 而没走 Request 对象。Laravel 的 request()->query() 是安全的,但裸用 $_GET 就不行。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











