当业务日活从一万激增到百万,单台服务器显然已无法招架。本文将以实际生产环境为背景,为你拆解如何一步步搭建起能支撑百万级并发的 Nginx 负载均衡与高可用架构体系。
一、为什么需要负载均衡?
1.1 业务增长带来的挑战
回忆一次真实的生产事故:某电商平台在去年双十一的流量洪峰中,其唯一的 Nginx 服务器直接宕机,导致了数百万订单的流失。事后复盘,暴露了几个核心问题:
- 单点故障:仅有一台 Nginx 作为入口,一旦故障,整个服务瘫痪。
- 性能瓶颈:单机性能受限于文件描述符、CPU、内存等硬件资源。
- 无法横向扩展:后端应用服务器可以轻松扩容,但流量入口却成了瓶颈,无法随之扩展。
1.2 负载均衡的核心价值
负载均衡正是为了解决上述问题而生的。先来看一个典型的架构示意图:
┌─────────────────────────────────────────────────────────┐
│ 用户请求 │
└─────────────────────┬───────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────┐
│ Nginx负载均衡层(多节点) │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ Nginx-1 │◄──►│ Nginx-2 │◄──►│ Nginx-3 │ Keepalived │
│ │ Master │ │ Backup │ │ Backup │ VRRP │
│ └────┬────┘ └────┬────┘ └────┬────┘ │
└───────┼──────────────┼──────────────┼───────────────────┘
│ │ │
└──────────────┼──────────────┘
▼
┌──────────────────────────────┐
│ 后端应用服务器集群 │
│ ┌─────┐ ┌─────┐ ┌─────┐ │
│ │App-1│ │App-2│ │App-3│ │
│ └─────┘ └─────┘ └─────┘ │
└──────────────────────────────┘
简单来说,负载均衡核心解决了三大难题:
| 问题 |
解决方案 |
效果 |
| 单点故障 |
多Nginx节点 + Keepalived |
故障自动切换,实现99.99%高可用 |
| 性能瓶颈 |
横向扩展Nginx节点 |
支持百万级并发连接 |
| 流量不均 |
智能调度算法(如加权轮询、最少连接) |
后端负载均衡,最大化资源利用率 |
二、Nginx负载均衡核心配置
2.1 基础负载均衡配置
一切始于一个基础的 upstream 配置块。下面是一个包含健康检查参数的完整示例:
# /etc/nginx/nginx.conf
upstream backend_servers {
# 加权轮询(默认算法)
server 192.168.1.10:8080 weight=5;
server 192.168.1.11:8080 weight=3;
server 192.168.1.12:8080 weight=2;
# 健康检查与长连接优化参数
keepalive 32; # 到每个后端服务器的长连接池大小
keepalive_timeout 30s; # 长连接超时时间
keepalive_requests 1000; # 单个长连接上可处理的最大请求数
}
server {
listen 80;
server_name api.example.com;
location / {
proxy_pass http://backend_servers;
# 传递必要的客户端信息到后端
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# 超时控制,防止慢后端拖死Nginx
proxy_connect_timeout 5s;
proxy_send_timeout 10s;
proxy_read_timeout 30s;
}
}
2.2 七种负载均衡算法详解
Nginx 提供了多种调度算法,该怎么选择呢?
upstream backend {
# 1. 轮询(Round Robin)- 默认算法,请求依次分发
server 192.168.1.10:8080;
server 192.168.1.11:8080;
server 192.168.1.12:8080;
}
upstream backend_weighted {
# 2. 加权轮询 - 根据服务器性能分配权重,性能高的多承担流量
server 192.168.1.10:8080 weight=5; # 高性能服务器
server 192.168.1.11:8080 weight=3;
server 192.168.1.12:8080 weight=2; # 低性能服务器
}
upstream backend_ip_hash {
# 3. IP哈希 - 同一客户端IP的请求固定到同一后端(用于会话保持)
ip_hash;
server 192.168.1.10:8080;
server 192.168.1.11:8080;
}
upstream backend_least_conn {
# 4. 最少连接 - 将新请求分配到当前连接数最少的服务器
least_conn;
server 192.168.1.10:8080;
server 192.168.1.11:8080;
server 192.168.1.12:8080;
}
upstream backend_fair {
# 5. 公平调度(第三方模块nginx-upstream-fair)- 根据后端响应时间分配
fair;
server 192.168.1.10:8080;
server 192.168.1.11:8080;
}
upstream backend_url_hash {
# 6. URL哈希(Nginx自带) - 相同URL的请求分配到同一后端,提高缓存命中率
hash $request_uri consistent;
server 192.168.1.10:8080;
server 192.168.1.11:8080;
}
upstream backend_sticky {
# 7. 基于Cookie的会话保持(第三方模块nginx-sticky-module)
sticky cookie srv_id expires=1h domain=.example.com path=/;
server 192.168.1.10:8080;
server 192.168.1.11:8080;
}
算法选择指南:
| 场景 |
推荐算法 |
说明 |
| 普通Web服务 |
加权轮询 |
简单高效,适合无状态服务 |
| 需要会话保持 |
IP哈希 / Cookie粘性 |
确保用户会话数据的一致性 |
| 长连接服务 |
最少连接 |
WebSocket、API长轮询等场景 |
| 缓存服务 |
URL哈希 |
将相同资源请求定向到同一服务器,提高缓存命中率 |
| 后端性能差异大 |
加权轮询 |
根据服务器CPU、内存配置调整权重 |
2.3 高级健康检查配置
被动的失败标记不够及时?Nginx 可以通过第三方模块实现主动健康检查。
upstream backend {
server 192.168.1.10:8080 max_fails=3 fail_timeout=30s;
server 192.168.1.11:8080 max_fails=3 fail_timeout=30s;
server 192.168.1.12:8080 backup; # 备用服务器,平时不接收流量
# 主动健康检查(需要编译安装nginx_upstream_check_module)
check interval=3000 rise=2 fall=3 timeout=1000 type=http;
check_http_send "GET /health HTTP/1.0\r\n\r\n";
check_http_expect_alive http_2xx http_3xx;
}
server {
location /status {
check_status; # 查看健康检查状态页面
access_log off;
allow 192.168.1.0/24; # 限制内网访问
deny all;
}
}
健康检查参数说明:
| 参数 |
说明 |
推荐值 |
max_fails |
在 fail_timeout 时间内,失败次数达到此值,标记服务器不可用 |
3 |
fail_timeout |
失败时间窗口,同时也是服务器被标记为不可用的持续时间 |
30s |
interval |
主动健康检查的间隔时间 |
3000ms |
rise |
从失败状态恢复为健康状态所需连续成功的检查次数 |
2 |
fall |
从健康状态判定为失败状态所需连续失败的检查次数 |
3 |
三、Nginx高可用架构实战
单台 Nginx 做负载均衡,自己又成了单点。如何让负载均衡器本身也高可用?
3.1 Keepalived + Nginx 双机热备
利用 Keepalived 的 VRRP 协议,实现虚拟 IP (VIP) 在多台 Nginx 服务器间的漂移。
┌─────────────┐
│ 用户请求 │
└──────┬──────┘
│
┌──────▼──────┐
│ Virtual IP │ 192.168.1.100
│ (VIP) │
└──────┬──────┘
│
┌───────────────┼───────────────┐
│ │ │
┌──────▼──────┐ ┌──────▼──────┐ ┌──────▼──────┐
│ Nginx-Master│ │ Nginx-Backup│ │ Nginx-Backup│
│ 192.168.1.10│ │ 192.168.1.11│ │ 192.168.1.12│
│ Priority │ │ Priority │ │ Priority │
│ 100 │ │ 90 │ │ 80 │
└─────────────┘ └─────────────┘ └─────────────┘
Master节点配置 (/etc/keepalived/keepalived.conf):
global_defs {
router_id NGINX_MASTER
script_user root
enable_script_security
}
# VRRP脚本:检测Nginx是否存活
vrrp_script check_nginx {
script "/etc/keepalived/check_nginx.sh"
interval 2
weight -20 # 检测失败时优先级降低20
fall 3 # 连续3次失败才判定为失败
rise 2 # 连续2次成功才恢复
}
vrrp_instance VI_1 {
state MASTER
interface eth0
virtual_router_id 51
priority 100
advert_int 1
authentication {
auth_type PASS
auth_pass 1234
}
virtual_ipaddress {
192.168.1.100/24 dev eth0 label eth0:vip
}
track_script {
check_nginx
}
# 状态切换通知脚本
notify_master "/etc/keepalived/notify.sh master"
notify_backup "/etc/keepalived/notify.sh backup"
notify_fault "/etc/keepalived/notify.sh fault"
}
Backup节点配置(关键部分,其余同Master):
vrrp_instance VI_1 {
state BACKUP
interface eth0
virtual_router_id 51
priority 90 # 优先级低于Master
advert_int 1
... # 认证、VIP、脚本跟踪配置与Master相同
}
Nginx进程检测脚本 (/etc/keepalived/check_nginx.sh):
#!/bin/bash
# 检测Nginx进程是否存在
count=$(ps -ef | grep nginx | grep -v grep | wc -l)
if [ $count -q 0 ]; then
# 尝试启动Nginx
/usr/sbin/nginx
sleep 2
# 再次检测
count=$(ps -ef | grep nginx | grep -v grep | wc -l)
if [ $count -eq 0 ]; then
# 启动失败,停止Keepalived触发VIP切换
systemctl stop keepalived
exit 1
fi
fi
exit 0
记得给脚本执行权限:chmod +x /etc/keepalived/check_nginx.sh
3.2 状态通知脚本
故障切换了,运维怎么能不知道?一个集成告警的通知脚本至关重要。
#!/bin/bash
# /etc/keepalived/notify.sh
TYPE=$1
HOST=$(hostname)
DATE=$(date '+%Y-%m-%d %H:%M:%S')
VIP="192.168.1.100"
# 发送企业微信告警函数
send_wechat_alert() {
local msg="$1"
curl -s "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=YOUR_KEY" \
-H 'Content-Type: application/json' \
-d "{\"msgtype\": \"text\", \"text\": {\"content\": \"$msg\"}}"
}
case $TYPE in
master)
logger "Keepalived: $HOST switched to MASTER, VIP $VIP acquired"
send_wechat_alert "🟢 Nginx高可用切换\n时间: $DATE\n节点: $HOST\n事件: 提升为MASTER,接管VIP $VIP"
;;
backup)
logger "Keepalived: $HOST switched to BACKUP, VIP $VIP released"
send_wechat_alert "🟡 Nginx高可用切换\n时间: $DATE\n节点: $HOST\n事件: 降级为BACKUP,释放VIP $VIP"
;;
fault)
logger "Keepalived: $HOST entered FAULT state"
send_wechat_alert "🔴 Nginx高可用告警\n时间: $DATE\n节点: $HOST\n事件: 进入FAULT状态"
;;
esac
3.3 四层负载均衡(LVS + Nginx)架构
面对超大规模流量,可以在 Nginx 之前再部署一层 LVS(四层负载),形成 LVS + Nginx 的分层架构。LVS 负责高性能的流量分发,Nginx 则处理更复杂的七层逻辑。
┌─────────────┐
│ 用户请求 │
└──────┬──────┘
│
┌──────▼──────┐
│ LVS-DR模式 │ 192.168.1.100 (VIP)
│ Director │
└──────┬──────┘
│
┌───────────────────┼───────────────────┐
│ │ │
┌──────▼──────┐ ┌──────▼──────┐ ┌──────▼──────┐
│ Nginx-Real1 │ │ Nginx-Real2 │ │ Nginx-Real3 │
│ 192.168.1.10│ │ 192.168.1.11│ │ 192.168.1.12│
│ lo:0 VIP │ │ lo:0 VIP │ │ lo:0 VIP │
└─────────────┘ └─────────────┘ └─────────────┘
LVS Director(调度器)配置脚本:
#!/bin/bash
# LVS-DR模式配置脚本
VIP=192.168.1.100
RIP1=192.168.1.10
RIP2=192.168.1.11
RIP3=192.168.1.12
# 加载ip_vs模块
modprobe ip_vs
modprobe ip_vs_rr
# 配置VIP
ip addr add $VIP/24 dev eth0
# 添加虚拟服务(使用wrr加权轮询调度算法)
ipvsadm -A -t $VIP:80 -s wrr
# 添加真实服务器,-g表示DR模式,-w设置权重
ipvsadm -a -t $VIP:80 -r $RIP1:80 -g -w 5
ipvsadm -a -t $VIP:80 -r $RIP2:80 -g -w 3
ipvsadm -a -t $VIP:80 -r $RIP3:80 -g -w 2
# 保存配置
ipvsadm-save > /etc/sysconfig/ipvsadm
echo "LVS-DR配置完成"
ipvsadm -Ln
Real Server(真实Nginx服务器)配置脚本(每台都需要执行):
#!/bin/bash
# RealServer配置脚本
VIP=192.168.1.100
# 在回环接口上配置VIP,用于接收目标为VIP的数据包
ip addr add $VIP/32 dev lo:0
# 抑制ARP响应,防止Real Server抢答Director的VIP
echo 1 > /proc/sys/net/ipv4/conf/lo/arp_ignore
echo 2 > /proc/sys/net/ipv4/conf/lo/arp_announce
echo 1 > /proc/sys/net/ipv4/conf/all/arp_ignore
echo 2 > /proc/sys/net/ipv4/conf/all/arp_announce
echo "RealServer配置完成"
四、实战案例:电商大促架构配置
4.1 架构设计
电商大促时,流量类型复杂,需要针对性处理。通常采用动静分离与分层负载的策略。
CDN
│
┌──────────┴──────────┐
│ │
┌─────▼─────┐ ┌─────▼─────┐
│ 静态资源 │ │ 动态API │
│ Nginx │ │ Nginx │
└─────┬─────┘ └─────┬─────┘
│ │
┌───────────┴───────────┐ │
│ │ │
┌────▼────┐ ┌────▼────┐ │
│ 图片/JS │ │ 负载均衡 │◄───┘
│ 缓存 │ │ 集群 │
└─────────┘ └────┬────┘
│
┌────────────────┼────────────────┐
│ │ │
┌─────▼─────┐ ┌─────▼─────┐ ┌─────▼─────┐
│ 订单服务 │ │ 商品服务 │ │ 用户服务 │
│ Cluster │ │ Cluster │ │ Cluster │
└───────────┘ └───────────┘ └───────────┘
4.2 核心配置示例
静态资源Nginx配置(侧重缓存与压缩):
server {
listen 80;
server_name static.example.com;
# 开启gzip压缩
gzip on;
gzip_types text/css application/javascript image/svg+xml;
gzip_min_length 1k;
location /images/ {
alias /data/static/images/;
expires 30d;
add_header Cache-Control "public, immutable";
# 可选:开启图片实时裁剪处理
image_filter resize $arg_w $arg_h;
image_filter_buffer 10M;
}
location ~* \.(css|js)$ {
root /data/static/;
expires 1y;
add_header Cache-Control "public, immutable";
# 开启更高效的Brotli压缩
brotli on;
brotli_types text/css application/javascript;
}
}
动态API Nginx配置(侧重限流、熔断与高可用):
upstream api_backend {
least_conn; # 大促期间,使用最少连接算法更公平
server 10.0.1.10:8080 weight=5 max_fails=2 fail_timeout=10s;
server 10.0.1.11:8080 weight=5 max_fails=2 fail_timeout=10s;
server 10.0.1.12:8080 weight=5 max_fails=2 fail_timeout=10s;
server 10.0.1.13:8080 backup; # 备用节点,平时不参与负载
keepalive 100; # 增大长连接池
keepalive_timeout 60s;
}
server {
listen 80;
server_name api.example.com;
# 限流配置(大促关键!)
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=100r/s;
limit_conn_zone $binary_remote_addr zone=addr:10m;
location /api/ {
limit_req zone=api_limit burst=200 nodelay;
limit_conn addr 50;
proxy_pass http://api_backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
# 大促期间缩短超时,快速失败,避免雪崩
proxy_connect_timeout 3s;
proxy_send_timeout 5s;
proxy_read_timeout 10s;
# 自定义错误页面
proxy_intercept_errors on;
error_page 502 503 504 /50x.html;
}
location /api/order/ {
# 订单接口使用更严格的限流策略
limit_req zone=api_limit burst=50 nodelay;
proxy_pass http://api_backend;
# 订单接口响应要求更高,超时设置更短
proxy_read_timeout 5s;
}
location = /50x.html {
root /usr/share/nginx/html;
internal;
}
}
4.3 大促前的Nginx健康检查Checklist
上线前,用这个脚本快速检查你的 Nginx 负载均衡层是否准备就绪。
#!/bin/bash
# 大促前Nginx检查脚本
echo "========== Nginx大促前检查 =========="
# 1. 检查Nginx配置语法
echo "[1/8] 检查配置语法..."
nginx -t || exit 1
# 2. 检查连接数限制
echo "[2/8] 检查系统连接数限制..."
ULIMIT=$(ulimit -n)
if [ $ULIMIT -lt 65535 ]; then
echo "⚠️ 警告: 文件描述符限制过低: $ULIMIT,建议设置为65535+"
else
echo "✅ 文件描述符限制: $ULIMIT"
fi
# 3. 检查Nginx Worker进程配置
echo "[3/8] 检查Worker进程..."
WORKER_PROCESSES=$(grep "worker_processes" /etc/nginx/nginx.conf | grep -v "#" | awk '{print $2}' | tr -d ';')
CPU_CORES=$(nproc)
if [ "$WORKER_PROCESSES" != "auto" ] && [ $WORKER_PROCESSES -lt $CPU_CORES ]; then
echo "⚠️ 警告: worker_processes ($WORKER_PROCESSES) 小于CPU核心数 ($CPU_CORES)"
else
echo "✅ Worker进程配置正常"
fi
# 4. 检查后端健康状态
echo "[4/8] 检查后端健康状态..."
curl -s http://localhost/status 2>/dev/null || echo "⚠️ 未配置状态检查页面"
# 5. 检查Keepalived状态
echo "[5/8] 检查Keepalived状态..."
systemctl is-active keepalived > /dev/null && echo "✅ Keepalived运行中" || echo "⚠️ Keepalived未运行"
# 6. 检查日志磁盘空间
echo "[6/8] 检查日志空间..."
LOG_USAGE=$(df /var/log/nginx | tail -1 | awk '{print $5}' | tr -d '%')
if [ $LOG_USAGE -gt 80 ]; then
echo "⚠️ 警告: 日志目录使用率 ${LOG_USAGE}%"
else
echo "✅ 日志空间充足: ${LOG_USAGE}%"
fi
# 7. 检查SSL证书有效期
echo "[7/8] 检查SSL证书..."
for cert in /etc/nginx/ssl/*.crt; do
if [ -f "$cert" ]; then
EXPIRY=$(openssl x509 -in "$cert" -noout -dates | grep notAfter | cut -d= -f2)
EXPIRY_EPOCH=$(date -d "$EXPIRY" +%s)
NOW_EPOCH=$(date +%s)
DAYS_LEFT=$(( (EXPIRY_EPOCH - NOW_EPOCH) / 86400 ))
if [ $DAYS_LEFT -lt 30 ]; then
echo "⚠️ 证书 $cert 将在 $DAYS_LEFT 天后过期"
else
echo "✅ 证书 $cert 有效期剩余 $DAYS_LEFT 天"
fi
fi
done
# 8. 压测命令准备
echo "[8/8] 压测命令准备..."
echo " wrk -t12 -c400 -d30s http://your-api-endpoint"
echo " ab -n 100000 -c 1000 http://your-api-endpoint"
echo "========== 检查完成 =========="
五、常见生产问题与解决方案
5.1 502 Bad Gateway
这是最常见的问题之一,意味着 Nginx 无法从后端服务器收到有效响应。
# 问题排查步骤
# 1. 检查后端服务是否存活
curl -I http://backend-server:8080/health
# 2. 查看Nginx错误日志,定位具体原因
tail -f /var/log/nginx/error.log | grep 502
# 3. 常见原因及解决
# 原因1: 后端应用连接池耗尽或进程崩溃
# 解决: 检查后端应用日志,考虑扩容或优化连接池配置。
# 原因2: 防火墙或安全组规则拦截
# 解决: 检查服务器本地防火墙及云平台安全组。
iptables -L -n | grep 8080
# 原因3: Nginx到后端的网络超时设置过短
# 解决: 适当调整 `proxy_read_timeout`, `proxy_connect_timeout`。
5.2 会话保持(Session Stickiness)失效
在集群环境下,确保用户会话不丢失是关键。
# 方案1: IP哈希(简单但可能导致负载不均)
upstream backend {
ip_hash;
server 192.168.1.10:8080;
server 192.168.1.11:8080;
}
# 方案2: Cookie粘性(需第三方模块,推荐)
upstream backend {
server 192.168.1.10:8080;
server 192.168.1.11:8080;
sticky cookie srv_id expires=1h domain=.example.com path=/;
}
# 方案3: 共享Session存储(最佳实践,实现后端无状态化)
# 使用Redis、Memcached等集中存储Session,彻底解决会话保持问题。
5.3 性能调优核心参数速查
一份整理好的 nginx.conf 优化配置片段。
# /etc/nginx/nginx.conf
user nginx;
worker_processes auto; # 自动设置为CPU核心数
worker_rlimit_nofile 65535; # 提高每个worker进程能打开的文件描述符上限
events {
worker_connections 65535; # 每个worker进程允许的最大并发连接数
use epoll; # Linux高性能I/O模型
multi_accept on; # 一次性接受所有新连接
}
http {
# 长连接优化,减少TCP握手开销
keepalive_timeout 60s;
keepalive_requests 10000;
# 文件传输优化
sendfile on; # 启用高效文件传输模式
tcp_nopush on; # 在sendfile模式下,等数据包满后再发送,提高网络效率
tcp_nodelay on; # 禁用Nagle算法,小数据包即时发送,降低延迟
# 压缩优化
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_types text/plain text/css text/xml application/json application/javascript;
# 文件缓存优化,减少磁盘I/O
open_file_cache max=100000 inactive=20s;
open_file_cache_valid 30s;
open_file_cache_min_uses 2;
open_file_cache_errors on;
}
六、总结与演进路线
从单机到支撑千万日活的 高可用架构,是一个循序渐进的过程。本文梳理的路径可以概括如下:
| 架构层级 |
技术方案 |
适用场景 |
| 单机优化 |
优化worker_processes、连接数、系统参数 |
日活 < 10万 |
| 负载均衡 |
Nginx Upstream 多后端分发 |
日活 10万 - 100万 |
| 高可用 |
Keepalived + Nginx 主备切换 |
要求99.9% SLA |
| 超大规模 |
LVS(四层) + Nginx(七层) 分层集群 |
日活 > 1000万 |
最终的生产环境建议:
- 监控先行:部署如 Prometheus + Grafana 监控 Nginx 的 QPS、连接数、后端健康状态等关键指标。
- 日志集中:使用 ELK(Elasticsearch, Logstash, Kibana)或类似方案收集和分析 Nginx 访问日志与错误日志。
- 灰度发布:巧妙利用 Nginx 的
split_clients 模块或根据请求头进行流量切分,实现金丝雀发布,降低上线风险。
- 定期演练:主动模拟 Nginx 节点或后端服务故障,验证整个高可用切换流程是否顺畅,告警是否及时。
构建稳固的流量入口是业务稳定的基石。希望这份融合了实战配置与架构思想的指南,能帮助你搭建出真正扛得住流量洪峰的负载均衡系统。如果你在实践过程中遇到其他有趣的问题或解决方案,欢迎在 云栈社区 的后端与架构板块与大家交流分享。
本文基于 Nginx 1.24+ 版本编写,生产环境请根据实际版本测试调整配置参数。