Apache与Nginx的工作原理有何不同?连接处理与性能如何验证
Apache 和 Nginx 的区别,不能简单归结为“一个慢、一个快”。Nginx 通常以事件驱动模型处理大量并发连接,Apache 则通过 MPM(多处理模块)在进程、线程或事件模型之间切换;但现代 Apache 的 event MPM 同样可以高效处理 Keep-Alive 连接。因此,静态文件、高并发空闲连接和动态应用请求,可能得出完全不同的性能结果。
要回答 Apache 与 Nginx 的工作原理有何不同,建议先检查实际启用的进程模型,再在相同内容、相同协议、相同并发和相同日志策略下压测。重点观察每秒请求数、P95/P99 延迟、错误率、CPU、内存、文件描述符和后端耗时,而不是只看某一次测试输出的吞吐量。
先明确“连接处理”与“请求处理”的区别
一个 TCP 连接不一定只承载一个 HTTP 请求。HTTP/1.1 Keep-Alive 会让同一连接连续发送多个请求;反向代理场景中,一个客户端连接还可能对应一个到后端的连接。因此:
- 并发连接数不等于每秒请求数。
worker_connections不等于 Nginx 能处理的 HTTP 请求数。- Apache 的
MaxRequestWorkers更接近同时处理请求的能力,而不是简单的 TCP 连接总数。 - 高并发连接占用的主要是文件描述符、连接状态和少量内存;真正执行动态代码时,还会消耗线程、进程和 CPU。
| 对比项 | Apache | Nginx |
|---|---|---|
| 核心结构 | 由 MPM 决定,可使用 prefork、worker 或 event | Master-Worker 加事件循环 |
| 连接等待 | 取决于 MPM;旧式进程模型可能让连接长期占用进程 | Worker 使用非阻塞事件机制管理多个连接 |
| 静态文件 | 通过模块和进程/线程处理 | 事件循环配合文件 I/O、缓存或 sendfile |
| 动态请求 | 可通过模块、CGI、代理或应用服务器执行 | 通常转发给 PHP-FPM、Java、Python 等后端 |
| 配置方式 | 模块丰富,也可支持目录级配置 | 以中心化配置为主,不使用 Apache 的 .htaccess |
| 典型优势 | 兼容性、模块生态和目录级控制较灵活 | 高并发连接、静态资源和反向代理场景资源开销较可控 |
| 典型限制 | 进程、线程和模块会增加资源消耗 | 动态执行依赖后端,复杂模块或阻塞操作仍可能拖慢 Worker |
Apache 如何处理连接
Apache 的实际行为首先取决于启用的 MPM。可以把它理解为同一套 Web 服务框架下的不同调度方式。
prefork:进程隔离明显,资源开销较高
prefork 模式通常由多个子进程处理请求,每个子进程同一时间处理一个请求。早期依赖进程内模块的应用常使用这种方式,因为模块不需要考虑多线程安全问题。
它的特点是隔离性较直观:一个子进程异常,通常不会直接影响其他子进程。但每个进程都需要独立的地址空间和运行环境,在并发连接数增加时,内存占用可能快速上升。
如果大量连接处于 Keep-Alive 等待状态,进程模型可能出现“连接没有进行计算,却占用了处理资源”的情况。具体行为还与 Apache 版本、配置和 MPM 实现有关,不能仅根据 Apache 软件名称判断。
worker:一个进程包含多个线程
worker 模式由多个子进程和多个线程共同工作。线程共享进程内存,通常比一连接一进程的模式节省内存,适合多线程安全的模块和应用。
但线程共享资源也增加了并发安全、锁竞争和模块兼容方面的要求。如果某个模块不是线程安全的,或者请求执行过程中存在阻塞操作,线程模型的优势可能被削弱。
event:将空闲连接与活跃请求分开处理
event MPM 进一步优化了 Keep-Alive 连接的处理方式。监听线程可以管理处于等待状态的连接,把真正需要读取、解析或发送数据的请求交给工作线程处理。
这意味着 Apache 并非永远是“一个连接对应一个进程”。在启用 event MPM 后,Apache 与 Nginx 在连接等待阶段的差距可能明显缩小。需要注意的是,具体模块是否支持事件模型、请求是否会回退到传统处理方式,仍取决于实际版本和模块组合。
查看 Apache 当前使用的 MPM,不要只看配置文件名称,应以运行结果为准:
apachectl -V
apachectl -M | grep -i mpm
在部分发行版中命令可能是 apache2ctl,服务名也可能是 httpd 而不是 apache2。如果命令没有输出,先确认 Apache 的管理命令和安装路径。
Nginx 如何处理连接
Nginx 通常由一个 Master 进程和多个 Worker 进程组成:

- Master 读取配置、创建监听套接字并管理 Worker。
- Worker 通过
epoll、kqueue或系统对应的事件机制监听多个连接。 - 当某个连接可读或可写时,Worker 才处理对应事件。
- 静态请求直接读取文件并返回。
- 动态请求或反向代理请求通过非阻塞 I/O 与上游服务交换数据。
Nginx Worker 并不是为每个连接创建一个线程,而是使用事件循环轮询大量套接字。大量连接处于等待状态时,每个连接只保留必要的状态,不需要长期占用独立进程或线程。
但“事件驱动”不等于“任何请求都不会阻塞”。以下操作仍可能阻塞 Worker 或显著增加 CPU 消耗:
- 执行耗时的第三方模块;
- 大量压缩、加密或复杂正则匹配;
- 慢速磁盘读取;
- 不合理的日志写入;
- 上游响应迟迟不返回;
- 在 Worker 中执行同步、耗时的业务逻辑。
Nginx 中常见的 worker_processes 和 worker_connections 也不能脱离系统限制理解。理论上的连接数量可近似表示为:
Worker 数量 × 每个 Worker 的 worker_connections
但实际可用上限还会受到文件描述符、监听队列、内存、内核参数以及反向代理上游连接数影响。一个客户端连接加一个上游连接时,连接资源消耗可能接近两个套接字,而不是一个。
查看 Nginx 编译参数和最终生效配置:
nginx -V 2>&1
nginx -T 2>&1 | sed -n '1,180p'
nginx -T 会输出完整配置,适合确认 worker_processes、worker_connections、Keep-Alive、代理缓冲和日志配置是否真的生效。
为什么静态请求和动态请求会得出不同结果
性能差异通常来自四个层面。
1. 连接等待成本
如果测试是大量长连接、低请求频率,主要考察的是连接状态管理、文件描述符和事件调度。此时 Nginx 的事件模型通常更容易保持较低的内存开销,但启用 Apache event MPM 后,结果可能接近。
2. 请求处理成本
如果每个请求都需要复杂规则、认证、压缩、日志或模块处理,单纯比较连接模型就不够了。Apache 的模块链、目录级配置检查和请求钩子可能增加处理步骤;Nginx 则可能因第三方模块或复杂配置消耗更多 CPU。
3. 后端应用成本
动态请求通常经过 PHP-FPM、Java、Python 或其他应用服务。此时 Web 服务器只负责接收请求、转发数据和返回结果。如果后端 CPU 已经达到瓶颈,换 Apache 或 Nginx 对吞吐量的影响可能很小。
4. 网络与协议成本
TLS 握手、HTTP/2 多路复用、响应压缩、客户端网络延迟和丢包都会改变结果。使用本机回环地址测试,主要验证 Web 服务自身;使用远端压测机,才会把网络路径和真实传输过程纳入结果。
因此,测试静态小文件时,可能看到明显的连接处理差异;测试一个平均耗时 80 毫秒的动态接口时,两者结果可能都被后端耗时主导。
一套可复现的性能验证方法
下面以 Linux 主机、HTTP/1.1、静态文件和可选动态接口为例。示例命令默认访问 127.0.0.1,只适用于自有主机或已获授权的测试环境,不应直接对生产服务进行高并发压测。
1. 记录测试环境
至少记录以下内容:
- CPU 逻辑核心数和内存;
- Linux 发行版、内核版本;
- Apache 或 Nginx 的版本;
- Apache 当前 MPM;
- 静态文件大小和内容;
- 是否开启 Keep-Alive、TLS、压缩和访问日志;
- 测试工具版本;
- 压测端与服务端是否为同一台机器;
- 每轮测试的持续时间、并发连接数和重复次数。
可以先执行基础检查:
uname -a
nproc
free -h
nginx -v
apachectl -v
ulimit -n
如果 Apache 使用的是 apache2ctl,将命令替换为对应名称。服务通过 systemd 管理时,还可以检查服务级文件描述符限制:
systemctl show nginx -p LimitNOFILE
systemctl show apache2 -p LimitNOFILE
2. 确认请求确实到达目标服务
Apache 和 Nginx 应使用相同的测试文件、相同的响应头策略和相同的协议。先用 curl 验证状态码和延迟:
curl --http1.1 -fsS -o /dev/null \
-w 'code=%{http_code} connect=%{time_connect}s ttfb=%{time_starttransfer}s total=%{time_total}s\n' \
http://127.0.0.1:8080/test-1k.txt
连续执行几次,观察是否稳定返回 200。如果两个服务器返回的内容不一致,可以分别保存响应并比较摘要:
curl --http1.1 -fsS http://127.0.0.1:8080/test-1k.txt -o /tmp/web-test-a.bin
sha256sum /tmp/web-test-a.bin
测试文件应放在专用目录,文件名应避免覆盖现有业务文件。静态测试至少准备一个较小文件和一个较大文件,否则只能观察单一 I/O 场景。
3. 先做低并发基线,再提高并发
可以使用 wrk 进行持续压测。以下数据只是测试流程示例,具体并发应根据文件描述符和机器资源逐步增加:
wrk -t2 -c10 -d30s --latency \
http://127.0.0.1:8080/test-1k.txt
预热完成后,使用相同参数测试多个并发档位:
for c in 1 10 50 100 300
do
echo "===== connections: $c ====="
wrk -t4 -c"$c" -d60s --latency \
http://127.0.0.1:8080/test-1k.txt
done
每个档位至少重复三次,记录平均值和波动范围。不要一次只跑一个高并发结果,因为首次加载文件、连接建立、CPU 频率变化和后台任务都可能影响输出。
Keep-Alive 应单独作为变量进行测试。默认 Keep-Alive 与关闭连接复用的结果不能混在同一张表中:
wrk -t4 -c100 -d60s --latency \
-H 'Connection: close' \
http://127.0.0.1:8080/test-1k.txt
如果比较动态接口,应保证接口返回内容稳定,并尽量避免数据库写入、随机计算和外部网络调用。静态接口与动态接口的结果应分开记录,不能用静态文件的吞吐量推断业务接口容量。
4. 同时观察系统资源
仅记录 wrk 输出不够。压测期间可以在另一个终端观察系统状态:
vmstat 1
查看 Nginx 进程的 CPU 和内存:
pidstat -dur -C nginx 1
查看 Apache 进程:
pidstat -dur -C apache2 1
如果发行版中的进程名不同,先用以下命令确认:
pgrep -a nginx
pgrep -a apache2
pgrep -a httpd
没有 pidstat 输出时,可以改用具体 PID:
pidstat -dur -p 1234,1235 1
连接和监听状态可以通过以下命令辅助判断:
ss -s
ss -lntp | grep -E ':(8080|8081)\b'
重点关注以下指标:
| 指标 | 观察重点 | 常见解释 |
|---|---|---|
| Requests/sec | 吞吐量是否随并发增加 | 到达平台后说明某个资源接近上限 |
| P50 | 常规请求体验 | 反映大多数请求的延迟 |
| P95/P99 | 尾部延迟 | 适合发现排队、慢后端和资源争用 |
| Socket errors | 连接或读写失败 | 可能与监听队列、文件描述符或客户端中断有关 |
| CPU user/system | 用户态和内核态消耗 | 区分应用计算与网络、系统调用开销 |
| RSS | 进程常驻内存 | Apache 多进程或模块加载可能使总量上升 |
| Context switches | 上下文切换数量 | 线程、进程过多或竞争激烈时可能增加 |
| Established 连接数 | 实际连接规模 | 验证并发参数是否真正生效 |
如何解读一组示例结果
下面是一组模拟数据,仅用于说明判断方法,不代表特定机器、版本或实际测量结论。测试条件设为:同一台 Linux 主机、1 KiB 静态文件、HTTP/1.1 Keep-Alive、100 个并发连接、单轮 60 秒,内存为所有相关进程的合计值。

| 服务模型 | 吞吐量 | P95 延迟 | 错误率 | 总 RSS |
|---|---|---|---|---|
| Apache prefork | 约 4,300 req/s | 34 ms | 0 | 约 460 MB |
| Apache event | 约 17,200 req/s | 13 ms | 0 | 约 145 MB |
| Nginx Worker | 约 19,400 req/s | 11 ms | 0 | 约 82 MB |
这组数据可以说明三个现象:
- Apache 的差异可能来自 MPM,而不是软件名称本身。将 prefork 与 event 混为一个“Apache 性能值”会造成误判。
- Nginx 与 Apache event 的差距已经比 Nginx 与 Apache prefork 的差距小,说明连接处理模型比品牌标签更重要。
- RSS 较低不代表所有请求都更快,还需要结合 CPU、尾部延迟和错误率判断。
如果把测试对象换成一个后端平均耗时较高的动态接口,可能得到类似结果:
| 服务模型 | 吞吐量 | P95 延迟 | 后端 CPU |
|---|---|---|---|
| Apache event | 约 4,850 req/s | 84 ms | 96% |
| Nginx Worker | 约 4,900 req/s | 82 ms | 96% |
此时两者的吞吐量接近,原因不是连接模型没有差异,而是后端已经成为主要瓶颈。继续调大 worker_connections 或 Apache 工作线程,通常不能线性提高整体吞吐量,反而可能增加排队和超时风险。
常见测试异常及其含义
吞吐量随并发增加后不再上升
先看 CPU:
- CPU 接近满载,说明请求处理、压缩、TLS 或应用计算可能已经饱和;
- CPU 不高但延迟持续上升,可能是磁盘、锁、后端连接池或监听队列受限;
- Nginx 反向代理场景还要统计上游连接,因为客户端连接数增加不一定等于后端处理能力增加。
出现“Too many open files”
这通常意味着进程级或服务级文件描述符不足。Nginx 的 worker_connections 即使设置得很高,也不能突破进程的 LimitNOFILE;Apache 也会受到系统限制。
不要直接把参数改到很大。先记录当前值、确认内存和连接规模,再逐步调整,并在调整后重新运行相同并发档位。
Apache 返回 503 或请求排队
需要检查当前 MPM 的工作线程、进程数和 MaxRequestWorkers 等配置。503 可能表示可用工作槽位耗尽,也可能来自后端不可用,不能只根据状态码判断原因。
Nginx 返回 502 或 504
这通常优先指向上游服务、上游连接、应用响应时间或代理超时配置,而不是 Nginx 的静态连接模型。应同时查看 Nginx 错误日志、后端监听端口和后端应用日志。
压测结果波动很大
常见原因包括:
- 压测端与服务端共用 CPU;
- 测试文件首次读取触发磁盘缓存;
- 一次开启日志、一次关闭日志;
- 一次使用 Keep-Alive,另一次使用短连接;
- 测试期间有备份、更新或其他后台任务;
- 每轮测试的并发、线程数或持续时间不同。
应先固定变量,再重复三轮以上。若要观察真实用户延迟,不能只使用回环地址;若只验证 Web 服务连接处理能力,回环测试反而能减少网络因素干扰。
修改配置后的复测边界
性能测试前不一定需要调参。应先确认当前配置,再建立基线。如果确实需要修改配置,先备份对应文件,记录变更内容和影响范围。Nginx 可以先做语法检查:
sudo cp /etc/nginx/nginx.conf \
/etc/nginx/nginx.conf.bak.$(date +%F-%H%M%S)
sudo nginx -t
检查通过后再按实际服务管理方式重新加载。Apache 同样先检查语法:
sudo apachectl configtest
不同发行版的配置文件路径、服务名和包含文件可能不同,不能只恢复主配置文件而忽略 conf.d、站点配置或模块配置。回滚时恢复备份,重新执行语法检查,再进行 reload;如果服务已处于异常状态,应避免在没有确认影响范围的情况下反复重启。
每次复测至少保持以下条件不变:
- 相同的服务版本和编译模块;
- 相同的静态文件或动态接口;
- 相同的 HTTP 协议、Keep-Alive 和 TLS 设置;
- 相同的日志、压缩和缓存策略;
- 相同的压测工具、线程数、并发数和持续时间;
- 相同的预热和重复次数。
结果的适用边界
如果目标是大量静态文件、反向代理和长连接,Nginx 的事件驱动模型通常值得优先验证;如果依赖丰富模块、目录级配置、既有 Apache 规则或特定应用兼容性,Apache 的 MPM 选择和模块组合更关键。
但不能从一次静态文件压测直接得出动态业务结论,也不能从回环地址测试推断远程用户的网络延迟。真正有参考价值的结果,应同时说明连接模型、测试内容、协议、并发、资源使用和后端状态。
判断 Apache 与 Nginx 谁更适合当前服务,最可靠的方式不是比较软件名称,而是先确认实际工作模型,再用低并发基线、高并发曲线、尾部延迟和错误率进行复测。只有当吞吐量、P95/P99、资源消耗和应用后端状态共同指向同一个瓶颈时,测试结果才足以支持配置或架构调整。