服务器负载均衡是什么?Nginx负载均衡配置与应用场景

一台服务器撑不住的时候,最自然的想法是加机器。但加完机器之后问题来了:用户访问的是同一个域名,凭什么这次请求去 A 机器、下次去 B 机器?这个"分配请求的组件"就是负载均衡器。

这篇文章从原理讲到落地:四层与七层负载均衡的区别到底在哪、Nginx 的 upstream 怎么配、五种调度算法各自适合什么场景、健康检查参数怎么设才不会误伤、会话保持为什么是应该尽量避免的方案、动静分离与 SSL 终结怎么落地、Nginx 做负载均衡与 HAProxy/LVS/云负载均衡的定位差异,以及最后一块——负载均衡器自己挂了怎么办(keepalived + VIP 的思路)。

一、负载均衡解决什么问题

负载均衡(Load Balancing)的核心职责是:把到达的请求按照某种规则分发到后端的多个服务节点,同时对外部隐藏后端的具体拓扑。它带来四个直接收益:

收益 说明
提升容量 请求被分散,整体处理能力随节点数近似线性增长
高可用 单个节点故障时自动摘除,请求被转发到健康节点
可维护性 可以逐台下线做发布和维护,不影响整体服务
弹性 业务高峰时增加节点、低谷时减少,按需调整

需要一开始就明确的是"负载均衡"不等于"高可用"。负载均衡器本身是单点——所有流量都经过它,它挂了整个服务就不可用了。所以完整的方案必须包含负载均衡器自身的冗余(第七节详述)。

几个容易混淆的概念:

  • LB(Load Balancer):泛指负载均衡的软件、硬件或服务。
  • 反向代理(Reverse Proxy):代表服务端接收请求的代理。负载均衡通常基于反向代理实现,但反向代理的功能不限于负载均衡(还有缓存、SSL 终结、压缩、鉴权等)。
  • Upstream / Backend / Real Server:都指后端的真实服务节点,只是不同软件的叫法。Nginx 叫 upstream,HAProxy 叫 backend,LVS 文档里常叫 RS。

二、四层与七层的本质区别

这是选型时最关键的一个分叉点。所谓"四层"是 OSI 模型的传输层(TCP/UDP),"七层"是应用层(HTTP/HTTPS 等)。

对比项 四层负载均衡(L4) 七层负载均衡(L7)
工作层次 TCP/UDP,看 IP + 端口 + 协议 HTTP/HTTPS,看 URL、Header、Cookie、内容
转发依据 目标地址、源地址、端口 URL 路径、域名、Header、Cookie、请求方法
是否解析内容 不解析,只改写地址后转发 完整解析,可按内容做路由决策
性能 极高,接近线速,CPU 开销小 较高开销,但现代软件(Nginx)也能达到很高吞吐
支持的并发 百万级(DR 模式下几乎无上限) 通常数万到数十万
功能 只有转发 动静分离、SSL 终结、压缩、重写、限流、灰度、WAF
典型实现 LVS(DR/NAT/TUN 模式)、云厂商的 CLB/NLB Nginx、HAProxy、Envoy、云厂商的 ALB
适用位置 超大流量的第一级入口 业务层、需要内容路由的场景

为什么四层性能远高于七层? 因为四层不需要建立完整的应用层连接、不需要解析 HTTP 报文、不需要分配大量的应用层缓冲区。在 LVS 的 DR(Direct Routing)模式下,响应包甚至不经过负载均衡器,而是由后端直接回给客户端,所以吞吐量基本不受 LB 自身带宽限制——这是它能支撑超大流量的根本原因。

反过来,七层能做的事多得多。同一个域名下要把 /api/ 转到后端 API 集群、把 /static/ 交给静态服务、把 /admin/ 限制内网访问,这些只有七层能做到。

选型结论:

  • 绝大多数中小规模的业务,直接用 Nginx(七层)就够了。它的性能对十万级 QPS 以下的场景绰绰有余。
  • 流量非常大且只需要转发(比如视频、下载、直播),用 LVS 或云厂商的四层 LB 做第一级,后面再接 Nginx 做七层。
  • 典型的大型站点会采用分层架构:四层 LB → 七层 Nginx 集群 → 应用集群。上一层做容量,下一层做智能路由。

Nginx 从 1.9 版本开始也支持 stream 模块做四层转发(TCP/UDP),但它的四层能力远不如 LVS 专业,主要用于简单的 TCP 代理场景(如数据库代理、MQ 代理)。

三、Nginx upstream 基础配置

upstream backend {
    server 10.0.1.11:8080;
    server 10.0.1.12:8080;
    server 10.0.1.13:8080;
}

server {
    listen 80;
    server_name example.com;

    location / {
        proxy_pass http://backend;
        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;
    }
}

三个请求头值得说明,它们决定了后端能不能拿到正确的信息:

  • Host $host:把原始请求的 Host 头传给后端,否则后端收到的是 upstream 名("backend"),依赖 Host 做路由的应用会出错。
  • X-Real-IP 和 X-Forwarded-For:传递客户端真实 IP。不配的话后端看到的全部是 Nginx 的 IP,访问日志、风控、地域判断全都会错。X-Forwarded-For 用 $proxy_add_x_forwarded_for 而不是 $remote_addr,是为了在多层代理时保留完整的 IP 链。
  • X-Forwarded-Proto $scheme:告诉后端原始请求是 HTTP 还是 HTTPS。如果 Nginx 做了 SSL 终结,后端看到的是 HTTP,没有这个头会导致应用生成的链接回退成 http://。

处理真实 IP 更规范的做法是用 real_ip 模块(当上游还有一层代理时):

# 只有来自 10.0.1.0/24 的代理带来的 XFF 才被信任
set_real_ip_from 10.0.1.0/24;
real_ip_header X-Forwarded-For;
real_ip_recursive on;

注意安全点: 不设 set_real_ip_from 就无条件信任客户端传来的 X-Forwarded-For,等于允许任何人伪造 IP。

四、五种调度算法与选型

Nginx upstream 支持以下几种分发方式:

轮询(round-robin,默认)

upstream backend {
    server 10.0.1.11:8080;
    server 10.0.1.12:8080;
}

按顺序逐个分配,不考虑任何后端状态。适合所有节点配置相同、请求处理耗时相近的场景。这是默认算法,也是最常用、最不容易出错的一种。

加权轮询(weight)

upstream backend {
    server 10.0.1.11:8080 weight=3;
    server 10.0.1.12:8080 weight=1;
    server 10.0.1.13:8080 weight=1;
}

第一台会分到大约 60%(3/5)的请求。适合节点性能不等的场景(比如新老机器混跑、灰度机器配置较低),也常用于灰度引流——给新版本机器很小的权重观察一段时间。

最少连接(least_conn)

upstream backend {
    least_conn;
    server 10.0.1.11:8080;
    server 10.0.1.12:8080;
}

把新请求交给当前活动连接最少的节点。适合请求处理时间差异较大的场景(例如有大文件上传/导出的接口、响应时间波动大的业务)——轮询在这种场景下会造成"耗时长的请求恰好堆积在某台机器上"。

IP 哈希(ip_hash)

upstream backend {
    ip_hash;
    server 10.0.1.11:8080;
    server 10.0.1.12:8080;
}

按客户端 IP 的前三段(IPv4)计算哈希,同一 IP 的请求固定转发到同一台机器。这是实现会话保持(session sticky)的一种方式,但它是本节最后要劝退的方案,见第六节。

注意 ip_hash 与 weight 可以一起使用,但在某些版本组合下行为有限制。

通用哈希(hash)

upstream backend {
    hash $request_uri consistent;      # consistent 启用一致性哈希
    server 10.0.1.11:8080;
    server 10.0.1.12:8080;
}

# 也可以基于任意变量
upstream backend2 {
    hash $cookie_userid consistent;
    server 10.0.1.11:8080;
    server 10.0.1.12:8080;
}

按任意变量哈希,consistent 参数启用一致性哈希——这是关键:普通哈希在节点数量变化时会导致大部分 key 的映射关系改变(缓存大规模失效),一致性哈希只影响少量 key。

选型的对照表:

算法 配置 适合场景 注意点
轮询 默认 节点同质、请求耗时接近 最安全的选择
加权轮询 weight=N 节点性能不等、灰度引流 权重比例要与实际能力匹配
最少连接 least_conn 请求耗时差异大(长任务是其中的一部分) 短耗时场景意义不大
IP 哈希 ip_hash 需要会话保持时的退而求其次方案 移动网络/NAT 会导致分布不均
一致性哈希 hash $key consistent 后端有本地缓存、希望提高命中率 节点增减时影响最小

开源版本的 Nginx 没有商业版提供的 least_time(最少响应时间)算法,需要按响应快慢分发时应考虑 HAProxy 的商业替代或自行实现。

五、健康检查与故障摘除

Nginx 开源版通过被动健康检查判断后端是否存活(商业版支持主动探测)。相关参数:

upstream backend {
    server 10.0.1.11:8080 max_fails=3 fail_timeout=10s;
    server 10.0.1.12:8080 max_fails=3 fail_timeout=10s;
    server 10.0.1.13:8080 backup;          # 备用节点
}
参数 含义 说明
max_fails 在 fail_timeout 时间内失败多少次判定为不可用 默认 1
fail_timeout 失败计数的窗口,同时也是被判定不可用后的隔离时长 默认 10s
backup 标记为备用节点,只有主节点全挂时才启用 灾备、降级页面
down 标记为永久不可用,便于临时摘除 发布时使用
slow_start(商业版) 节点恢复后逐步增加流量,避免刚启动就被打垮 开源版无

参数的含义要准确理解:max_fails=3 fail_timeout=10s 的意思是,在 10 秒内连续失败 3 次就认为该节点不可用,并将它摘除 10 秒,10 秒后重新尝试。所以这个组合避免了节点刚恢复就被流量打崩的情况。

什么叫"失败"?可以通过 proxy_next_upstream 定义:

location / {
    proxy_pass http://backend;
    proxy_next_upstream error timeout http_500 http_502 http_503 http_504;
    proxy_next_upstream_tries 3;
    proxy_next_upstream_timeout 10s;
}

这条配置的含义是:当请求遇到 error、timeout 或后端返回 500/502/503/504 时,自动把请求重发到下一个节点,最多重试 3 次。

这里有一个非常重要的注意事项:默认配置下 Nginx 会对失败的请求自动重试下一个节点,proxy_next_upstream 默认就包含 error 和 timeout。 对于非幂等的写操作(POST 下单、支付),一次失败后自动重试到另一台,可能导致重复下单、重复扣款。所以涉及资金的接口必须显式处理:

location /api/order {
    proxy_pass http://backend;
    proxy_next_upstream off;          # 写接口关闭自动重试
    proxy_read_timeout 30s;
}

或者让应用层保证幂等(更推荐的做法,因为网络层面的超时很难完全避免重试)。

一个常见的痛点:后端只有一台时,proxy_next_upstream 的 timeout 重试会让单次请求的超时变成多倍(重试 3 次 × 每次 30 秒 = 90 秒)。这就是为什么 proxy_read_timeout 和 proxy_next_upstream_tries 要一起考虑。

六、会话保持:能用别的方式就别用

会话保持(session stickiness)是指让同一个用户的请求始终落到同一台后端。它的存在是因为早期应用把 session 存在本地内存/本地文件里,第二次请求落到别的机器上就找不到 session 了。

ip_hash 的四个问题:

  1. 分布不均:大量用户通过同一个 NAT 出口(公司、学校、移动运营商网关)访问时,哈希会全部落到同一台机器。移动网络尤其严重——用户的 IP 可能在基站间切换,导致 session 频繁丢失。
  2. 故障时的体验:固定到的那台机器挂了,这些用户的 session 全部失效,需要重新登录。
  3. 扩缩容不友好:增加或减少节点会改变哈希映射(普通哈希),大量用户的 session 丢失。
  4. 掩盖了真正的问题:它让"应用有状态"这件事可以继续存在,而这恰恰是阻碍水平扩展的根源。

更好的三种方案,按推荐顺序:

方案一:应用无状态化(最推荐)。 把 session 从本地内存迁移到集中式存储:Redis、Memcached、或者数据库。任何一台机器处理请求都能从共享存储里读到 session。

用户 → Nginx → 任意一台应用节点 → Redis 读 session

这样加机器、减机器、机器挂掉都不影响用户体验,负载均衡可以用最简单的轮询。对于 Spring 应用可以用 Spring Session,PHP 可以在 php.ini 里配 session.save_handler = redis,实现方式都很成熟。

方案二:JWT 等令牌机制。 把状态放在客户端的令牌里(签名保证不可篡改),服务端完全不需要存 session。适合 API 场景。注意令牌大小和无法即时失效这两个特点。

方案三:cookie 路由(次选)。 如果确实做不到无状态,至少要基于应用层标识而不是 IP:

upstream backend {
    hash $cookie_jsessionid consistent;
    server 10.0.1.11:8080;
    server 10.0.1.12:8080;
}

用 consistent(一致性哈希)能在节点增减时把影响降到最小。这比 ip_hash 好,但仍然不如无状态化。

结论:会话保持是一种妥协,不是目标。 只有在遗留系统无法改造时才用它,而且要用基于 cookie 的一致性哈希而不是 ip_hash。

七、动静分离与 SSL 终结

动静分离

静态资源(图片、CSS、JS、视频)和动态请求有不同的特性:静态资源数量多、体积大、可缓存;动态请求 CPU 密集、不可缓存。分开处理能显著提升效率。

upstream app_backend {
    least_conn;
    server 10.0.1.11:8080;
    server 10.0.1.12:8080;
}

upstream static_backend {
    server 10.0.1.21:80;
    server 10.0.1.22:80;
}

server {
    listen 80;
    server_name example.com;

    # 静态资源:本地直接返回,带长过期时间
    location ~* \.(jpg|jpeg|png|gif|ico|webp|css|js|woff2)$ {
        root /var/www/static;
        expires 30d;
        add_header Cache-Control "public, immutable";
        access_log off;
        open_file_cache max=10000 inactive=30s;
        gzip_static on;             # 直接使用预压缩的 .gz 文件
    }

    # 静态资源量特别大时,交给专门的静态集群
    location /download/ {
        proxy_pass http://static_backend;
        proxy_set_header Host $host;
    }

    # 动态请求走应用集群
    location / {
        proxy_pass http://app_backend;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}

静态资源量大时,进一步的做法是交给 CDN 或对象存储,把这部分流量完全从自己的服务器上卸掉。

SSL 终结(SSL Termination)

让 Nginx 负责 HTTPS 的加密解密,后端走明文 HTTP:

upstream app_backend {
    server 10.0.1.11:8080;
    server 10.0.1.12:8080;
}

server {
    listen 80;
    server_name example.com;
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl http2;
    server_name example.com;

    ssl_certificate     /etc/nginx/ssl/example.com.pem;
    ssl_certificate_key /etc/nginx/ssl/example.com.key;
    ssl_protocols       TLSv1.2 TLSv1.3;
    ssl_ciphers         ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384;
    ssl_prefer_server_ciphers off;
    ssl_session_cache   shared:SSL:10m;
    ssl_session_timeout 1d;
    ssl_session_tickets off;

    # HSTS(确认全站 HTTPS 后再开启,且要先从小 max-age 开始)
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;

    location / {
        proxy_pass http://app_backend;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-Proto $scheme;   # 重要:告知后端原始协议
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}

SSL 终结的三个好处:

  • 后端不用配证书,简化运维,证书更新只需要在一处操作。
  • TLS 握手的计算集中在 LB 层,可以集中升级硬件或做专门调优。
  • 后端有了明文请求,便于排错和做应用层优化(如内容改写、压缩)。

代价是 LB 到后端这段链路是明文的,所以前提是这段链路在可信网络内(同 VPC、专线、内网)。有合规要求的场景需要在 Nginx 到后端之间再加密(proxy_pass https://...),或者使用 mTLS。

ssl_session_cache 和 TLS 1.3 能有效降低握手开销。X-Forwarded-Proto 不要漏配,否则后端应用生成的重定向 URL 会错误地退回 http:// 造成混合内容警告或重定向循环。

风险提示:修改 add_header Strict-Transport-Security 后,浏览器会在 max-age 期间强制用 HTTPS 访问该域名。如果 HTTPS 还没完全就绪就把 max-age 设得很大(一年),会导致用户无法回退到 HTTP 访问。建议先用较小的值(如 300 秒)验证。

八、Nginx 与 LVS / HAProxy / 云负载均衡的定位

方案 层次 定位 什么时候用它
Nginx 七层(也有 stream 四层) 七层负载均衡的事实标准,同时是 Web 服务器和反向代理 绝大多数 HTTP 场景;需要按 URL/Header 路由、SSL 终结、动静分离
HAProxy 四层 + 七层 专业负载均衡器,健康检查与状态统计更精细 需要更丰富的健康检查策略、更细的调度算法(如 least time)、TCP 层负载均衡
LVS 四层 内核态转发,性能极高,DR 模式可支撑超大流量 百万级并发、视频/下载/直播入口的第一级负载
云负载均衡服务 四层 / 七层 托管服务,跨可用区高可用,按需付费 云上部署,不想自己维护 VIP 和 keepalived;需要跨 AZ 容灾
keepalived + Nginx 七层 + VIP 自建高可用方案 自建机房,或需要在云上做自定义的高可用策略

Nginx 与 HAProxy 的差异(经常被问到):两者七层能力接近,Nginx 的优势在于它同时是 Web 服务器(能直接服务静态文件、做缓存、处理 SSL),生态和资料更丰富;HAProxy 的优势在于更专业的负载均衡特性(丰富的健康检查、统计接口、多种调度算法包括最少响应时间、更细粒度的连接管理)。规模不大、团队熟悉 Nginx 的情况直接用 Nginx 就好。

云负载均衡服务的定位:它解决的最大痛点是高可用——云厂商的 LB 本身就是跨可用区部署的,你不用自己搭 keepalived。缺点是不够灵活、某些高级特性缺失、跨云或混合云场景覆盖不到。云上部署时,通常直接用云 LB 作为入口(负责 VIP 和跨 AZ),后面接自己的 Nginx 集群做七层路由。

九、单点问题:keepalived + VIP

回到开头提到的关键点:只有一台 Nginx 做负载均衡,它故障就等于服务中断。解决方案是两台或多台 Nginx 集群共享一个虚拟 IP(VIP),由 keepalived 通过 VRRP 协议管理这个 VIP 的归属。

典型拓扑(文字描述):

                    公网 VIP: 203.0.113.10
                             |
              +--------------+--------------+
              |                             |
      Nginx LB1 (MASTER)            Nginx LB2 (BACKUP)
      10.0.1.2, priority 150        10.0.1.3, priority 100
              |                             |
              +--------------+--------------+
                             |  (内网 VIP 或直连后端)
              +--------------+--------------+
              |              |              |
         App1 10.0.1.11  App2 10.0.1.12  App3 10.0.1.13
              |              |              |
              +--------------+--------------+
                             |
                    Redis / MySQL 集群(共享状态与数据)

工作流程:正常情况下 VIP 绑定在 MASTER(LB1)上,所有流量走 LB1。keepalived 通过 VRRP 心跳持续检测对方存活;当 LB1 宕机或检测到 Nginx 进程异常时,VIP 在几秒内漂移到 BACKUP(LB2),LB2 接管流量,用户几乎无感知(切换期间有短暂中断,通常 1~3 秒)。

MASTER 配置示例:

# /etc/keepalived/keepalived.conf  (LB1,MASTER)
global_defs {
    router_id LB1
    script_user root
    enable_script_security
}

vrrp_script chk_nginx {
    script "/usr/bin/killall -0 nginx"    # 检测 nginx 进程是否存在
    interval 2
    timeout 2
    fall 2                                 # 连续 2 次失败才认为失败
    rise 2
    weight -30                             # 失败时优先级降低 30
}

vrrp_instance VI_1 {
    state MASTER
    interface eth0
    virtual_router_id 51
    priority 150                           # BACKUP 设为 100
    advert_int 1                           # VRRP 通告间隔 1 秒

    authentication {
        auth_type PASS
        auth_pass 一段共享密钥            # 同一 VRRP 组内必须一致
    }

    track_script {
        chk_nginx
    }

    virtual_ipaddress {
        203.0.113.10/24 dev eth0
    }

    notify_master "/usr/local/bin/notify.sh master"
    notify_backup "/usr/local/bin/notify.sh backup"
    notify_fault  "/usr/local/bin/notify.sh fault"
}

BACKUP 的差别只有 state BACKUP 和 priority 100,其余保持一致(特别是 virtual_router_id 和 auth_pass)。

安装与启动:

sudo dnf install -y keepalived            # RHEL、Rocky、CentOS
sudo apt install -y keepalived            # Debian、Ubuntu

sudo systemctl enable --now keepalived
ip addr show eth0 | grep -E 'inet|203.0.113.10'      # 确认 VIP 在谁身上
journalctl -u keepalived -f                          # 观察切换日志

防火墙必须放行 VRRP 协议(协议号 112),这是最常见的配置失败原因——两台机器都起来了但互相收不到通告,结果是两台都抢到 VIP(脑裂):

# RHEL、Rocky、CentOS(firewalld)
sudo firewall-cmd --permanent --add-rich-rule='rule protocol value="vrrp" accept'
sudo firewall-cmd --reload

# Debian、Ubuntu(ufw 需要在 before.rules 里加,或直接用 nftables)
sudo ufw allow in proto vrrp from 10.0.1.0/24 comment 'keepalived VRRP'

脑裂(split-brain)是关键风险:两台机器都认为自己是 MASTER 并绑定 VIP,导致流量混乱。原因通常是心跳网络不通(防火墙未放行 VRRP、网卡故障)或者两台机的 VRRP 参数不一致。防护措施除了解除网络故障外,还可以使用双心跳链路、以及在云环境下(不支持 VRRP 组播时)改用云厂商的负载均衡或 keepalived 的单播模式:

# 云环境下常用单播(很多云网络不支持组播)
unicast_src_ip 10.0.1.2
unicast_peer {
    10.0.1.3
}

注意:公有云环境通常不支持自定义的 VIP 漂移(ARP 受管控),这种情况下应当直接使用云厂商提供的负载均衡服务,而不是自建 keepalived。这是"云上选型"的重要原则。

下线维护的正确姿势:不要直接关掉 keepalived 所在的机器。应该调低优先级或停服务让 VIP 平滑漂移:

sudo systemctl stop keepalived        # VIP 会漂到 BACKUP
ip addr show eth0                     # 确认 VIP 已经不在本机
# 维护完成后
sudo systemctl start keepalived

常见问题

该选用四层还是七层负载均衡?

看需不需要"按内容路由"。需要按 URL、域名、Header 做分发,需要 SSL 终结、压缩、动静分离,就用七层(Nginx/HAProxy)。只是单纯把超大流量(视频、下载、直播)分给后端,不需要解析内容,用四层(LVS 或云厂商四层 LB)性能更好。大型站点通常两者结合:四层做第一级入口,七层做业务路由。

Nginx 能支撑多少并发?

取决于硬件、请求内容和配置。一般的 4~8 核机器做纯反向代理,处理简单接口的 QPS 在几千到几万量级;静态小文件可以更高。不要相信笼统的"能支持十万并发"的说法,要用压测在自己的真实请求上测出来。另外反向代理场景每个请求占用两个 fd,估算上限时要除以 2(详见本系列 Nginx 并发优化篇)。

ip_hash 做会话保持有什么问题?

三个主要问题:一是大量用户共享出口 NAT 导致请求集中到某台机器,分布严重不均;二是移动网络下用户 IP 会变,session 还是会丢;三是扩缩容时会破坏哈希映射导致大批 session 失效。建议改造应用做到无状态(session 存 Redis),实在改不了就用基于 cookie 的一致性哈希。

为什么后端一台没挂,请求还是会失败?

先看 proxy_next_upstream 的行为:默认情况下 error 和 timeout 会触发重试。如果 proxy_read_timeout 是 30 秒而后端某次处理超过 30 秒,Nginx 会认为超时并重试,总耗时可能累积到几十秒最终返回 504。解决办法是把慢接口单独配置更长的 timeout,或者优化这个接口本身。另外要检查 worker 连接数/backlog 是否是瓶颈(前面 Nginx 并发篇有详细说明)。

写放在 backup 节点上,平时是不是浪费?

backup 节点的设计目的就是"主节点全挂时才启用",所以它平时的闲置是预期的。实践中可以把备用节点配置得比主节点低(节省成本),或者让它承载一些低优先级的任务(如后台跑批处理、接收内部管理流量),这样不算完全浪费。也可以采用双活方案——两台 Nginx 互为主备,各自承载一部分业务的 VIP,平时都工作。

云上还需要自己搭 keepalived 吗?

一般不需要,也不推荐。公有云的网络通常不支持自定义 VIP 的 ARP 漂移,强行搭会遇到各种限制。雲厂商的负载均衡服务本身就是分布式、跨可用区高可用的,直接作为入口使用更合适,后面接自己的 Nginx 集群做七层路由。自建 keepalived 主要适用于自建机房、裸金属或特殊的混合云场景。

健康检查用 Nginx 自带的 max_fails 够吗?

对多数场景够用,但它是被动的——只有真的有请求打过去失败了才会发现。如果希望主动探测(定期发健康检查请求),Nginx 开源版本身不支持,可以考虑:用 HAProxy 替代、使用第三方 nginx_upstream_check_module 模块(需要自行编译)、或者在外部用一个探活脚本定期修改 upstream 配置。商业版 Nginx Plus 提供了主动健康检查能力。

总结

  • 负载均衡解决四件事:提升容量、高可用(摘除故障节点)、可维护(逐台下线发布)、弹性扩缩。但它本身是单点,完整方案必须包含 LB 自身的冗余(keepalived + VIP 或云负载均衡)。
  • 四层(L4)看 IP+端口,性能极高(LVS 的 DR 模式下响应包不经过 LB),但只能转发;七层(L7)能解析 HTTP,支持按 URL/Header/Cookie 路由、SSL 终结、动静分离、限流。绝大多数中小业务直接用 Nginx 七层就够;超大流量用四层做第一级、七层做第二级。
  • Nginx upstream 的调度算法:轮询(默认,节点同质时最安全)、加权轮询(节点性能不等或灰度引流)、least_conn(请求耗时差异大时)、ip_hash 与一致性哈希(会话保持的妥协方案)。开源版没有 least_time。
  • 健康检查参数 max_fails=3 fail_timeout=10s 的正确理解是:10 秒内失败 3 次则摘除 10 秒后重试。 proxy_next_upstream 默认会对 error/timeout 自动重试,写接口(下单、支付)必须 proxy_next_upstream off 或由应用层保证幂等,否则可能造成重复下单。
  • 会话保持是妥协不是目标:ip_hash 在 NAT 和移动网络下分布严重不均、故障时 session 全部失效、扩缩容会破坏映射。优先做应用无状态化(session 存 Redis),次选基于 cookie 的一致性哈希。
  • 动静分离把静态资源从应用层卸下来(expires、open_file_cache、gzip_static,量大就上 CDN/对象存储);SSL 终结把 TLS 计算和证书管理集中在 LB 层,但必须配 X-Forwarded-Proto,否则后端生成的链接会错误地回到 http。HSTS 的 max-age 要从小值开始验证。
  • 选型对照:Nginx 是七层的事实标准且兼做 Web 服务器;HAProxy 健康检查与调度算法更专业;LVS 是超高性能的四层方案;云负载均衡服务的最大价值是自带跨可用区高可用,云上不必自建 keepalived。
  • keepalived 通过 VRRP 管理 VIP(MASTER priority 150 / BACKUP 100),配合 vrrp_script 检测 Nginx 进程可实现秒级切换。防火墙必须放行 VRRP 协议(协议号 112),否则两台机器互相收不到心跳会造成脑裂。云环境不支持组播时用 unicast_peer 单播模式,或者直接使用云厂商的负载均衡。下线维护用 systemctl stop keepalived 让 VIP 平滑漂移,不要直接关机。