frankenphp高并发响应慢但cpu低的主因是线程池过载、请求排队或i/o阻塞,典型表现为frankenphp_queue_depth持续>0且threads_busy/total>0.8,需监控/metrics指标并检查curl、db连接等超时配置。

看 frankenphp_queue_depth 和 threads_busy 比值
慢请求占满线程的典型表现是:CPU 很低,但请求排队、响应延迟飙升。FrankenPHP 不会靠 CPU% 判断瓶颈,而是靠两个 Prometheus 指标:frankenphp_queue_depth(当前排队请求数)和 frankenphp_threads_busy / frankenphp_threads_total(忙线程占比)。
访问 /metrics 端点(需 Caddy 启用 prometheus 指令),持续看到 frankenphp_queue_depth > 0,且 threads_busy / threads_total > 0.8,说明线程池已饱和——不是没资源,是线程全卡在某个地方出不来。
- 大量线程处于
S(sleep)状态 + 高队列深度 → 基本是 I/O 阻塞,比如没设超时的cURL、未释放的数据库连接、同步文件读写 - 线程长期处于
R(running)但 CPU 不高 → 可能是 PHP 层死循环、正则回溯、或 GC 频繁触发 - 只查
top或htop进程总 CPU,会漏掉线程级阻塞,必须用htop -p $(pgrep -f frankenphp)看每个线程状态
给外部调用加硬性超时
90% 的线程卡住,源于没设超时的下游依赖。FrankenPHP 本身不干预这些调用,必须你在 PHP 代码里显式控制。
-
cURL必须设CURLOPT_TIMEOUT(总耗时)和CURLOPT_CONNECTTIMEOUT(建连耗时),不能只靠default_socket_timeout - PDO 连接要在 DSN 中加
&connect_timeout=5,或初始化时传PDO::ATTR_TIMEOUT => 5 -
file_get_contents()用stream_context_create()设置timeout和ignore_errors,否则 DNS 失败可能卡住几十秒 - 避免在 Worker 循环内复用未设超时的长连接客户端(如 Guzzle 默认不设 timeout,要手动配
timeout: 5)
用 max_wait_time 主动拒绝排队请求
FrankenPHP 允许你设置请求在队列中最多等多久。超过即返回 503,而不是无限排队拖垮整个池子。
在 Caddyfile 的 php 块中加:
php {
root /var/www
max_wait_time 2s
}
-
max_wait_time是 FrankenPHP 特有指令(非 Caddy 原生),1.1+ 版本支持 - 设太短(如
100ms)会导致正常波动也被拒;设太长(如10s)失去保护意义;建议从2s起压测调整 - 该值应明显小于 Caddy 的
timeout(如timeout 30s),确保排队失败早于执行超时 - 配合监控告警:当
frankenphp_queue_depth频繁触达max_wait_time边界,说明线程池真不够了,该扩容或优化脚本
Worker 模式下主动清理连接与状态
慢请求残留常因上一个请求的资源没释放,导致下一个请求继承“卡住”的上下文。尤其在复用数据库连接、缓存句柄、全局静态变量时。
- 每次
frankenphp_handle_request()执行完后,显式调用DB::disconnect()(Laravel)或mysqli_close($conn),别依赖析构函数 - 避免
static $cache = [];类型的跨请求缓存;改用apcu_store()并设 TTL,或用$_SERVER['REQUEST_TIME_FLOAT']做轻量隔离 - 重置框架级单例:Laravel 要调
$app->resetResolvedInstances(),ThinkPHP 用think\Container::clear() - 手动触发 GC:
gc_collect_cycles()放在请求处理末尾,防止内存缓慢增长间接拖慢后续请求
真正难的不是加超时或调参数,而是把“一次请求 = 一次干净沙盒”这个契约写进每一处连接初始化和资源释放逻辑里。FrankenPHP 不会替你做状态隔离,它只提供复用的舞台——谁上台、怎么谢幕,得你自己定规则。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











