先改代码而非盲目调小线程数,因frankenphp内存压力主因是worker中单请求资源未释放(如未unset大数组、pdo未close、日志句柄泄漏、opcache配置不当),线程数仅放大泄漏而非根源;减线程治标不治本,会降低qps且掩盖真实泄漏。

先改代码,而不是盲目调小线程数。 FrankenPHP 的内存压力往往不是线程太多导致的,而是单个请求在常驻 worker 中持续累积、没释放——比如未 unset 大数组、PDO 连接没 close、日志句柄一直开着、或 OPcache 配置不当。线程数只是放大器,不是根源。
为什么减线程是治标不治本
FrankenPHP 的 worker 模式下,每个 PHP 进程长期存活,反复处理请求。如果某次请求因代码问题占用了 128MB 内存却没释放,下一次请求进来就可能叠加到 256MB……直到 OOM。此时单纯把 num_threads 从 8 降到 4,只是让崩溃来得慢一点,但单个 worker 的内存泄漏仍在发生。
-
num_threads × memory_limit是硬性约束,但它防的是“理论峰值”,不是“实际泄漏” - 减线程会直接降低并发吞吐,QPS 可能掉 30%~50%,而你真正该省的是每请求的内存峰值,不是请求数
- 很多线上事故里,
memory_limit = 512M+num_threads = 4的配置,反而比memory_limit = 256M+num_threads = 8更早 OOM——因为泄漏更隐蔽、更难被触发和发现
哪些代码问题最常导致内存卡住
重点盯住那些“只增不减”的地方,尤其是 worker 模式下跨请求复用的上下文:
- 全局静态变量或
static属性缓存了大数组、查询结果、模板渲染内容,没做 TTL 或 size 限制 - 数据库连接(
PDO)没显式$pdo = null或unset($pdo),连接对象在 worker 生命周期内一直持有着资源 - 日志写入用
file_put_contents(..., FILE_APPEND)而非fopen+fwrite+fclose,文件句柄没关,底层 fd 泄漏 - 使用
opcache_get_status()['scripts']或类似调试函数,返回全量脚本信息(可能几 MB),又没及时unset - ThinkPHP/Laravel 的
Cache::rememberForever()或Config::set()在请求中动态写入大量键值,没清理机制
怎么快速定位泄漏点
别靠猜,用 memory_get_usage(true) 打点监控,放在关键路径前后:
error_log('before query: ' . memory_get_usage(true));
$result = DB::table('big_table')->get(); // or raw PDO
error_log('after query: ' . memory_get_usage(true));
unset($result); // 立即释放
error_log('after unset: ' . memory_get_usage(true));
观察三处差值。如果“after query”比“before query”高 80MB,“after unset”只降了 5MB,说明 $result 没真正释放——可能是被闭包、静态变量或日志中间件意外引用了。
- 不要依赖
gc_collect_cycles()强制回收,它不解决引用环,且 worker 模式下 GC 触发时机不可控 - 禁用
opcache.enable_cli=1(如果用 CLI 启动 worker),避免 OPcache 在非 Web 场景下误开,占用额外内存 - 检查
php.ini中realpath_cache_size是否过大(如设成 4M),在大量文件操作场景下会吃掉几十 MB
线程数调整只在特定条件下才该动
只有当你确认代码已无明显泄漏、OPcache 已调优、所有 memory_limit 设置合理后,再考虑线程数:
- 压测时发现平均请求内存稳定在 180MB,服务器可用内存 2GB → 理论上限是
2048 / 180 ≈ 11个线程,但留 20% 余量,设num_threads = 8是安全的 - 日志里频繁出现
Cannot allocate memory错误,且free -h显示可用内存已低于 500MB,这时才该降线程 - 用
ps aux --sort=-%mem | head -10查看实际进程 RSS,如果单个frankenphp进程 RSS 常驻在 300MB+,说明代码层优化还没到位,别急着调配置
最危险的操作,是看到 OOM 就立刻去改 Caddyfile 里的 num_threads,然后以为问题解决了——其实只是把崩溃推迟到了下次流量高峰。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











