容器设了cpu-quota后,num_threads不按物理核数算,须按实际可调度cpu时间反推:以可用逻辑cpu数为基础打7–8折设值,max_threads设auto极危险,需显式限制并调优opcache与memory_limit。

容器设了cpu-quota后,num_threads还按物理核数算吗
不按物理核数算,必须按容器实际可调度的CPU时间反推线程数。Docker 的 --cpu-quota(或 cpus)限制的是容器每秒能用的 CPU 时间片总量,FrankenPHP 的线程是 OS 级线程,每个线程在调度时都争抢这有限的时间片。若仍按宿主机 16 核设 num_threads 16,实际只有 2 核配额,会导致大量线程处于 R(运行中)和 S(可中断睡眠)反复切换状态,frankenphp_busy_threads 长期接近 frankenphp_total_threads,但 CPU% 却上不去——这不是空闲,是“饿着等调度”。
怎么算合理的 num_threads 值
直接看容器的可用逻辑 CPU 数(不是宿主机核数),再打 7–8 折留余量:
- 用
docker run --cpus=2.5→ 按 2 个逻辑 CPU 计,num_threads设2或3(别设 4) - 用
--cpu-quota=50000 --cpu-period=100000(即 0.5 核)→num_threads必须 ≤1;设 2 就注定排队,frankenphp_queue_depth会持续 > 0 - Worker 模式下更要保守:每个 Worker 是常驻线程,长期持有连接和 OPcache 内存,
num_threads 1对应约 30–50MB 固定内存开销,超了容易触发 OOM - 验证方法:压测时盯
htop -p $(pgrep -f frankenphp),如果多个线程 CPU% 都卡在 40–60% 上不去,且frankenphp_threads_busy / frankenphp_threads_total > 0.9,说明线程数远超调度能力
max_threads 设 auto 还安全吗
不安全,尤其在受限 CPU 下设 max_threads auto 是高危操作。FrankenPHP 会无视配额限制,按需拉起线程,结果就是:
- 线程数暴涨,但每个线程分到的 CPU 时间更少,响应延迟反而升高
-
frankenphp_total_threads达到几十,frankenphp_busy_threads却只维持在 1–2,其余全在S状态排队等调度 - Go runtime 调度器压力陡增,
runtime.numGoroutine异常升高,Caddy 日志开始出现context deadline exceeded - 根本不用等到 OOM,
max_wait_time就会批量超时,用户看到的是 503 或 curlConnection refused
正确做法是显式设 max_threads = num_threads + 2,最多允许临时加 2 个线程撑过毛刺流量,之后立刻回收。
OPcache 和 memory_limit 还得跟着调
CPU 受限后,单请求执行时间拉长,OPcache 编译缓存命中率下降,PHP 更频繁地重编译文件,进一步挤占本就紧张的 CPU 时间。此时不调 OPcache,等于把瓶颈从调度层转移到解析层:
-
opcache.enable_cli=1必须开启(Worker 模式依赖 CLI 环境) -
opcache.memory_consumption建议 ≥128(单位 MB),避免频繁淘汰导致重编译 -
memory_limit要同步下调:CPU 受限下请求处理变慢,长时间运行更容易触达原设上限;建议设为128M或96M,并用memory_get_peak_usage(true)实测确认 - 禁用
opcache.validate_timestamps=0(开发环境除外),否则代码更新后无法热生效,排查问题时会误判为性能未提升
最易被忽略的一点:容器 CPU 限制生效后,getrusage(RUSAGE_SELF) 返回的 ru_stime(系统时间)可能显著高于 ru_utime(用户时间),这是内核调度开销上升的信号——此时再堆线程,只是把问题从“慢”变成“更慢且不稳定”。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











