为什么需要这篇文档?
很多公司的架构演进会经历这样一个过程:
阶段一:单机部署,nginx+ 后端服务都在一台服务器。某天后端服务挂了,网站打不开;或者流量突增,服务器扛不住了。
阶段二:开始拆分,nginx单独一台,后端多台部署,用 upstream 做负载均衡。解决了单点故障和性能瓶颈,但发现 nginx自己成了新的单点。
阶段三:nginx也做高可用,Keepalived + VIP,主备切换。终于有了完整的高可用架构,但又遇到新问题:切换时连接中断、灰度发布怎么做、如何动态调整后端…
这里不讲零散的概念,看完希望能有以下收获:
- 根据业务场景选择合适的负载均衡算法
- 设计高可用架构并平滑处理故障切换
- 实现灰度发布、蓝绿部署等高级流量治理
1、负载均衡核心原理
1.1 四层负载均衡 vs 七层负载均衡
很多人分不清 nginx的负载均衡工作在哪个层面:
| 层面 | 协议 | 特点 | 适用场景 |
|---|---|---|---|
| 四层代理 | TCP/UDP | 基于 IP + 端口转发,性能高 | 数据库、Redis、RPC 服务 |
| 七层代理 | HTTP/HTTPS | 基于 URL/Header/Cookie 转发,功能丰富 | Web 应用、API 网关 |
nginx同时支持两者:
# 七层负载均衡(HTTP) http { upstream backend { server 10.0.0.1:8080; server 10.0.0.2:8080; } server { listen 80; location / { proxy_pass http://backend; } } } # 四层负载均衡(TCP) stream { upstream mysql_backend { server 10.0.0.1:3306; server 10.0.0.2:3306; } server { listen 3306; proxy_pass mysql_backend; } }选择建议:
- HTTP/HTTPS 服务用七层,可以基于 URL 做精细化路由
- 数据库、缓存等 TCP 服务用四层,性能损耗小
1.2 负载均衡算法深度解析
nginx支持多种负载均衡算法,每种都有特定的适用场景:
1.2.1 轮询(round-robin)- 默认算法
upstream backend { server 10.0.0.1:8080 weight=3; # 权重3 server 10.0.0.2:8080 weight=2; # 权重2 server 10.0.0.3:8080 weight=1; # 权重1 # 请求按 3:2:1 的比例分发 }原理:按照权重轮流分发请求适用:后端服务器配置相同,处理能力不同时用权重调整
优点:简单公平缺点:不考虑后端实际负载
1.2.2 IP哈希(ip_hash)
upstream backend { ip_hash; server 10.0.0.1:8080; server 10.0.0.2:8080; }原理:对客户端 IP 做哈希计算,相同 IP 永远分配到同一台后端适用:需要会话保持的应用(无共享 session)
优点:解决 session 问题缺点:负载可能不均(某个 IP 段请求多)
高级玩法:用hash指令实现更灵活的会话保持
# 根据请求参数保持会话 upstream backend { hash $arg_session_id consistent; server 10.0.0.1:8080; server 10.0.0.2:8080; } # 根据 cookie 保持会话 upstream backend { hash $cookie_userid consistent; server 10.0.0.1:8080; server 10.0.0.2:8080; }1.2.3 最少连接(least_conn)
upstream backend { least_conn; server 10.0.0.1:8080; server 10.0.0.2:8080; }原理:转发给当前活跃连接数最少的后端适用:请求处理时间差异很大的场景(如大文件下载、长轮询)
优点:真正的负载均衡,考虑实际负载缺点:需要维护连接状态
一致性哈希(hash consistent)
upstream backend { hash $request_uri consistent; server 10.0.0.1:8080; server 10.0.0.2:8080; server 10.0.0.3:8080; }原理:使用一致性哈希算法,节点变化时只影响少量请求适用:缓存服务、分布式存储
优点:扩缩容时缓存命中率高缺点:实现复杂
1.3 负载均衡算法选型矩阵
| 业务场景 | 推荐算法 | 原因 |
|---|---|---|
| 无状态微服务 | 轮询 + keepalive | 简单高效 |
| 有状态应用(无分布式session) | IP哈希 | 保持会话 |
| 大文件下载/视频流 | 最少连接 | 避免慢连接堆积 |
| API网关 | 轮询 | 通常无状态 |
| 缓存集群 | 一致性哈希 | 扩缩容影响小 |
| gRPC服务 | 最少连接 | 长连接场景 |
2、健康检查机制
2.1 被动健康检查
被动式、传输层的健康检查(Nginx 原生)。它只能知道后端“连不上”,不知道后端“业务挂了”。
upstream backend { server 10.0.0.1:8080 max_fails=3 fail_timeout=30s; server 10.0.0.2:8080 max_fails=3 fail_timeout=30s; } # nginx 不会主动发请求,当真实用户请求进来,Nginx 尝试连接 10.0.0.1。 # 如果连接失败(拒绝连接、超时)或返回了特定的错误(默认是 502/503/504 等),计数器 +1。 # 当连续失败3次 (max_fails=3),Nginx 将该节点标记为“不可用”,在接下来的30秒(fail_timeout=30s) 内不再转发任何请求给它。 # 30秒后,Nginx 会尝试“试探性”地转发一个新请求给它,如果成功则恢复,失败则重新计时。2.2 主动健康检查
主动式、应用层的健康检查(需要第三方模块,如nginx_upstream_check_module)。它能真正探测后端业务是否存活。
upstream backend { server 10.0.0.1:8080; server 10.0.0.2:8080; # 主动健康检查 # check interval=3000 rise=2 fall=5 timeout=5000 type=http; check_http_send "HEAD /test/status.jsp HTTP/1.0\r\n\r\n"; check_http_expect_alive http_2xx http_3xx; } # nginx每隔3秒向后端服务器发送一个 HEAD 请求,询问/test/status.jsp。 # 连续 2 次 (rise=2) 收到 2xx 或 3xx 状态码,标记为“健康”。 # 连续 5 次 (fall=5) 超时或状态码不对,标记为“不健康”,立即停止转发流量。3、高可用架构设计
3.1 为什么要做高可用?
单点故障场景:
- nginx 服务器宕机 → 所有业务中断
- nginx 进程崩溃 → 服务不可用
- 机房网络故障 → 区域无法访问
高可用目标:
- 故障自动切换(< 5秒)
- 数据不丢失
- 对客户端透明
3.2 Keepalived + Nginx 主备模式
这是最常见的高可用方案:
虚拟IP:192.168.1.100 ↓ ┌────┴───┐ ↓ ↓ Master Backup 192.168.1.10 192.168.1.11 ↓ ↓ Nginx Nginx ↓ ↓ 后端服务 后端服务3.2.1 Keepalived 配置
主节点配置(/etc/keepalived/keepalived.conf):
global_defs { router_id nginx_master } vrrp_script check_nginx { script "/etc/keepalived/check_nginx.sh" # 检查脚本 interval 2 weight -20 fall 2 rise 1 } 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 } track_script { check_nginx } notify_master "/etc/keepalived/master.sh" notify_backup "/etc/keepalived/backup.sh" notify_fault "/etc/keepalived/fault.sh" }备节点配置:
global_defs { router_id nginx_backup } vrrp_script check_nginx { script "/etc/keepalived/check_nginx.sh" interval 2 weight -20 } vrrp_instance VI_1 { state BACKUP interface eth0 virtual_router_id 51 priority 90 # 比主节点低 advert_int 1 authentication { auth_type PASS auth_pass 1234 } virtual_ipaddress { 192.168.1.100/24 dev eth0 } track_script { check_nginx } }健康检查脚本(/etc/keepalived/check_nginx.sh):
#!/bin/bash # 检查 Nginx 是否存活 if pgrep nginx > /dev/null; then # 再检查端口是否监听 if netstat -tnlp | grep :80 > /dev/null; then exit 0 fi fi # 尝试重启 systemctl restart nginx sleep 2 if pgrep nginx > /dev/null; then exit 0 else exit 1 fi3.3 注意点:ARP 缓存问题
主备切换后,客户端和交换机的 ARP 缓存可能还是旧 MAC 地址,导致流量依然发往故障节点。
解决方案:
# 主节点切换时发送 gratuitous ARP vrrp_instance VI_1 { # ... 其他配置 # 切换时发送 5 个 gratuitous ARP garp_master_delay 5 garp_master_repeat 5 # 主动刷新 ARP notify_master "/etc/keepalived/refresh_arp.sh" } # 【关键参数 1】garp_master_delay 5 # 含义:成为 Master 后,等待 5 秒 再开始发送 ARP 广播。 # 作用:给系统一点时间让网卡完全启动、IP 地址完全绑定好。 # 风险:如果设得太小(如 0),可能 IP 还没绑好就发 ARP,交换机学不到;设得太大,切换延迟增加。 # 建议:通常 2-5 秒是安全的,但在极速切换场景下可改为 0 或 1。 # 【关键参数 2】garp_master_repeat 5 # 含义:连续发送 5 次 免费 ARP (Gratuitous ARP) 包。 # 作用:防止丢包。发一次可能丢了,发 5 次确保交换机一定能收到并更新 MAC 地址表。 # 【关键参数 3】notify_master "/etc/keepalived/refresh_arp.sh" # 含义:当状态变为 MASTER 时,执行这个脚本。 # 作用:执行自定义的“暴力刷新”逻辑,作为内置 ARP 发送的补充。#!/bin/bash # notify_master.sh - 刷新 ARP 缓存 VIP="192.168.1.100" INTERFACE="eth0" # 记录日志 echo "Keepalived: Becoming MASTER, sending gratuitous ARP for $VIP" # 1. 发送免费 ARP (核心动作) # 循环发送几次,确保交换机收到 for i in {1..5}; do arping -U -I $INTERFACE -c 1 $VIP sleep 0.1 # 间隔 100ms,避免发包太快被交换机限速丢弃 done # 如果确实发现本机访问网关有问题,可以只刷新网关的 ARP exit 04、高级流量治理
4.1 灰度发布(金丝雀发布)
场景:新版本只让 10% 的流量验证,没问题再全量。
4.1.1 基于权重
upstream backend { server 10.0.0.1:8080 weight=90; # 旧版本 90% server 10.0.0.2:8080 weight=10; # 新版本 10% }缺点:权重调整需要 reload,连接会断开
4.1.2 基于 Cookie/Header
http { upstream new_backend { server 10.0.0.1:8080; } upstream old_backend { server 10.0.0.2:8080; } # 假设 cookie 名为 "user_id" split_clients "${cookie_user_id}" $variant_ratio { 10% "new"; * "old"; } # 或者使用其他标识,比如 IP 地址 # split_clients "$remote_addr" $variant_ratio { # 10% "new"; # * "old"; # } # 根据 header 覆盖灰度结果 map $http_canary $final_variant { default $variant_ratio; "enable" "new"; } map $final_variant $upstream_name { "new" "new_backend"; "old" "old_backend"; } server { listen 80; location / { proxy_pass http://$upstream_name; } } }4.1.3基于 IP 段(内测用户优先)
geo $canary_group { default 0; 10.0.0.0/8 1; # 内网IP 192.168.1.0/24 1; # 公司IP段 114.212.33.0/24 1; # 特定外网IP } upstream backend { server 10.0.0.1:8080; # 旧版本 } upstream backend_canary { server 10.0.0.2:8080; # 新版本 } location / { if ($canary_group = 1) { proxy_pass http://backend_canary; break; } proxy_pass http://backend; }4.2 蓝绿部署
原理:两套环境(蓝和绿),通过切换流量实现无缝发布。
# 初始状态:蓝环境在线 upstream backend { server blue:8080; # 当前生产 server green:8080 down; # 备用 } # 部署新版本到 green # 测试验证后,切换流量 upstream backend { server blue:8080 down; # 下线蓝 server green:8080; # 上线绿 } # 切换只需要 reload,秒级完成 nginx -s reload # 有状态应用:如果用户的 Session 存储在 Blue 服务器的内存中,切换后用户跳转到 Green,会立即掉线(需要重新登录) # 共享会话:必须使用 Redis/Memcached 等外部存储共享 Session,不能存本地内存。4.3 A/B 测试
原理:利用 IP 算出一个固定的“标签”,再把“标签”翻译成“服务器组名”,最后让 nginx根据这个名字动态把请求扔过去。
http { upstream backend_v1 { server 10.0.0.1:8080; } upstream backend_v2 { server 10.0.0.2:8080; } # 基于 IP 分流 split_clients "$remote_addr" $ui_version { 50% "v2"; * "v1"; } # 使用 map 将版本映射到 upstream 名称 map $ui_version $backend_name { "v2" "backend_v2"; "v1" "backend_v1"; default "backend_v1"; # 防御性默认值 } server { listen 80; location / { proxy_pass http://$backend_name; # 添加调试头 add_header X-UI-Version $ui_version; add_header X-Backend $backend_name; # 标准 proxy 设置 proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } } }5、生产环境最佳实践
5.1 架构选型矩阵
| 规模 | 节点数 | 负载均衡 | 高可用方案 | upstream管理 |
|---|---|---|---|---|
| 初创期 | 1 | 无 | 无 | 静态配置 |
| 成长期 | 2-5 | nginx upstream | Keepalived主备 | 静态配置 |
| 发展期 | 5-20 | nginx + LVS | Keepalived双主 | 脚本动态更新 |
| 成熟期 | 20-100+ | 多层负载均衡 | DNS轮询+多活 | Consul + nginx-upsync |
5.2 配置规范
upstream 命名规范:
upstream service-product {} # 业务服务名 upstream service-order {} upstream service-user {} upstream gateway-api {} # API网关 upstream static-assets {} # 静态资源server 标签规范:
server 10.0.0.1:8080 weight=5 max_fails=3 fail_timeout=30s; # 生产 server 10.0.0.2:8080 backup; # 备用 server 10.0.1.1:8080 down; # 已下线5.3 容量规划
# 单机预估容量 worker_processes auto; # 通常等于 CPU 核心数 worker_connections 65535; # 每个 worker 最大连接数 # 理论最大并发连接数 = worker_processes * worker_connections # 例如:8核 * 65535 ≈ 50万并发连接 # 实际建议水位:最大值的 60-70% # 8核 * 65535 * 0.7 ≈ 35万连接负载均衡和高可用不是一蹴而就的,而是一个持续演进的过程:
- 阶段一:先做负载均衡,解决单点性能瓶颈
- 阶段二:再做高可用,解决 nginx 自身单点
- 阶段三:灰度发布,提升发布效率
- 阶段四:多活架构,实现真正的弹性伸缩