swoole 5.x 中 onrequest 默认运行于独立协程,但全局变量、静态属性、单例等仍跨协程共享,易致数据污染;应停用静态累加、全局数组赋值、单例持请求上下文,改用 co::getcontext() 管理协程局部状态,并启用全量 hook、禁用原生 session、压测验证。

在 Swoole 5.x 的 Http\Server 中,onRequest 回调默认运行于独立协程内,但**全局变量($GLOBALS)、静态属性、单例实例、static 数组等仍跨协程共享**——这是常驻内存模型下最典型的隐性污染源。解决核心不是“避免用”,而是“明确生命周期边界”。
必须停用的三类危险状态存储
以下写法在 Swoole 5.x 中极易引发请求间数据串扰,应立即清理:
-
静态属性累加:如
class RequestLogger { public static $count = 0; public static function inc() { return ++self::$count; } }—— 第 1000 次请求时值为 1000,非每个请求从 0 开始 -
全局数组污染:如
$GLOBALS['current_user'] = $user;—— 后续任意协程都可能读到前一个请求残留的用户信息 -
单例持有请求上下文:如
UserContext::getInstance()->set('trace_id', $id),未在每次请求前重置,导致 trace_id 错乱或越权访问
推荐的协程安全上下文方案
Swoole 5.x 原生提供轻量、零开销的协程隔离机制,无需引入外部容器:
Swoole 6.1.1 是一个专为 PHP 设计的高性能事件驱动并发网络引擎。作为稳定版,它修复了编译时对 zlib 依赖的缺失及 curl 模块的内存安全风险。该版本支持协程、多线程与多进程架构,内置 TCP/HTTP/WebSocket 服务器,能够显著提升 PHP 在微服务、实时通信等场景下的执行效率与并发能力。
-
用
Co::getContext()绑定当前协程数据:在onRequest开头获取并写入关键上下文,例如:$ctx = Co::getContext();<br>$ctx->set('trace_id', $req->header['x-trace-id'] ?? uniqid('req_'));<br>$ctx->set('user_id', $auth->userId); -
在中间件或业务逻辑中统一取值:任何深度调用均可通过
Co::getContext()->get('user_id')安全读取,无需参数透传 -
不依赖
Co::getUid()存业务数据:该 ID 仅作协程标识,set/get必须走Context实例,否则无法保证类型安全与生命周期一致
框架集成时的关键检查点
若使用 ThinkPHP 8/Laravel/hyperf 等框架,需确认以下配置已生效:
-
协程 Hook 已全量启用:启动脚本中必须有
Swoole\Runtime::enableCoroutine(SWOOLE_HOOK_ALL);,否则 PDO/Redis/curl 等仍会阻塞协程 - 禁用 PHP 原生 session 文件驱动:Swoole 下 session_start() 不再安全,应切换至 Redis 或 Swoole\Table 存储
-
重写单例的 reset 方法:对必须复用的单例类(如数据库连接池管理器),增加
resetForRequest()并在onRequest入口显式调用
上线前必做的污染验证
仅靠代码审查不够,需用并发压测暴露问题:
- 用 ab 或 wrk 发起 100 并发、1000 总请求数,观察日志中
trace_id是否重复、user_id是否错乱 - 在
onRequest结尾添加检测:if (Co::getContext()->get('user_id') !== null) { error_log("WARN: context not cleaned"); } - 启用
swoole_get_local_statistics()监控协程数是否持续增长,排查 Context 对象泄漏










