mod_php + prefork 容量计算:ServerLimit 默认 256 会限制 MaxRequestWorkers
mod_php 站点做容量规划,容易出错的地方不在应用代码,而在进程模型。prefork 下每个连接对应一个进程,每个进程又各自带一份 PHP 解释器;容器或虚拟机内存算错,先出问题的是 Apache 的 worker 数。
默认配置和第一个容易忽略的限制
prefork MPM 的默认值如下:
StartServers 5MinSpareServers 5MaxSpareServers 10MaxRequestWorkers 256ServerLimit 256MaxConnectionsPerChild 0
MaxRequestWorkers 受 ServerLimit × ThreadsPerChild 限制。prefork 下 ThreadsPerChild=1,所以想把它提到 256 以上,必须同步提高 ServerLimit,否则启动时直接告警:
WARNING: MaxRequestWorkers of X exceeds ServerLimit value of 256, lowering...
这类报错不是建议,它会把实际并发强行改回 256。压测时发现 QPS 上不去,先检查这里,再看应用。
每个 httpd 子进程包含什么
mod_php 把 PHP 解释器嵌入每个 httpd 子进程。非 ZTS 构建的 PHP 只能跑 prefork,没有线程复用这回事。单个进程的常驻内存大致是:
httpd 基础 + PHP 解释器 + 已加载扩展 + 脚本常驻内存
脚本结束时内存会释放,但内存分配器不一定马上归还 OS,所以进程 RSS 会稳定在一个偏高的水位,不会明显回落。
opcache 的共享内存不计入每进程 RSS
opcache 在 mod_php 下使用 mmap(MAP_SHARED) 的共享内存段,所有子进程共享同一份。判断方法是:
smem里子进程的 shared 部分偏高;- 各进程 RSS 直接相加,会把这段共享内存重复计入 N 次;
- PSS 才是把共享页按引用数分摊后的真实占用。
所以不要用 opcache.memory_consumption 乘进程数去估算内存,这样算出来的数字会明显偏大。
容量公式:用 PSS,不用 RSS
目标 MaxRequestWorkers = (可用内存 ÷ 单进程实测 PSS) × 0.8
可用内存要先扣除 OS、MySQL、Redis 等常驻部分。0.8 是安全系数,用来应对流量峰值时的脚本内存抖动和 allocator 碎片。单进程 PSS 取实测均值,不要拿空载数字,最好在压测中间取。
MaxConnectionsPerChild 不要长期为 0
MaxConnectionsPerChild 0 表示永不回收,如果 PHP 扩展有泄漏或分配器碎片持续累积,进程会越长越胖。设成 500–5000 一般够用。代价是回收时重新初始化解释器、重新做 opcache 校验,但 mmap 段本身保留,不会每次都从头编译。
静态资源同样占用 PHP 解释器
mod_php 下即使请求的是静态文件,进程依然带着完整的 PHP 解释器占内存。内存吃紧时,把静态资源拆给 nginx,或者在 Apache 侧加 mod_expires 和缓存头,收益比调 MPM 参数更直接。
实测命令
# 平均 RSS(会重复计入共享页,只作粗略参考)
ps -o rss= -C httpd | awk '{s+=$1;n++} END{print n, s/n/1024" MB"}'
# 按 RSS 排序看大户
ps -eo pid,rss,comm --sort=-rss | grep '[h]ttpd' | head
# RSS / PSS / USS 三列一起看,PSS 才是分摊后的真实占用
smem -P httpd -k -c "pname pid rss pss uss"
# 确认当前 MPM
apachectl -M | grep mpm
httpd -V
httpd -M
RPM 系发行版里 MPM 的加载配置在 /etc/httpd/conf.modules.d/00-mpm.conf。要换 MPM,注释掉 LoadModule mpm_prefork_module,换成 mpm_event。
什么时候是真的 worker 不够
MaxRequestWorkers 被打满时,Apache scoreboard 里会出现大量 W 状态,新连接只能排队。但如果大量 W 而 CPU 不高,一般是进程阻塞在 IO 或 DB 上,加 worker 只会让更多请求一起等,先查慢查询和下游超时。
为什么不能直接换 event MPM
event MPM 与 mod_php 不兼容,典型报错是:
PHP Module not compiled to be thread safe
这就是 prefork 成为唯一选择的根本原因。想换成 event,得先让 PHP 脱离 Apache 进程,走 PHP-FPM。
容量计算的关键是两件事:每进程 PSS 实测值要准,ServerLimit 要允许你想要的 MaxRequestWorkers 通过。