Nginx配置负载均衡详解
Nginx 做负载均衡,本质上就是把同一个入口的请求分发到多个后端节点上,让单机服务变成一组服务。这样做的直接收益有三个:提升并发承载能力、降低单点故障风险、为后续扩容留下空间。
很多人第一次接触 Nginx 负载均衡时,容易停留在“会配 upstream 和 proxy_pass”这个层面,但真正线上可用的配置,除了能转发,还要考虑调度策略、健康状态、超时控制、失败重试、会话保持、真实 IP 透传,以及动静分离和高可用部署。
为什么需要负载均衡
当应用只部署一台服务器时,所有请求都落到单个实例上,会遇到几个典型问题:
-
性能瓶颈明显 CPU、内存、连接数、磁盘 IO 都会成为上限。
-
单点故障严重 一台机器宕机,整个服务不可用。
-
扩容困难 单机纵向扩容成本高,而且存在硬件上限。
-
发布风险大 一次重启或升级就可能影响全部用户请求。
负载均衡的思路很直接:前面放一个统一入口,后面挂多台业务节点。客户端只访问 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_fails与fail_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 时
- 当前请求可切换到其他可用节点
需要注意两点:
-
并不是所有请求都适合重试 对于幂等请求,例如 GET,重试通常问题不大。 对于非幂等请求,例如支付、下单、扣库存,自动重试要非常谨慎。
-
失败切换不等于完整健康检查 被动检查只能在请求发生后感知异常,不能提前探测节点状态。
如果业务对高可用要求很高,通常会结合服务注册中心、云负载均衡、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_fails、fail_timeout、proxy_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 日志观察请求量,再结合后端日志比对转发情况。
方法四:压力测试
使用 ab、wrk、hey、JMeter 等工具压测,验证高并发下分发是否均衡、是否存在异常节点倾斜。
例如:
wrk -t4 -c100 -d30s http://www.example.com/api/test
验证时不要只做单次访问,因为负载均衡策略通常需要一定量的请求才能观察出分布规律。
Nginx 负载均衡与高可用不是同一个问题
很多文章会把“负载均衡”和“高可用”混在一起讲,但这两个不是一回事。
Nginx 做的是后端节点的流量分发。但如果前置的 Nginx 自己挂了,整个入口仍然会不可用。
所以严格来说,完整高可用需要至少两层考虑:
- 后端应用集群负载均衡
- Nginx 自身高可用
常见做法是两台或多台 Nginx 配合 Keepalived 提供虚拟 IP,实现入口层故障切换。也可以直接使用云厂商提供的 SLB、ALB、CLB 等托管负载均衡服务,把 Nginx 放在更后面处理七层路由。
结论是:Nginx 负载均衡解决的是应用集群分发问题,不自动等于入口高可用。
版本说明
开源版 Nginx
本文示例基于开源版 Nginx 常见能力展开,主要包括:
upstreamproxy_pass- 轮询、加权轮询、
ip_hash least_conn- 被动失败检测
- 代理超时与请求头透传
Nginx Plus
Nginx Plus 是商业版本,提供更完整的主动健康检查、状态监控、动态配置等能力。若线上需要更强的服务治理与可观测性,需要明确区分开源版和商业版的功能边界,不能混用概念。
实际落地建议
如果是普通中小型 Web 系统,Nginx 负载均衡最常见、最稳妥的落地方式通常是:
- 前端入口使用 Nginx
- 后端部署多个应用实例
- 调度策略优先使用轮询或最少连接
- 会话统一放 Redis,不依赖 IP Hash
- 配置真实 IP 透传、超时和失败切换
- 静态资源由 Nginx 直接处理
- 入口层再配合 Keepalived 或云负载均衡实现高可用
这套组合不复杂,但足够覆盖大多数互联网业务的核心需求。
从工程实践角度看,Nginx 负载均衡并不难,难的是把它从“能跑”配置成“线上稳定可控”。真正有价值的配置,不是只有几行 upstream 和 proxy_pass,而是把请求分发、故障处理、日志透传、连接控制、会话策略这些关键点一起考虑清楚。