应监控 RSS 而非 memory_get_usage(),因后者无法反映协程栈、C 层 malloc 及扩展内存;RSS 持续上升表明内存泄漏,需结合 gc_collect_cycles()、swoole_tracker(启用 malloc hook)及软性熔断(如 max_requests)定位与防控。

看 RSS 而不是 memory_get_usage()
PHP 进程常驻后,memory_get_usage(true) 只反映 Zend 内存管理器分配的堆内存,但 Swoole 的协程栈、底层 malloc 分配、第三方扩展(如 Redis、PDO)持有的 C 层内存,它完全看不见。你看到“用了 2MB”,实际 RSS 已涨到 80MB——这差额就是泄漏高发区。
- 用
ps aux --sort=-rss | head -n 5或top -p $(pgrep -f "swoole")每 30 秒观察 RSS 值是否持续爬升 - 在每次请求/任务结束时加两行:
gc_collect_cycles(); gc_mem_caches();,再等 5 秒看 RSS 是否回落 - 如果 GC 后 RSS 不降,说明有强引用没断——比如闭包捕获了大对象、全局静态变量存了 Request 实例、或 EventLoop 中注册了未注销的回调
用 swoole_tracker 开启 malloc hook 抓泄漏点
它能记录每一次 malloc/free 调用的调用栈,是定位 C 层泄漏最直接的手段,尤其适合排查 Redis 扩展、Swoole 自身或自定义扩展的泄漏。
- 必须关闭
tracker.enable=0,开启tracker.enable_malloc_hook=1和tracker.enable_memcheck=1(二者不能共存) - 不要设
tracker.sampling_rate=100上线环境——只在复现阶段开,否则性能暴跌;调试时建议先设为10 - 泄漏报告默认输出到
/tmp/swoole_tracker.log,里面会有类似[leak] addr=0x7f8b4c0a1234 size=16384 at ext/redis/redis.c:1234的行,直接定位到 C 文件行号
别信“重启就没事”,要设软性熔断
长连接服务扛不住无限增长的内存,硬扛只会导致 OOM Kill。与其等系统杀进程,不如自己控制节奏。
- 在 Worker 启动时记录初始 RSS:
$startRss = (int)shell_exec("ps -o rss= -p " . getmypid()); - 每个请求后检查:
if ((int)shell_exec("ps -o rss= -p " . getmypid()) - $startRss > 50 * 1024 * 1024) { exit(0); }(50MB 上限) - Laravel Octane / Webman / laravels 都支持
--max-requests=1000或max_request=1000配置,这是最稳妥的兜底方式——但注意:它不解决泄漏,只防雪崩
常见泄漏陷阱:Laravel/Symfony 容器和中间件
框架本身不是问题,但开发者容易在协程上下文里误用单例、静态属性或事件监听器。
- 在 Swoole 中,
app('request')返回的是同一个实例,多次请求会不断往里面塞新数据(如上传文件临时路径),且不会自动清理 - 用
App::forgetInstance('request')或改用request()->instance(new Request(...))显式重建 - 中间件中注册了
Event::listen('xxx', function () { ... })?每次请求都注册一次,监听器数组越积越多——必须确保只注册一次,或用once替代listen - Hyperf 用户注意:
HttpServerMiddleware在旧版配置中会导致重复初始化,引发容器泄漏,建议直接移除或升级到 v3.0+ 并用@Aspect替代
真正难查的泄漏往往藏在 C 扩展的 malloc 分配里,memory_get_usage() 看不见,gc_*() 清不掉,连 debug_backtrace() 都打不出来——这时候 swoole_tracker 不是可选项,是唯一能给你指路的工具。










