2.2 MPM 三模型:prefork、worker 与 event 本节摘要:MPM(多处理模块)决定 httpd 用什么结构接住并发连接:prefork 每连接一进程,worker 进程池加线程,event 用少量线程看管大量连接。本节逐一拆解三种模型的内部结构、阻塞行为与适用场景,解释从 prefork 到 event 的演进动因——本质上是一场"慢连接占资源"的持久战,最后用一张对比矩阵收拢选型逻辑。 读完这一节你能做什么 阅读完本节,你应当能够: 画出三种 MPM 的进程/线程结构并解释各自接客方式; 解释 keep-alive 慢连接对不同模型资源占用的影响; 说出当前 MPM 的查看与切换方法及切换注意事项; 根据流量形态给出 MPM 选型建议;
本节摘要:MPM(多处理模块)决定 httpd 用什么结构接住并发连接:prefork 每连接一进程,worker 进程池加线程,event 用少量线程看管大量连接。本节逐一拆解三种模型的内部结构、阻塞行为与适用场景,解释从 prefork 到 event 的演进动因——本质上是一场"慢连接占资源"的持久战,最后用一张对比矩阵收拢选型逻辑。
阅读完本节,你应当能够:
回到 1990 年代的服务器设计。最朴素的方案:每来一个连接,fork 一个子进程去伺候,处理完就回收。简单、隔离性极好——一个进程崩了别的连接毫发无损。这就是 prefork 的原型,也是它至今仍被保留的原因:进程隔离带来的稳定性,对某些环境(特别是每请求都要独立地址空间的场景)仍是硬需求。
prefork 的结构:一个父进程负责监督,预先生成一批空闲子进程排队等活(这就是"pre-fork"——提前 fork)。连接到来分配给一个空闲子进程,一对一伺候到底,包括连接闲置的每一秒。
代价在内存与伸缩性:每个 httpd 进程典型要吃 5–20MB 内存(取决于加载的模块与请求处理深度),250 个并发连接就是 250 个进程。更糟的是 keep-alive:浏览器为了下次请求省握手,会让连接保持打开几十秒。在 prefork 下,一个打开着但没在传数据的连接,依然占着一个完整进程。想象一个仓库雇了 250 个保安,每人只盯一个访客,而访客大部分时间只是在门厅里刷手机——保安也得干等着。
worker 模型的解法是进程内再切线程:若干个子进程(典型 2–16 个),每个进程里再开几十个线程,每个线程伺候一个连接。线程共享进程内存,单个连接的内存成本从"整个进程"降到"一个线程栈"(典型 1–8MB 栈空间共享进程映像,实际增量远小于新进程)。
同样 250 并发,worker 可能只需要 5 个进程乘以 50 线程,内存占用降到 prefork 的几分之一。CPU 上下文切换也更轻。
但 worker 引入了新的约束:线程共享地址空间,模块必须线程安全。任何一个模块在多线程下使用非线程安全的库(老版本的非线程安全数据库客户端库是重灾区),就会出现随机崩溃、数据串扰——这类 bug 最难查,因为复现不稳定。2.0 时代大量第三方模块没做线程安全处理,这是 prefork 长期作为默认模型的原因,也是"PHP 环境必用 prefork"这类老经验的历史来源。
worker 还没解决 keep-alive 的问题:连接闲置时,占用的仍是"一个线程"。线程虽便宜,但慢连接一多(移动网络下大量半死连接是常态),线程池照样被拖干。
event 模型(2.4 转正)的核心改进一句话可以说完:把"等数据"和"处理数据"分开。连接闲置、等待下一个请求的时段,交给少量专门的监听线程用操作系统的事件通知机制(epoll/kqueue 一类)统一看管;只有数据真的到达、需要读写处理时,才交给工作线程处理,处理完立刻归还,连接回到监听池。
效果:5000 个空闲 keep-alive 连接可能只占几十个线程的看管成本,而同样场景下 prefork 需要 5000 个进程、worker 需要 5000 个线程。慢连接从"吞噬资源"变成"几乎免费"。
一个容易忽略的细节:event 对连接的挂起处理有边界——请求体交互阶段和某些需要长阻塞的过滤器仍会把连接交回线程伺候。另外,TLS 连接在早期版本里长期是"半 event"状态,直到 2.4.x 后期版本才完整支持挂起 SSL 读写。生产上升级 event 前先确认版本足够新。

先看当前跑的是哪种模型:
httpd -V | grep -i mpm # 或 apachectl -M | grep mpm
输出示例:
Server MPM: event mpm_event_module (static)
Debian 系切换:a2dismod mpm_prefork && a2enmod mpm_event,RedHat 系编辑模块加载配置换 LoadModule 行。切换后必须完整重启(不是 graceful)——进程模型变了,得整个重开。
选型决策表:
| 维度 | prefork | worker | event |
|---|---|---|---|
| 单连接成本 | 高(整进程) | 中(一线程) | 闲置近零 |
| 稳定性隔离 | 最好 | 进程级 | 进程级 |
| 线程安全要求 | 无 | 有 | 有,且更严格 |
| 万级空闲连接 | 不可行 | 勉强 | 舒适 |
| 现代默认 | 否 | 否 | 是(2.4 默认) |
实践建议压缩成三条:新部署一律 event,除非有非线程安全模块拖后腿;老 mod_php 环境要么留 prefork,要么(更推荐)把 PHP 挪到 FastCGI/PHP-FPM 再上 event——这也是当前主流路线;worker 在 event 覆盖后已少有独立存在意义,可当 event 不可用(极老内核)时的备胎。
⚠️ 常见坑:在 prefork 上按 event 的思路调大连接数参数,内存先爆。MPM 没换,参数哲学就不能换。
httpd -V 查看、需完整重启、切换前清点模块线程安全性;💡 一句话记住本节:MPM 决定"陪聊"的价格——event 把闲聊的账单基本抹掉了。
下一节我们把模型选型落到数字上:参数怎么算、压测怎么验。