2.3 MPM 参数调优与容量规划 本节摘要:MPM 参数调优不是背推荐值,而是从"每个进程吃多少内存、机器有多少内存、流量峰值多少并发"三个已知量推导上限。本节给出容量公式与 event 模型参数组的完整配置范例,讲清 KeepAlive、监听队列、进程升降速的联动关系,最后用压测把参数验证闭环。 先把目标摆清楚 阅读完本节,你应当能够: 用内存公式推导 MaxRequestWorkers 的合理上限; 写出一组带注释的 event MPM 参数配置; 解释 StartServers/MinSpare/MaxSpare 与进程升降速的关系; 配置 KeepAlive 并理解它对内存与延迟的双面影响; 用压测工具验证参数改动并读懂瓶颈信号。
本节摘要:MPM 参数调优不是背推荐值,而是从"每个进程吃多少内存、机器有多少内存、流量峰值多少并发"三个已知量推导上限。本节给出容量公式与 event 模型参数组的完整配置范例,讲清 KeepAlive、监听队列、进程升降速的联动关系,最后用压测把参数验证闭环。
阅读完本节,你应当能够:
调优 MPM 的第一性问题:这台机器最多能同时养活多少个伺候进程? 答案由内存决定。公式:
可用给httpd的内存 = 总内存 − 系统占用 − 其他服务占用 − 文件缓存预算 MaxRequestWorkers ≈ 可用内存 ÷ 单进程平均内存
单进程平均内存怎么测?别拍脑袋,直接量。先跑一轮接近真实流量的压测,然后看进程的实际驻留内存:
# 每个工作进程的实际内存(KB),取平均值再乘安全系数 1.2 ps -o pid,rss,cmd -C apache2 | awk 'NR>1 {sum+=$2; n++} END {print sum/n " KB avg"}'
示例输出:
18432 KB avg
代入一台 8GB、系统与其他服务占 2GB 的机器:约 6GB 可用,单进程均值 18MB 乘安全系数 1.2 约 22MB,6144 ÷ 22 ≈ 279,向下取整到 270 左右。这就是这台机器 prefork 的 MaxRequestWorkers 天花板——注意这是内存天花板,不是目标值;真实目标取"业务峰值并发乘 1.5 倍余量"与天花板的较小者。
event/worker 模型的同一参数含义变为"最大工作线程数",因为单线程成本远低于单进程,同样的机器上限可以放大一个数量级。但别高兴太早:线程数大了,CPU 与后端(数据库、PHP-FPM)会先成为瓶颈。MPM 参数只管"门能开多大",队伍真正卡在哪要靠压测说话。
event 模型的参数组(写在 mpm 配置片段里,Debian 系在 mods-enabled 下、RedHat 系在 conf.modules.d 下):
<IfModule mpm_event_module> # 启动时先拉起的子进程数;热身用,运行中会被 Min/MaxSpare 接管 ServerLimit 16 StartServers 4 # 空闲线程的最小/最大储备:低于 Min 立刻扩,高于 Max 逐步缩 MinSpareThreads 75 MaxSpareThreads 400 # 线程总数硬上限 = ServerLimit × ThreadsPerChild ThreadLimit 64 ThreadsPerChild 50 # 允许的最大工作线程数,需 ≤ ServerLimit × ThreadsPerChild MaxRequestWorkers 800 # 空闲进程最多存活时长(秒),防僵尸进程堆积 MaxConnectionsPerChild 5000 # 新连接排队上限,监听套接字的接受队列 ListenBacklog 511 </IfModule>
几条参数之间的联动关系必须理解,否则就是玄学数字:
prefork 对应的关键参数(对照理解):
<IfModule mpm_prefork_module> StartServers 5 MinSpareServers 5 MaxSpareServers 10 MaxRequestWorkers 256 # ← 内存公式算出来的那个数 MaxConnectionsPerChild 4000 </IfModule>
KeepAlive 让同一连接可以连续服务多个请求,省掉每次的 TCP 握手。但第 2.2 节已经看到:在 prefork 下它意味着"占着进程不干活"。参数:
KeepAlive On # 空闲连接最多保留多久;移动网络用户建议 5 秒以内 MaxKeepAliveRequests 100 KeepAliveTimeout 5
配置哲学按模型分家:event 下 KeepAliveTimeout 可以放宽容忍(比如 15–60 秒),因为闲置连接便宜;prefork 下必须抠门(2–5 秒),否则并发一上来进程池被闲连接占光,新请求排队超时。HTTP/2(第 5 章)则天然单连接多路复用,KeepAlive 语义被连接级复用取代。
⚠️ 常见坑:扩容后忘记重算 MaxRequestWorkers。内存翻倍了参数没动,等于买了新仓库还按旧库容量拒客;反过来内存没加却调大参数,OOM 杀进程的随机崩溃会让你查到怀疑人生——被 OOM 杀掉的工作进程在错误日志里往往只留下"child pid exited with signal 11"之类的线索。
调参不能盲调,两个内置视角:
# 实时 scoreboard:每个工作槽位的状态 apachectl status # 或开 mod_status 后: curl -s http://127.0.0.1/server-status?auto | grep -E "Busy|Idle|Scoreboard"
输出示例:
BusyWorkers: 23 IdleWorkers: 27 Scoreboard: __W__W_....K KK...
Scoreboard 字母含义要认几个关键的:_ 空闲、W 正在处理、K keep-alive 挂起、. 尚未开启的槽位。诊断口诀:W 满了且持续 → MaxRequestWorkers 不够或后端太慢;K 一大片 → KeepAliveTimeout 太长;大片点号 → ServerLimit 还没用上,参数互相矛盾。
监听队列的观察在系统层:
ss -tln | grep ':443' # Recv-Q 接近 Send-Q(队列上限)说明接客速度跟不上到达速度
参数改完必须验证。轻量工具 ab 就够做基线:
# 200 并发持续 30 秒压一个静态页 ab -c 200 -t 30 -k https://www.example.com/ | grep -E "Requests per|Time per|Failed|Non-2xx"
输出片段:
Requests per second: 8420.11 [#/sec] (mean) Time per request: 23.752 [ms] (mean) Failed requests: 0
看四个信号:吞吐是否随并发线性上涨(不涨说明撞到 CPU 或后端瓶颈);失败请求是否为零;延迟分布(关注 95/99 分位而非均值——均值是平均幸福,分位是真实幸福);压测时 scoreboard 是否 W 满。更复杂的场景换专业压测工具做阶梯加压,但读数的逻辑不变。

💡 一句话记住本节:先算内存天花板,再看流量需求,最后让压测当裁判。
旅程第二站到此讲完。下一章请求进入站内:配置如何被匹配与合并。