基于Nginx + Apache mod_qos的多层流量整形架构设计:云服务器上的高并发保命方案
1. 背景与痛点
云服务器上的 Web 服务,最怕的不是正常流量,而是「突发毛刺」和「慢连接拖死」。Nginx 处理静态文件是一把好手,但一旦动态请求打到 Apache,Apache 的线程池就会成为阿喀琉斯之踵。肉眼看 Apache 状态页,W (Waiting) 状态堆积上千,机器负载不高,但用户请求就是转圈。原因是每个请求占用了后端连接,而 Nginx 的错误重试机制又放大了流量。
传统方案只做前端限流,却忽略了后端的连接级防护。本文给出一个真正的多层流量整形架构:Nginx 做边缘限流,Apache 用 mod_qos 做后端深度整形,同时用 mod_remoteip 解决真实 IP 穿透问题。这套方案在轻云互联的云服务器上实测,能扛住 30 倍突发流量而不打满后端连接。
2. 架构设计
- 入口层:Nginx(主)——静态资源直接返回,动态请求反向代理到 Apache,并承担 TLS 终结。
- 后端层:Apache(event MPM)——只处理动态应用,启用 mod_qos、mod_remoteip。
- 流量路径:客户端 → Nginx(limit_req/limit_conn) → Apache(mod_qos 二次整形) → 应用。
为什么要双重限流?Nginx 的 limit_req 基于 token bucket,只能做“频率”限制;Apache mod_qos 能针对并发连接、每 IP 带宽、慢客户端超时做精细控制。前者防止攻击者打满 Nginx,后者防止慢连接拖死 Apache 线程。
3. Nginx 层配置
http {
# 每个 IP 每秒 10 个请求,突发 20 个
limit_req_zone $binary_remote_addr zone=flood:10m rate=10r/s;
# 每个 IP 最多 10 个并发连接
limit_conn_zone $binary_remote_addr zone=perip:10m;
upstream apache_backend {
server 127.0.0.1:8080 max_fails=3 fail_timeout=10s;
keepalive 32;
}
server {
listen 443 ssl;
server_name example.com;
# 静态资源直接处理
location ~* \.(css|js|png|jpg|webp|svg)$ {
root /var/www/static;
expires 7d;
access_log off;
}
location / {
limit_req zone=flood burst=20 nodelay;
limit_conn perip 10;
proxy_pass http://apache_backend;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Real-IP $remote_addr;
# 关键:连接超时和半关闭处理
proxy_connect_timeout 5s;
proxy_read_timeout 60s;
proxy_buffering off;
}
}
}
注意 proxy_buffering off——对于实时性要求高的接口,关闭缓冲能让 Apache 快速释放连接;但如果静态压测吞吐,建议开启 buffering。
4. Apache mod_qos 深度配置
先安装和启用模块(Debian/Ubuntu):
apt install libapache2-mod-qos
a2enmod qos
a2enmod remoteip
Apache 配置(mod_qos 部分):
QS_SrvRequestLimit 500
# 每个 IP 同时只允许 10 个连接
QS_ConnLimitPerIP 10
# 每个主机(相对于 IP)最大连接 100
QS_ConnLimitMaxPerHost 100
# 服务端总最大连接数(线程池上限)
QS_SrvMaxConn 600
# 允许 60 秒内关闭的并发连接数
QS_SrvMaxConnClose 120
# 关键:慢连接防护
# 当客户端超过 10 秒没发送数据,强制断开
QS_ClientEventLimit 10
QS_ClientEventBlock MSG
# 带宽整形:每个 IP 最大 1MB/s
QS_SrvMaxBandwidthPerIP 1M
这些指令不是随意的。注意 QS_SrvMaxConn 必须小于 Apache 的实际线程上限(event MPM 的 ThreadsPerChild x ServerLimit),否则 mod_qos 会报错。而 QS_ClientEventLimit 可以识别空闲连接——即使请求没有结束,只要不读写数据,10 秒后就会被踢掉。
再配合 event MPM 调优:
MPM event:
ServerLimit 100
ThreadsPerChild 25
MaxRequestWorkers 250
MinSpareThreads 25
MaxSpareThreads 75
ThreadStackSize 65536
这样 Apache 最多 250 个并发,mod_qos 的 QS_SrvMaxConn 设为 600 已经覆盖,但实际生效时连接会被控制在 250 以内。所以建议把 QS_SrvMaxConn 设为 240,留一点余量。
5. 真实 IP 穿透
因为 Nginx 在后端,Apache 看到的所有连接都来自 127.0.0.1。必须启用 mod_remoteip:
RemoteIPHeader X-Forwarded-For
RemoteIPTrustedProxy 127.0.0.1
这样 Apache 日志里记录的 %h 才会是真实客户端 IP,mod_qos 的每 IP 限制也才能生效。否则所有流量都算到 Nginx 的头上,限流直接失效。
6. 内核模块配合
仅靠应用层还不够,还需要调整系统 TCP 参数,避免 TIME_WAIT 堆积:
cat >> /etc/sysctl.conf << 'EOF'
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_fin_timeout = 15
net.ipv4.tcp_max_syn_backlog = 65535
net.core.somaxconn = 65535
EOF
sysctl -p
对于 keepalive 长连接,Nginx 到 Apache 的 keepalive 必须开启(上面 upstream 里已经加了 keepalive 32),否则每次请求都会新建 TCP,白白增加握手开销。在轻云互联的云服务器上,开启 keepalive 后 QPS 能有 20% 的提升,同时 CPU 占用率下降。
7. 压测与排错
用 ab 或 wrk 压测时,不要只看 QPS,要重点观察两个指标:
- Apache 的
scoreboard中W状态数量——如果持续超过 100,说明有慢连接,检查 mod_qos 的QS_ClientEventLimit是否生效。 - Nginx 的
error.log中是否有limit_req丢弃记录。
# 查看 mod_qos 是否生效
apache2ctl -M | grep qos
# 查看实时连接数
watch -n1 'ss -s'
# 查看特定 IP 被拒绝的日志
grep -i "qos" /var/log/apache2/error.log | tail -20
如果发现 mod_qos 的日志没有输出,检查模块加载顺序:RemoteIPHeader 必须在 QS_ConnLimitPerIP 之前加载,因为 mod_qos 依赖请求的主机信息来判断 IP。
8. 总结
这套架构的关键不是堆配置,而是“连接级防护”和“真实 IP 还原”的配合。Nginx 管好入口频率,Apache 管好线程生命周期,再加上 mod_remoteip 的准确定位,云服务器才能在高并发下保持稳定。单个服务器如此,多台后面再接 LVS,同样适用。
记住:限流做不到“绝对禁止”,只能“把损失控制在你能承受的范围内”。上述配置已经在生产环境跑了一年,再也没有出现过“连接不够用”的告警。