单集群稳定支撑 80 万并发连接,CPU 长期维持在 30% 以下。这个结果并不是碰运气得来,而是把内核级负载分发和用户态灵活代理两套东西合理拼在一起,用 LVS 把大量连接快速分发到 Nginx 集群,再由 Nginx 处理复杂的路由和应用层逻辑。

先说最后的架构逻辑。最上层用 LVS 做四层分发,负责把到达 VIP 的 TCP 包以最小开销分散到多台 Nginx;Nginx 负责 TLS、路由、限流、缓存等七层工作,再把请求转给后端应用。这样的组合,把各自擅长的部分放到最合适的位置,既能承受百万级别的长连接,又能保证灵活的规则处理。看起来简单,落地有一堆细节要调整。
回到开始遇到的问题。业务上有两类压力:一类是长连接(列如长时间的 TCP 连接、WebSocket),数量很大;另一类是突发请求,且带有复杂的路由和鉴权规则。单纯把流量交给 Nginx 做全部事情,用户态的上下文切换、内核与用户态复制开销,在并发到几十万时会迅速吃掉 CPU。只靠 LVS,又缺乏七层的灵活性。于是决定把 LVS 放前面做大规模的连接分发,Nginx 做后面一层的智能处理。
架构部署的关键点先说清楚,然后再细讲各阶段的处理和配置调整。整体分三层:VIP/LVS 层 -> Nginx 层 -> 应用/业务层。LVS 使用 DR(Direct Routing)模式以减少转发开销,真实服务器(Nginx 节点)把 VIP 配到回环接口,但关闭 ARP 响应以避免与 LVS 冲突。Nginx 做 TLS 终结、layer7 路由、缓存和限流,必要时通过 keepalive 将连接复用到后端应用。
具体到 LVS 的配置和内核调整。常用的 ipvsadm 命令如下(示例):
– 在 VIP 机器上添加虚拟服务:ipvsadm -A -t 10.0.0.100:443 -s rr
– 添加真实服务器(DR 模式):ipvsadm -a -t 10.0.0.100:443 -r 10.0.0.11:443 -g -w 1
– 在真实服务器上开启 IP 转发:sysctl -w net.ipv4.ip_forward=1
DR 模式有几项必须在真实服务器上设置,否则会产生 ARP 冲突或路由问题。关键 sysctl:
– net.ipv4.conf.all.arp_ignore=1
– net.ipv4.conf.all.arp_announce=2
– net.ipv4.conf.lo.arp_ignore=1
– net.ipv4.conf.lo.arp_announce=2
这些设置让真实服务器在收到针对 VIP 的 ARP 请求时不主动回应,从而把流量留在 LVS 上分发。真实服务器需要把 VIP 配在回环接口上(lo),列如 ip addr add 10.0.0.100/32 dev lo,然后确保不会对外 ARP。
内核层面的优化也很重大。常见的调整包括:
– net.core.somaxconn 增大到 4096 或更高
– net.ipv4.tcp_tw_reuse=1 和 tcp_tw_recycle(现已不推荐)谨慎用
– net.ipv4.tcp_fin_timeout 调低以回收 TIME_WAIT
–
net.ipv4.tcp_max_syn_backlog 提升
这些能协助系统在海量并发下更快地分配端口和回收资源。
Nginx 的配置上,要同时思考高并发与复杂路由。关键指标要调:
– worker_processes 与 worker_connections 的乘积足够大
– 使用 epoll 模型:events { use epoll; }
– keepalive_timeout 合理设置,减少空连接占用
– reuseport 可以配合多个 worker 来改善 accept 抢占
– proxy_buffering、proxy_cache 做好缓存策略,减少后端压力
一个简化的 Nginx 配置片段(示例):
worker_processes auto;
events {
worker_connections 65536;
use epoll;
}
http {
proxy_buffer_size 16k;
proxy_buffers 4 32k;
proxy_busy_buffers_size 64k;
keepalive_timeout 30s;
proxy_connect_timeout 5s;
proxy_read_timeout 60s;
limit_conn_zone $binary_remote_addr zone=addr:10m;
proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=mycache:10m max_size=10g inactive=60m;
upstream backend {
server 10.0.0.21:8080 max_fails=3 fail_timeout=10s;
keepalive 64;
}
server {
listen 443 ssl;
ssl_certificate /etc/ssl/cert.pem;
ssl_certificate_key /etc/ssl/key.pem;
location / {
limit_conn addr 10;
proxy_pass http://backend;
proxy_http_version 1.1;
proxy_set_header Connection “”;
}
}
}
这些设置让 Nginx 在处理大量短请求和复杂规则时更稳。
性能对比与压测是决策的硬证据。我们做了同一套硬件下的对比测试:在小流量(千级并发)场景,Nginx 单干和 LVS+Nginx 的差别并不明显;当并发攀升到十万量级,Nginx 单机的用户态开销变得显著,CPU 与内存压力迅速上升;而引入 LVS 后,内核直接分发连接,Nginx 的压力被均摊,延迟和 CPU 占用都降低。从观测数据看,在高并发的长连接场景下,LVS 带来的优势明显——这是由于内核层面处理包的开销比用户态小许多。
面对百万级别的长连接,我们采取了以下应对措施:
– 把尽可能多的连接保持在 LVS 到 Nginx 的分发层,减少 Nginx 与后端之间的频繁握手。
– 在 Nginx 与后端之间启用长连接复用(keepalive),降低后端建立连接的频率。
– 在后端应用层增加连接池与异步处理,避免同步阻塞造成资源堆积。
最终效果是单个集群可以稳定支撑 80 万并发连接,且观察期内 CPU 使用率低于 30%。这是多方面优化积累的结果,不是某一条神奇配置。
突发流量与复杂路由同时出现时,LVS 继续做四层分发,Nginx 变成策略中心。实战中遇到的问题有:
– 一台 Nginx 节点在短时间内被命中大量规则,导致处理时间增长。解决方法是把规则做差分:把最频繁的路由用更简单的匹配方式(列如基于 Host 的快速路由),把复杂逻辑推到后端或用 Lua 做部分预处理。
– TLS 握手压力聚焦在少数几台机上。解决方法是在更多 Nginx 节点上开启 TLS,或者使用硬件/用户态加速(列如 xdp、bpf、或专用卡)来分散握手开销。
– 状态不一致:在做限流/黑名单时,要把共享状态放到外部存储(Redis)或让 Nginx 使用一致性哈希把同一用户固定到一组后端,减少跨节点一致性问题。
在实际部署中,也会碰到 ARP、路由和回环接口的细节坑。有一次测试中,部分真实服务器在 DR 配置下还是回应了 VIP 的 ARP,使得 LVS 分发不均匀。追查后发现是 lo 接口的 arp_ignore 没全部生效,后来把 lo 上也设置了 arp_announce=2 和 arp_ignore=1,问题解决。还有一次由于 net.ipv4.ip_forward 未打开,返回包被本地路由走掉,造成连接中断。这样的细节在 DR 模式下尤其容易被忽视。
把 LVS 和 Nginx 组合起来,并不是把任务简单切块就行。关键在于配合:LVS 负责高效把包打到合适的 Nginx;Nginx 再把请求用适合的策略交给后端。现实里还得照顾运维便利:监控、告警、健康检查要覆盖 LVS、Nginx 和后端;配置下发和回滚要能快速恢复;容量扩缩容需要有自动化脚本,避免手工调整成瓶颈。
最实用的三点经验(不做总结,只给几个可直接用的做法):
– 把四层的连接分发交给内核级的 LVS,七层的规则交给 Nginx,这样两者的优势互补。
– DR 模式下,把 VIP 配到回环接口并关闭 ARP 响应;别忘了 ip_forward。
– 针对长连接和突发场景同时优化:keepalive、内核端口回收、Nginx 缓存与限流、后端异步化。
整个改造过程花了不少迭代的时间,每次压测都会暴露新的边界条件。调整不是一次性的,跟着业务形态变,参数也要跟着变。实际运维里会碰到各种各样的小问题,仔细调好网络栈和进程模型,许多卡顿就能避免。最后,按场景选工具,不要把任何一套方案当成灵丹妙药。