原创

Nginx配置负载均衡详解

Nginx 做负载均衡,本质上就是把同一个入口的请求分发到多个后端节点上,让单机服务变成一组服务。这样做的直接收益有三个:提升并发承载能力、降低单点故障风险、为后续扩容留下空间。

很多人第一次接触 Nginx 负载均衡时,容易停留在“会配 upstream 和 proxy_pass”这个层面,但真正线上可用的配置,除了能转发,还要考虑调度策略、健康状态、超时控制、失败重试、会话保持、真实 IP 透传,以及动静分离和高可用部署。

为什么需要负载均衡

当应用只部署一台服务器时,所有请求都落到单个实例上,会遇到几个典型问题:

  1. 性能瓶颈明显 CPU、内存、连接数、磁盘 IO 都会成为上限。

  2. 单点故障严重 一台机器宕机,整个服务不可用。

  3. 扩容困难 单机纵向扩容成本高,而且存在硬件上限。

  4. 发布风险大 一次重启或升级就可能影响全部用户请求。

负载均衡的思路很直接:前面放一个统一入口,后面挂多台业务节点。客户端只访问 Nginx,Nginx 决定把请求转发到哪一台后端服务器。

Nginx 负载均衡的基本工作机制

Nginx 在反向代理场景下,通常使用 upstream 定义后端服务器组,再通过 proxy_pass 把请求转发到这个组。

最基础的结构是这样的:

http {
    upstream backend_servers {
        server 192.168.1.101:8080;
        server 192.168.1.102:8080;
        server 192.168.1.103:8080;
    }

    server {
        listen 80;
        server_name www.example.com;

        location / {
            proxy_pass http://backend_servers;
        }
    }
}

这个配置的含义是:

  • upstream backend_servers:定义一个后端服务组
  • 三个 server:表示三个可转发的应用节点
  • proxy_pass http://backend_servers:把访问当前站点的请求代理给这个 upstream

在没有显式指定调度算法时,Nginx 默认采用轮询

负载均衡的常见调度算法

Nginx 支持多种分发策略。不同策略适合不同业务场景,不能简单理解成“哪个好就一直用哪个”。

1. 轮询

轮询是默认策略,请求按顺序依次分发到各个节点。

upstream backend_servers {
    server 192.168.1.101:8080;
    server 192.168.1.102:8080;
    server 192.168.1.103:8080;
}

特点:

  • 配置简单
  • 适合后端机器性能差异不大的场景
  • 请求分布相对均匀

适用场景:

  • 多台应用服务器配置基本一致
  • 无明显长连接倾斜
  • 业务处理耗时差异不大

2. 加权轮询

如果不同服务器性能不一致,可以给性能更好的节点分配更高权重。

upstream backend_servers {
    server 192.168.1.101:8080 weight=5;
    server 192.168.1.102:8080 weight=3;
    server 192.168.1.103:8080 weight=2;
}

这里权重总和为 10,大体上请求会按 5:3:2 的比例分发。

特点:

  • 能体现服务器性能差异
  • 比纯轮询更适合异构机器环境

注意点:

  • weight 影响的是分配概率,不是严格的固定次数
  • 权重并不等于自动性能感知,仍然需要人工配置

3. IP Hash

某些系统依赖会话,例如把用户登录状态保存在本机内存中,这种情况下希望同一个客户端尽量总是落到同一台服务器。

upstream backend_servers {
    ip_hash;
    server 192.168.1.101:8080;
    server 192.168.1.102:8080;
    server 192.168.1.103:8080;
}

特点:

  • 同一个客户端 IP 的请求,通常会分配到同一台后端节点
  • 可以在一定程度上实现会话保持

局限性:

  • 客户端经过 NAT 或共享出口时,大量用户可能共用一个公网 IP,容易造成流量倾斜
  • 后端节点变更后,映射关系会变化
  • 不适合复杂网络环境下的精准会话保持

4. 最少连接数

这种方式优先把请求分发给当前连接数较少的节点。

upstream backend_servers {
    least_conn;
    server 192.168.1.101:8080;
    server 192.168.1.102:8080;
    server 192.168.1.103:8080;
}

特点:

  • 适合请求处理时间差异较大的业务
  • 能避免某个节点长期堆积过多连接

适用场景:

  • 长连接较多
  • 接口耗时不稳定
  • WebSocket 或部分流式业务

5. 第三方扩展算法

有些更复杂的调度方式,例如基于 URL、一致性哈希等,通常依赖第三方模块或 Nginx Plus,不属于开源版默认能力。生产环境中最常用的仍然是轮询、加权轮询、IP Hash、最少连接数这几类。

一个可直接理解的完整负载均衡示例

下面给出一个更接近实际生产的配置:

worker_processes auto;

events {
    worker_connections 10240;
}

http {
    include       mime.types;
    default_type  application/octet-stream;

    sendfile on;
    keepalive_timeout 65;

    upstream backend_servers {
        least_conn;

        server 192.168.1.101:8080 weight=5 max_fails=3 fail_timeout=30s;
        server 192.168.1.102:8080 weight=5 max_fails=3 fail_timeout=30s;
        server 192.168.1.103:8080 weight=3 max_fails=3 fail_timeout=30s;
    }

    server {
        listen 80;
        server_name www.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;

            proxy_connect_timeout 3s;
            proxy_send_timeout 10s;
            proxy_read_timeout 10s;

            proxy_next_upstream error timeout invalid_header http_500 http_502 http_503 http_504;
        }
    }
}

这份配置比最简单版本多了几项关键能力:

  • least_conn:按最少连接分发
  • weight:根据节点能力分配权重
  • max_failsfail_timeout:控制失败判定
  • proxy_set_header:把客户端真实请求信息传给后端
  • proxy_*_timeout:控制连接与读写超时
  • proxy_next_upstream:当一个节点失败时自动切换到其他节点

这类配置已经具备较强的生产可用性基础。

upstream 中常见参数解释

理解 upstream 里的参数很重要,因为线上是否稳定,很大程度上取决于这些细节。

weight

指定该节点的权重。权重越高,被分配到的请求通常越多。

server 192.168.1.101:8080 weight=5;

max_fails

fail_timeout 时间窗口内,允许失败的最大次数。超过后,该节点会被暂时认为不可用。

server 192.168.1.101:8080 max_fails=3 fail_timeout=30s;

表示 30 秒内失败超过 3 次,则临时摘除。

fail_timeout

max_fails 配合使用,表示失败统计窗口以及节点被暂时标记为不可用的时间。

backup

把某个节点设为备用节点,只有主节点全部不可用时才会转发到它。

upstream backend_servers {
    server 192.168.1.101:8080;
    server 192.168.1.102:8080;
    server 192.168.1.103:8080 backup;
}

适合做低频兜底流量承接。

down

临时标记某个节点不可用,但配置仍然保留。

upstream backend_servers {
    server 192.168.1.101:8080;
    server 192.168.1.102:8080 down;
    server 192.168.1.103:8080;
}

这在灰度、下线、排障时很实用。

真实 IP 透传为什么必须配置

如果不配置请求头透传,后端服务看到的客户端 IP 往往是 Nginx 所在机器的 IP,而不是用户真实地址。这会导致:

  • 登录审计失真
  • 风控判断失真
  • 限流策略不准确
  • 地域分析错误

常用配置如下:

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;
}

后端应用应优先从这些头中获取真实客户端信息。

例如 Java Spring Boot 应用,通常会结合反向代理配置来识别真实 IP,否则日志、鉴权、风控都可能出问题。

会话保持问题怎么处理

这是负载均衡中最容易被忽略,也最容易出故障的点之一。

如果系统把 Session 保存在应用本机内存,那么同一个用户第一次请求打到 A 机器,第二次请求打到 B 机器时,就可能发生登录态丢失。

方案一:IP Hash

适合简单场景,但不够稳定,前面已经提到其限制。

方案二:Session 共享

把 Session 存到 Redis、数据库等外部存储中,多个应用节点共享。

这是更推荐的方式,因为它从架构上解耦了会话与单机节点的绑定。

方案三:无状态化

现在更常见的做法是使用 JWT、Token、网关鉴权等方案,让服务本身尽量无状态。这样负载均衡就不需要关心“同一个用户必须访问同一台机器”这个问题。

结论很明确:不要把会话保持完全寄希望于 Nginx 调度策略,优先改造业务为共享状态或无状态。

健康检查与故障转移

开源版 Nginx 没有非常完整的主动健康检查能力,常见做法主要依赖被动检查,也就是通过请求失败结果判断节点是否可用。

例如:

upstream backend_servers {
    server 192.168.1.101:8080 max_fails=3 fail_timeout=30s;
    server 192.168.1.102:8080 max_fails=3 fail_timeout=30s;
}

配合:

proxy_next_upstream error timeout invalid_header http_500 http_502 http_503 http_504;

含义是:

  • 某个节点连接失败、超时、返回错误头,或者返回 500/502/503/504 时
  • 当前请求可切换到其他可用节点

需要注意两点:

  1. 并不是所有请求都适合重试 对于幂等请求,例如 GET,重试通常问题不大。 对于非幂等请求,例如支付、下单、扣库存,自动重试要非常谨慎。

  2. 失败切换不等于完整健康检查 被动检查只能在请求发生后感知异常,不能提前探测节点状态。

如果业务对高可用要求很高,通常会结合服务注册中心、云负载均衡、Kubernetes Ingress 或更完善的网关体系来实现主动探活。

超时配置应该怎么设

超时配置不是越大越好,也不是越小越好,关键在于和业务响应特征匹配。

Nginx 常用的几个代理超时参数如下:

proxy_connect_timeout 3s;
proxy_send_timeout 10s;
proxy_read_timeout 10s;

proxy_connect_timeout

Nginx 与后端建立连接的超时时间。适合设置得相对短一些,因为连接建立本来就应该很快。

proxy_send_timeout

Nginx 向后端发送请求数据的超时时间。大多数普通 HTTP 请求这个值不需要太大。

proxy_read_timeout

Nginx 等待后端响应的超时时间。这个值要结合业务接口耗时设置,例如报表导出、文件处理、批量计算接口可能需要更长时间。

常见错误有两个:

  • 把所有接口统一设置成很大的超时时间,结果故障拖得更久
  • 设置过小,导致正常慢请求被误判超时

正确做法是按业务分类,必要时对不同 location 分别设置。

动静分离与负载均衡结合

Nginx 不只是做请求转发,还非常适合做动静分离。

静态资源可以直接由 Nginx 返回,动态请求再交给后端应用集群处理。这样能显著减轻应用服务器压力。

例如:

server {
    listen 80;
    server_name www.example.com;

    location /static/ {
        root /data/www;
        expires 7d;
    }

    location /api/ {
        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;
    }
}

这种方式把:

  • /static/ 下的资源直接交给 Nginx
  • /api/ 请求交给后端应用集群

线上站点几乎都会做这类拆分。

负载均衡配置中的常见误区

只配 proxy_pass,不配请求头

这样后端拿不到真实主机名、协议、客户端 IP,日志和业务逻辑都会受影响。

只关心转发成功,不关心失败策略

没有设置合理的 max_failsfail_timeoutproxy_next_upstream,节点半故障时会持续影响用户请求。

用 IP Hash 解决所有会话问题

IP Hash 只能算权宜之计,不是通用解法。真正稳定的方案是共享会话或服务无状态化。

超时全部统一一个值

不同接口耗时模型差异很大,统一配置往往不是过松就是过紧。

以为 Nginx 自带完善主动健康检查

开源版主要是被动检查,不要误以为它天然具备完整的节点探活能力。

忽略长连接与 WebSocket 场景

普通 HTTP 配置不一定适合 WebSocket 或 SSE。如果业务包含长连接,需要单独处理升级头和更长的读超时。

例如 WebSocket 常见配置如下:

location /ws/ {
    proxy_pass http://backend_servers;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_read_timeout 3600s;
}

生产环境推荐配置模板

下面是一份较为实用的 Nginx 负载均衡模板,适合作为后端服务入口的基础版本。

worker_processes auto;

events {
    worker_connections 20480;
}

http {
    include       mime.types;
    default_type  application/octet-stream;

    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;

    keepalive_timeout 65;
    client_max_body_size 50m;

    upstream app_cluster {
        least_conn;

        server 10.0.0.11:8080 weight=5 max_fails=3 fail_timeout=30s;
        server 10.0.0.12:8080 weight=5 max_fails=3 fail_timeout=30s;
        server 10.0.0.13:8080 weight=3 max_fails=3 fail_timeout=30s;
        server 10.0.0.14:8080 backup;
    }

    server {
        listen 80;
        server_name api.example.com;

        access_log /var/log/nginx/api_access.log;
        error_log  /var/log/nginx/api_error.log warn;

        location / {
            proxy_pass http://app_cluster;

            proxy_http_version 1.1;

            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;

            proxy_connect_timeout 3s;
            proxy_send_timeout 10s;
            proxy_read_timeout 10s;

            proxy_next_upstream error timeout invalid_header http_500 http_502 http_503 http_504;
            proxy_next_upstream_tries 3;
        }
    }
}

这个模板覆盖了以下重点:

  • 自动适配 worker 进程数量
  • 合理的连接数与基础网络参数
  • 最少连接调度
  • 主节点加权分配
  • 备用节点兜底
  • 真实 IP 与协议透传
  • 超时与失败切换配置
  • 独立访问日志和错误日志

这已经比很多“示例配置”更接近真实生产环境。

如何验证负载均衡是否生效

配置写完之后,不能只看 Nginx 是否启动成功,还要验证流量是否真的分发到了多个节点。

方法一:后端返回机器标识

在每个后端应用中返回本机 IP 或主机名,例如:

  • 节点 A 返回 server-a
  • 节点 B 返回 server-b
  • 节点 C 返回 server-c

多次访问接口,观察返回值是否在不同节点之间变化。

方法二:查看应用日志

分别查看各台后端服务日志,确认是否都有请求进入。

方法三:查看 Nginx 访问日志

通过 Nginx 日志观察请求量,再结合后端日志比对转发情况。

方法四:压力测试

使用 abwrkhey、JMeter 等工具压测,验证高并发下分发是否均衡、是否存在异常节点倾斜。

例如:

wrk -t4 -c100 -d30s http://www.example.com/api/test

验证时不要只做单次访问,因为负载均衡策略通常需要一定量的请求才能观察出分布规律。

Nginx 负载均衡与高可用不是同一个问题

很多文章会把“负载均衡”和“高可用”混在一起讲,但这两个不是一回事。

Nginx 做的是后端节点的流量分发。但如果前置的 Nginx 自己挂了,整个入口仍然会不可用。

所以严格来说,完整高可用需要至少两层考虑:

  1. 后端应用集群负载均衡
  2. Nginx 自身高可用

常见做法是两台或多台 Nginx 配合 Keepalived 提供虚拟 IP,实现入口层故障切换。也可以直接使用云厂商提供的 SLB、ALB、CLB 等托管负载均衡服务,把 Nginx 放在更后面处理七层路由。

结论是:Nginx 负载均衡解决的是应用集群分发问题,不自动等于入口高可用。

版本说明

开源版 Nginx

本文示例基于开源版 Nginx 常见能力展开,主要包括:

  • upstream
  • proxy_pass
  • 轮询、加权轮询、ip_hash
  • least_conn
  • 被动失败检测
  • 代理超时与请求头透传

Nginx Plus

Nginx Plus 是商业版本,提供更完整的主动健康检查、状态监控、动态配置等能力。若线上需要更强的服务治理与可观测性,需要明确区分开源版和商业版的功能边界,不能混用概念。

实际落地建议

如果是普通中小型 Web 系统,Nginx 负载均衡最常见、最稳妥的落地方式通常是:

  1. 前端入口使用 Nginx
  2. 后端部署多个应用实例
  3. 调度策略优先使用轮询或最少连接
  4. 会话统一放 Redis,不依赖 IP Hash
  5. 配置真实 IP 透传、超时和失败切换
  6. 静态资源由 Nginx 直接处理
  7. 入口层再配合 Keepalived 或云负载均衡实现高可用

这套组合不复杂,但足够覆盖大多数互联网业务的核心需求。

从工程实践角度看,Nginx 负载均衡并不难,难的是把它从“能跑”配置成“线上稳定可控”。真正有价值的配置,不是只有几行 upstreamproxy_pass,而是把请求分发、故障处理、日志透传、连接控制、会话策略这些关键点一起考虑清楚。

正文到此结束
评论插件初始化中...
Loading...