图 1:同样八个连接,两种模型的资源消耗完全不同。
先说明属性:这是一篇经典题解析,题目出自本地历史面试资料整理,与任何公司的当前招聘无关。「Nginx 和 Apache 的区别」是 Web 服务器方向的保留题目,背特性清单只能拿基础分,真正的考点是并发模型:为什么「一连接一进程」注定扛不住高并发,事件驱动又赢在哪。
Apache 的经典模型:连接数是进程数的函数。
Apache 通过多处理模块(MPM)来选择并发模型,官方文档列出的三款各有分工:prefork 用多个进程、每进程同一时刻只处理一个连接;worker 用多进程加多线程;event 在 worker 基础上把 keep-alive 连接交给专门线程照看。面试语境里的经典对比对象通常是 prefork——它至今仍是兼容老式非线程安全模块时的选择。
prefork 的问题可以用一笔账说清:每个连接独占一个进程,每个进程带着自己独立的内存栈和地址空间。五千个并发连接就是五千个进程,哪怕其中九成连接正闲在 keep-alive 上发呆,进程也得原地候着。内存随连接数线性上涨先碰到天花板,就算内存撑得住,几千个进程之间的上下文切换开销也会把 CPU 耗在调度而不是干活上。这就是 C10K 问题的本质:瓶颈不在 CPU 算力,而在「每个连接一个执行体」这个映射关系本身。
Nginx 的模型:连接只是事件循环里的一个状态。
按 Nginx 官方文档的描述,它有一个 master 进程和若干 worker 进程:master 只负责读配置和维护 worker,worker 才是真正处理请求的,数量固定、通常按 CPU 核数自动设置。每个 worker 内部是事件驱动模型——借助操作系统提供的 epoll 这类机制,一个 worker 把成千上万个连接全部登记在一个事件循环里,哪个连接上有数据就绪就处理哪个,处理完立刻回到循环。
这个架构改掉了映射关系:连接不再对应执行体,只是循环里的一条状态记录。一万个连接占用的只是一万份连接状态内存,而不是一万个进程;CPU 只在「有事件」时醒来干活,空闲连接的成本趋近于零。于是内存占用近似常数,不再随连接数线性涨,单机连接数的上限被抬高了一个数量级以上。worker 之间各自独立、共享监听端口,也避免了多进程抢同一把锁的大部分麻烦。
为什么这个差异在高并发下被放大。
低并发时两种模型都够用,差距出现在连接数爬坡阶段。进程模型每多一个连接就多一份进程内存加一份切换成本,资源曲线是陡升的直线;事件模型的边际成本只是一条状态记录,曲线近似平的。尤其面对大量慢连接、长连接——客户端网络慢、keep-alive 挂着不走——进程模型会被空闲连接活活拖死,事件模型却几乎无感。这正是 Nginx 在静态资源服务和反向代理场景成为默认选项的原因:这两类场景的流量特征就是海量连接加少量计算。
图 2:架构对照的核心是「连接对应什么」。
那 Apache 是不是该被淘汰?也不是。
诚实的答案要带边界。Apache 的 event MPM 同样用事件机制处理 keep-alive,官方文档也明确现代系统上的默认 MPM 几乎总是 event——所以「Apache 一定慢」早已不成立,裸性能差距在缩小。Apache 真正的护城河在生态:可按需动态加载的模块体系、目录级的 .htaccess 配置、与老式非线程安全模块的兼容(这正是 prefork 仍被保留的原因)。Nginx 的强项则是高并发下的静态内容、反向代理与负载均衡,配置集中、内存稳、行为可预测。工程上的常见答案是两者搭着用:Nginx 在前扛连接,Apache 在后跑动态模块。
回到面试现场,建议的答题结构:先一句话点题「差异在并发模型」,再分别描述 prefork 的「一连接一进程」和 Nginx 的「master + 少量 worker + epoll 事件循环」,然后算那笔内存与切换的账说明为什么高并发放大差距,最后主动补边界——event MPM 的存在、Apache 的生态优势、两者组合的部署形态。答出「Nginx 是事件驱动」是及格线,讲清「瓶颈是连接与执行体的映射关系」才算摸到考点。你在线上见过哪种模型的瓶颈现场?是内存先爆还是 CPU 先被切换吃掉?欢迎分享。