必须监控rss而非memory_get_usage(),因后者仅反映zend堆内存,无法捕获协程栈、c层malloc(如redis扩展缓冲区)及swoole共享内存段;rss持续上升表明泄漏,需结合gc_collect_cycles()、swoole_tracker(启用malloc hook)及max_requests熔断定位防控。

内存溢出不是“突然爆了”,而是 RSS 持续爬升后触达系统限制或 PHP 的 memory_limit——排查必须从 RSS 开始,而不是 memory_get_usage()。
为什么 memory_get_usage() 会误导你
这个函数只统计 Zend 堆内存,完全看不到协程栈、C 层 malloc(比如 Redis 扩展分配的缓冲区)、Swoole 自身的共享内存段。你看到“用了 15MB”,ps aux --sort=-rss 可能显示进程 RSS 已达 420MB。差额就是泄漏高发区。
实操建议:
Swoole 6.1.1 是一个专为 PHP 设计的高性能事件驱动并发网络引擎。作为稳定版,它修复了编译时对 zlib 依赖的缺失及 curl 模块的内存安全风险。该版本支持协程、多线程与多进程架构,内置 TCP/HTTP/WebSocket 服务器,能够显著提升 PHP 在微服务、实时通信等场景下的执行效率与并发能力。
- 每 30 秒执行一次
ps -o rss= -p $(pgrep -f "swoole") | head -1,观察是否单调上升 - 在请求结束处加两行:
gc_collect_cycles(); gc_mem_caches();,再等 5 秒看 RSS 是否回落 - 如果 GC 后 RSS 不降,说明存在强引用未断:闭包捕获了
$this、静态变量存了 Request 实例、EventLoop 里注册了未注销的回调
用 swoole_tracker 定位 C 层泄漏点
这是唯一能精准定位到 ext/redis/redis.c:1234 这类位置的手段,尤其适合排查 Redis、PDO、Swoole 自身的 malloc 泄漏。
实操建议:
- 调试阶段启用:
tracker.enable_malloc_hook=1+tracker.enable_memcheck=1(二者不能同时开) -
tracker.sampling_rate=10足够复现问题,线上禁用sampling_rate=100,否则性能暴跌 - 泄漏日志默认输出到
/tmp/swoole_tracker.log,搜索[leak]行,直接看到地址、大小、调用栈 - 确认泄漏后,立刻检查对应扩展版本是否已知有 bug(如某些 Redis 扩展 5.3.7 之前存在连接池对象未析构)
max_request 是防雪崩的兜底,不是治本方案
设 max_request=1000 只是让 Worker 进程在处理完 1000 个请求后自动重启,获得干净内存空间。它不解决泄漏,但能防止 OOM Kill 导致服务中断。
实操建议:
- 上线前先压测:单 Worker 处理 500 请求后,用
ps看 RSS 增长量;若每 100 请求涨超 10MB,max_request应设为 300–500 - Laravel Octane / Webman / laravels 都支持
--max-requests=1000,Swoole 原生配置是'max_request' => 1000 - 别依赖它掩盖问题:每次重启后 RSS 应回落到初始值附近,否则说明泄漏发生在进程级(如全局
static $cache = []) - 配合软性熔断逻辑:在关键请求末尾加判断
if ((int)shell_exec("ps -o rss= -p " . getmypid()) > 50 * 1024 * 1024) { exit(0); }
高频泄漏陷阱:静态变量、框架 Request 实例、未 close 的资源
这些不是“写法错误”,而是在 Swoole 常驻模型下语义失效的惯性代码。
实操建议:
- 禁用所有 Controller/Service 中的
static $cache = [];改用Swoole\Table并设置 TTL,或用Redis::pool()+ 显式 key 过期 - TP8/Laravel 中避免直接调用
app('request');改用request()->instance(new Request())或App::forgetInstance('request') - 每个
new Redis()或new Swoole\Coroutine\MySQL()后,必须配对$client->close();闭包中持有客户端时,用defer确保释放 -
Swoole\Table写入前检查容量:if ($table->count() > $table->size * 0.8) { // 清理过期条目 }
最常被忽略的一点:泄漏往往不在业务主流程,而在异常分支、日志记录、中间件退出逻辑里——那些你以为“不会走到”的代码,可能因为某次 Redis 超时而高频执行。检查 every catch 和 finally 块里的资源清理动作。










