编程 mod_php + prefork 容量计算:ServerLimit 默认 256 压着 MaxRequestWorkers,PSS 才是每进程口径

2026-11-05 00:05:16

mod_php + prefork 容量计算:ServerLimit 默认 256 会限制 MaxRequestWorkers

mod_php 站点做容量规划,容易出错的地方不在应用代码,而在进程模型。prefork 下每个连接对应一个进程,每个进程又各自带一份 PHP 解释器;容器或虚拟机内存算错,先出问题的是 Apache 的 worker 数。

默认配置和第一个容易忽略的限制

prefork MPM 的默认值如下:

  • StartServers 5
  • MinSpareServers 5
  • MaxSpareServers 10
  • MaxRequestWorkers 256
  • ServerLimit 256
  • MaxConnectionsPerChild 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 通过。

复制全文 生成海报 mod_php Apache mpm_prefork 性能调优 运维

推荐文章

程序员茄子在线接单