协程中static变量和$globals会导致内存泄漏;onclose未unset连接上下文、未归还连接池、发起新协程均加剧泄漏;swoole\table需手动加锁防竞态;max_request仅process模式生效,不能替代主动清理。

协程里用 static 变量或 $GLOBALS 就等于埋内存泄漏雷——Worker 进程不重启,这些值就永远留着,且不会随请求结束自动清理。
onClose 不 unset 连接上下文,内存只增不减
很多人在 onClose 回调里只写 echo "closed",但 Swoole 的 $server->connections[$fd] 里可能存着用户 Session、Co\Redis 实例、临时 Co\Channel 或大数组缓存。只要没显式 unset(),它们就一直占着内存,且不会被 GC。
- 必须在
onClose中执行unset($server->connections[$fd]) - 若用了 Redis 连接池,要先调
$pool->put($redis)归还连接,再销毁变量 - 禁止在
onClose里发起新协程(如go(function () { ... }))或远程调用——此时连接已断,容易触发超时堆积 - 如果用了
Swoole\Table存连接元数据,记得同步$table->del($fd)
全局变量和 static 在 onRequest 中持续累加
PHP-FPM 下每次请求都重建脚本环境,static $counter = 0 每次都是新值;Swoole 的 onRequest 却运行在常驻 Worker 内,static 和 global 变量跨请求保留,极易导致状态污染。
Swoole 6.1.1 是一个专为 PHP 设计的高性能事件驱动并发网络引擎。作为稳定版,它修复了编译时对 zlib 依赖的缺失及 curl 模块的内存安全风险。该版本支持协程、多线程与多进程架构,内置 TCP/HTTP/WebSocket 服务器,能够显著提升 PHP 在微服务、实时通信等场景下的执行效率与并发能力。
-
static $req_id = 0; $req_id++在第 1000 次请求时就是 1000,不是 1 - 解决办法不是“不用”,而是明确用途:用
Swoole\Table存共享状态,用局部变量存单次请求数据 - 若真需进程级计数器,改用
Swoole\Atomic,它底层是 CPU 原子指令,无锁且安全 - 绝对不要把
$_SESSION或$_COOKIE直接赋给static变量——它们是超全局,生命周期与 Worker 绑定
Swoole\Table set/get 默认不加锁,多 Worker 写同一行会丢数据
Swoole\Table 是共享内存结构,但它的 set() 和 get() 方法本身不带任何并发控制。两个 Worker 同时对同一 $key 执行 set(),后写入者必然覆盖前写入者,出现“丢失更新”。
- 字符串、数组等非数值类型字段,必须手动调
$table->lock($key)+unlock()包裹读写逻辑 - 只有
TYPE_INT字段支持incr()原子操作,其他类型(TYPE_STRING、TYPE_FLOAT)不能靠incr解决竞态 -
Swoole\Atomic更快,但只支持 int64 单值,无法存多字段组合(比如同时更新last_time和status) - 别把
Table当消息队列用:它没持久化、没 ACK、不保证顺序,高并发下丢数据不报错
max_request 不是万能解药,Base 模式下它不生效
'max_request' => 3000 确实能让 Worker 处理完指定请求数后退出并由 Manager 重启,从而释放所有未清理的内存。但它有个关键限制:只在 SWOOLE_PROCESS 模式下有效,在 SWOOLE_BASE 模式下完全被忽略。
- 确认当前模式:
var_dump($server::MODE_PROCESS === SWOOLE_PROCESS) - 线上建议统一用
SWOOLE_PROCESS,避免因模式误配导致max_request形同虚设 -
max_request是兜底手段,不能替代主动清理——它治标不治本,重启有毫秒级抖动,且掩盖了真实泄漏点 - 配合
swoole_memory_dump()定期采样,比单纯依赖max_request更早发现增长对象
真正难的不是知道该清理,而是判断“哪些东西该在哪儿清”。比如一个 Co\Redis 实例,是在 onClose 里 unset,还是归还到池子里?是在 onWorkerStop 里 close(),还是根本不需要管?这些边界一旦模糊,问题就会在压测或上线后才爆发。










