frankenphp并发能力由内存硬封顶而非cpu决定,需按单请求峰值内存反推线程数,并通过队列深度与忙比率验证瓶颈。

不能靠“CPU核数×2”或“QPS理论值”估算,必须按内存约束反推,并用队列深度和线程忙比率验证。
先算死线:num_threads × memory_limit 必须小于可用内存
FrankenPHP 的并发承载能力不是由 CPU 决定的,而是被内存硬封顶。每个 num_threads 对应一个 OS 线程,每个线程在峰值时会吃掉接近 memory_limit 的内存(含 OPcache、autoload、框架实例等)。常见误判是只看 php.ini 里的 memory_limit = 128M,但实测 Laravel 单请求常驻内存常达 35–45MB,峰值可能冲到 200MB+。
- 用
memory_get_peak_usage(true)在入口打点,压测中跑watch -n 1 'ps aux --sort=-%mem | head -5'看真实占用 - 服务器有 4GB 可用内存?建议预留 2GB 给系统和其他服务 → 最多留给 FrankenPHP 2GB
- 若实测单请求峰值约 180MB,则
num_threads最高只能设2048 / 180 ≈ 11,取整为 8~10 更稳妥 - 别信默认
num_threads = cpu_cores × 2:8 核机器默认启 16 线程,16 × 180MB = 2.88GB,已超限
再盯活指标:frankenphp_queue_depth 和 busy_ratio 才反映真实瓶颈
CPU 低但响应慢?90% 是请求在排队,不是 PHP 没算完。关键不是“能启动多少线程”,而是“请求进来后能不能立刻被调度”。
- 访问
/metrics(需 Caddy 启用prometheus),重点看两个指标:frankenphp_queue_depth(排队请求数)、frankenphp_threads_busy/frankenphp_threads_total(忙比率) -
frankenphp_queue_depth > 0持续存在 → 线程池已满,新请求进不来;此时加 QPS 没意义,只会让队列更长 - 忙比率长期 > 0.85 且
frankenphp_queue_depth同步上涨 → 不是线程太少,而是线程卡在 I/O(如没超时的curl_exec()、未优化的 N+1 查询、PDO 连接池耗尽) - 用
htop -p $(pgrep -f frankenphp)看线程状态:大量S(sleep)而非R(running),基本可断定是阻塞型等待
最后验泄漏:PDO、日志、句柄不释放比线程数错配更早拖垮服务
Worker 模式下进程常驻,资源不显式释放就会缓慢堆积,上线几天后突然崩,日志里却找不到明显报错。
- MySQL 连接数检查:
show status like 'Threads_connected';对比max_connections,接近即为瓶颈;Doctrine 必须设pdo::ATTR_PERSISTENT => true,并禁用doctrine.dbal.auto_close - 文件句柄泄漏典型现象:请求卡在
fopen系统调用,ulimit -n被耗尽;file_put_contents(..., FILE_APPEND)必须换成RotatingFileHandler并设max_files: 7 - 所有
curl_init()、new PDO()、fopen()都要有明确的curl_close()、unset($pdo)、fclose()路径,不能依赖析构函数 - 用
netstat -an | grep :3306 | wc -l和lsof -p $(pgrep -f frankenphp) | wc -l持续观察连接/句柄数趋势,缓慢上涨就是泄漏信号
真正决定并发上限的,从来不是你写了多快的算法,而是你有没有把每个线程的内存边界、每个请求的资源生命周期、每条外部调用的超时和释放路径,都钉死在配置和代码里。漏掉任意一环,压测数据再漂亮,上线后流量一抖就露馅。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











