全局变量在swoole中必然跨请求残留并污染,因worker进程常驻内存,$globals、global变量和static属性生命周期绑定进程而非请求,协程切换时不销毁,下个请求直接复用;如$_server['request_time_float']存旧时间戳、self::$usercache持续累加致内存溢出、tp容器未重置导致app('request')返回已销毁对象。

全局变量在 Swoole 里不会自动清空,不手动重置就必然污染后续请求。
为什么 $GLOBALS 或 global $x 在 onRequest 里会跨请求残留
Worker 进程常驻内存,$GLOBALS、global $x、static 属性这些 PHP 全局状态不会随请求结束销毁。协程切换时它们仍挂在进程堆上,下个请求进来直接复用——不是“可能污染”,是“一定污染”。
- 典型现象:
$_SERVER['REQUEST_TIME_FLOAT']里存了上个请求的时间戳,本请求读出来比当前时间还早 - 更隐蔽的是
$_SESSION或自定义的$_CONTEXT数组,被前序请求写入用户 ID,后序请求没校验就直接用了 - PHP-FPM 下安全的写法(如
static $cache = [])在 Swoole 里等于埋雷
类静态属性 self::$data 累加不重置的实操后果
静态属性生命周期绑定 Worker 进程,不是请求。一旦写入,除非显式清空,否则越积越多,最终触发内存溢出或返回错乱数据。
Swoole 6.1.1 是一个专为 PHP 设计的高性能事件驱动并发网络引擎。作为稳定版,它修复了编译时对 zlib 依赖的缺失及 curl 模块的内存安全风险。该版本支持协程、多线程与多进程架构,内置 TCP/HTTP/WebSocket 服务器,能够显著提升 PHP 在微服务、实时通信等场景下的执行效率与并发能力。
- 错误写法:
self::$userCache[$id] = Db::find($id),第 1000 次请求时self::$userCache已有上千条,且混着不同租户的数据 - 调试线索:用
count(self::$userCache)打日志,会发现数值持续上涨,而非每次为 0 - 不能依赖
unset(self::$userCache),因为下次请求又会初始化为空数组——但 static 变量声明本身不会重执行,所以实际仍是原引用
ThinkPHP 容器实例和 Request 对象如何被污染
TP 默认把 Request、Response 实例挂进 Container 和 static::$instance,Swoole 复用进程时这些对象不会重建,导致 header、query、post 数据串请求。
- 最常见报错:
Call to a member function param() on null,本质是app('request')返回了上个请求残留的已销毁对象 -
$container->forget()只删 binding 定义,不释放已实例化的对象;必须配合$container->setInstances([])才真正清理 - 别清空
app绑定,否则app('request')会彻底失效;核心绑定建议只保留['app', 'think\App', 'think\Env']
协程内用 $sharedData = [] 而不是 global 的替代方案
协程本地存储(Co::getContext)才是安全的上下文载体,它按协程 ID 隔离,天然避免共享冲突。
- 正确姿势:
$ctx = Co::getContext(); $ctx['user_id'] = 123;,该值只对当前协程可见 - 不要用
Co::set(['hook_flags' => ...])后不恢复,它会影响整个 Worker 进程的 IO 行为,不是协程级 - Table 是进程级共享存储,适合缓存,不适合请求上下文;想存临时数据,优先走
Co::getContext()
真正难的不是知道要清,而是清哪些、什么时候清、清完会不会断链路。比如重置容器后忘了重新 register 自定义 Provider,或者清了 $_SERVER 却没补回 REQUEST_URI,接口就直接 500。这些点不跑线上压测根本暴露不出来。










