workerman 4 http 服务中 onmessage 回调滥用全局/静态变量会导致多连接数据污染,应改用 $connection 属性存储请求上下文、局部变量处理中间结果,并避免复用有状态的单例实例。

Workerman 4 的 HTTP 服务中,onMessage 回调里滥用全局变量或静态变量,是导致多连接间数据污染的最常见原因。根本问题不在于 Workerman 本身,而在于 PHP 的常驻进程模型下,变量生命周期被拉长——一次请求写入的全局状态,可能被下一次请求(甚至另一个客户端)读取到。
避免全局变量和静态变量跨请求残留
PHP-FPM 中每个请求都是独立生命周期,“全局变量”实际只活一次;但 Workerman 进程长期运行,global $user_id 或 static $cache = [] 一旦赋值,就会持续存在,被后续任意连接复用。
- 绝对不要在
onMessage中修改全局变量(如$GLOBALS['last_token'])、类静态属性(如User::$current)或函数内 static 变量来保存单次请求上下文 - 若必须缓存,使用连接对象自身属性:例如
$connection->user_id = 123;或$connection->session_data = [...];—— 每个连接独有一份,天然隔离 - 临时计算中间结果,统一用局部变量,作用域严格限制在当前回调函数内
用连接对象承载请求上下文
Workerman 的 $connection 实例在一次连接生命周期内稳定存在,是天然的上下文容器。所有与当前请求强相关的状态,都应绑定到它上面。
- 登录后把用户信息存到连接:
$connection->uid = $uid; $connection->role = 'admin'; - 解析后的参数、权限标识、请求 ID 等,全设为
$connection->xxx属性,后续业务逻辑直接读取 - 注意:连接断开时这些属性自动销毁,无需手动清理;但若需提前释放大对象,可显式
unset($connection->big_data);
禁止在 onMessage 中复用单例或共享实例
某些 SDK(如旧版 Redis 客户端、自定义 DB 单例)若内部维护了未重置的状态(如 lastInsertId、errorInfo、查询缓存),在并发请求下会互相覆盖。
- 检查第三方组件是否线程/协程安全;优先选用 workerman/mysql、workerman/redis 等原生协程适配版本
- 避免在
onMessage中 new 一个单例并反复调用其方法;改为每次请求 new 新实例,或确保该实例无内部状态 - 如确需复用连接池,确保池内每个连接取出后都做初始化(如
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);)
用 Closure 绑定连接上下文(进阶隔离)
对复杂业务链路(如中间件嵌套、异步回调嵌套),可将连接对象作为闭包的绑定对象,确保所有子调用都能安全访问专属上下文。
- 示例:
$handler = function () use ($connection) { return $this->doSomething($connection); }->bindTo(new class($connection) { public function __construct(public $conn) {} }); - 更实用的是封装一个轻量 Context 类,构造时传入
$connection,所有业务方法通过$this->conn访问,杜绝意外使用全局变量











