运维知识
悠悠
2026年8月1日

Nginx 负载均衡底层原理全剖析:从反向代理到生产实战,一篇讲透

很多人对 Nginx 的认知停留在"反向代理服务器"这五个字上,但这么说其实不全面。

Nginx 本质上是一个 高性能的 HTTP 和反向代理服务器,由俄罗斯人 Igor Sysoev 在 2002 年搞出来的,最初就是为解决 C10K 问题而生的——啥意思?就是单机同时处理一万个并发连接。

它能干啥?

  • Web 服务器(静态资源托管)
  • 反向代理(隐藏后端,转发请求)
  • 负载均衡(流量分发)
  • 邮件代理
  • API 网关(现在很多公司这么用)

但咱们今天重点聊负载均衡这块。

要理解 Nginx 的负载均衡原理,得先弄明白一个核心概念:反向代理

所谓反向代理,就是客户端发请求的时候,根本不知道后面有多少台服务器在干活。客户端只知道一个地址,就是 Nginx。Nginx 收到请求之后,根据内部规则,把请求转发给后端的某台服务器。

正向代理和反向代理的区别,我举个栗子。

你去饭店吃饭,正向代理就是你跟服务员说"我要吃隔壁那家的红烧肉",服务员帮你去隔壁买。反向代理就是你去一个火锅店,菜单上写啥你点啥,后厨有张三、李四、王五三个厨师在炒菜,但你根本不知道是谁给你炒的。

Nginx 就是那个火锅店的"前厅服务员"。


底层核心:事件驱动 + 异步非阻塞

讲负载均衡之前,必须先说清楚 Nginx 的底层架构,否则后面那些算法你只知道怎么配,不知道为啥这么高效。

Nginx 用了 master-worker 进程模型

一个 master 进程管全局,负责读取配置、管理 worker 进程、接收信号。多个 worker 进程负责实际处理请求。

这玩意儿厉害在哪?传统的 Apache 一个请求一个线程,扛不住高并发。Nginx 一个 worker 能同时处理成千上万个请求,靠的就是 事件驱动 + 异步非阻塞

啥意思?

传统模型就像银行的柜台,每个客户来了开一个窗口,客户走了关窗口。人一多,窗口不够用,就排队。Nginx 的模型就像快递驿站,只有一个窗口,但窗口后面的人不停地在多个包裹之间切换——这个包裹扫一下条码,那个包裹签个字,多个任务并发处理,谁先准备好就先处理谁。

具体到代码层面,Nginx 在 Linux 上用的是 epoll,在 BSD 上用 kqueue,这些都是操作系统提供的高效 I/O 多路复用机制。简单说就是:让一个线程同时监控多个连接,哪个连接有数据来了就处理哪个,没数据的就跳过。

这就是为什么 Nginx 能扛高并发的根本原因。

理解了这一点,再来看负载均衡就顺畅了。Nginx 处理请求的时候,根据你配置的 upstream 列表,按照某种算法挑一台后端服务器,然后转发过去。这个过程对客户端完全透明。


四种基础负载均衡算法:没那么简单

回到正题,负载均衡的核心问题是:Nginx 收到请求了,该转给哪台后端?

答案就是负载均衡算法。Nginx 默认提供了几种,每种都有自己的适用场景。

轮询(Round Robin)

这是最简单也最常用的一个。

顾名思义,就是按顺序来。第一个请求给第一台服务器,第二个给第二台……最后一台完了再回到第一台,周而复始。

配置起来特别简单:

upstream backend {
    server 192.168.1.10;
    server 192.168.1.11;
    server 192.168.1.12;
}
server {
    listen 80;
    location / {
        proxy_pass http://backend;
    }
}

就这么三台机器轮流来,平均分配。

但轮询有个致命问题:它不知道每台服务器的实际负载。如果三台机器配置不一样,性能差距很大,轮询就会把性能差的那台累死,性能好的那台闲死。

而且,轮询不考虑请求的复杂度。有的请求处理快,有的慢,轮询还是按顺序来,可能导致快的机器在等慢的机器。

所以纯轮询在生产环境用得不多,除非后端服务器配置完全一样,且请求类型也比较均匀。

权重轮询(Weighted Round Robin)

轮询的升级版。

既然服务器配置不一样,那就给每台机器分配不同的权重。性能好的多分点,性能差的少分点。

upstream backend {
    server 192.168.1.10 weight=5;
    server 192.168.1.11 weight=3;
    server 192.168.1.12 weight=2;
}

这里的 weight 就是权重。Nginx 会按照 5:3:2 的比例来分配请求。也就是说,十个请求里,第一台分到 5 个,第二台 3 个,第三台 2 个。

但这里有个细节很多人不知道:Nginx 的权重轮询不是简单的"先来五个再去下一台",而是用了平滑加权轮询算法

啥意思?

如果用简单加权轮询,分配顺序可能是:10、10、10、10、10、11、11、11、12、12(10 出现 5 次连着,11 出现 3 次连着……)。这样会导致某台机器在短时间内集中接收大量请求,造成压力尖峰。

平滑加权轮询的分配会更均匀,像 10、11、10、12、10、11、10、12、10、11 这样,避免连续分配。

Nginx 1.x 之后的版本都默认是平滑的,老版本可能不是。

最少连接(Least Connections)

这个就智能多了。

轮询的思路是"我不管你们谁忙谁闲,我按顺序来"。最少连接不一样,它会实时统计每台后端服务器当前的活跃连接数,把新请求分给连接数最少的那台。

upstream backend {
    least_conn;
    server 192.168.1.10;
    server 192.168.1.11;
    server 192.168.1.12;
}

加个 least_conn 指令就行。

这个算法的好处是:能根据后端的实时负载动态调整分配策略。如果某台机器的请求处理得慢,连接堆积,Nginx 就自动把新请求分给其他机器。

但它也有缺点:只看连接数,不看连接的实际负载。如果某个连接是长连接、慢请求,占着茅坑不拉屎,但连接数还是 1,这时候算法就不会把这台机器的请求分走。所以最少连接适合请求处理时间相对均匀的场景。

IP 哈希(IP Hash)

这个算法比较特殊。

前面的算法都只考虑后端服务器的状态,IP 哈希考虑的是客户端

upstream backend {
    ip_hash;
    server 192.168.1.10;
    server 192.168.1.11;
    server 192.168.1.12;
}

它的原理是:拿客户端的 IP 地址做哈希运算,映射到后端服务器列表中的某一个。这样一来,同一个 IP 的请求永远会落到同一台后端服务器上

这个特性非常重要,适合需要会话保持的场景。

举个栗子,电商网站,用户登录之后,session 存在某一台后端服务器上。如果用户的下一个请求被 Nginx 分到了另一台机器,那边没有 session 数据,用户就得重新登录。这种体验简直灾难。

IP 哈希就能避免这个问题。同一个用户的请求永远到同一台机器,session 就保住了。

但它也有坑。

如果你们公司出口 IP 是固定的(比如 NAT 后面的一批用户),那这些用户就全部分到同一台后端,负载均衡就形同虚设了。而且,如果后端服务器数量变了,哈希映射关系就会被打乱,session 又丢了。


这些算法背后的数学原理,你知道吗?

说到这,可能有人会问,IP 哈希具体怎么算的?

Nginx 用的是 crc32 算法,把客户端 IP 转换成一个 32 位的整数,然后对后端服务器的数量取模,得到一个 0 到 N-1 之间的数字,对应到具体的服务器。

举个例子,三台后端服务器,某个客户端 IP 算出来哈希值是 1234567,1234567 % 3 = 1,那就分配到第二台(索引 1)上。

简单粗暴,但很有效。

不过,这种简单的取模方式有个问题:后端服务器数量变化时,几乎所有映射关系都会变

比如你原来有 3 台,加到 4 台,原来分到索引 1 的请求,现在 hash % 4 的结果可能变成别的了,session 全乱。

要解决这个问题,得用一致性哈希。但 Nginx 原生的 IP 哈希不是一致性哈希,所以扩容的时候要小心。

真要做一致性哈希,可以考虑 OpenResty 的 lua-resty-balancer 模块,或者用 HAProxy,人家支持。


高级特性:让负载均衡更智能

上面讲的是基础算法,但在生产环境中,光有这些还不够。Nginx 还提供了一些高级特性来应对复杂的场景。

健康检查

这个必须有。

后端服务器有时候会挂掉——进程崩了、磁盘满了、网络断了……各种原因。如果 Nginx 不知道某台机器挂了,还在往那边转请求,用户就会看到 502 错误。

Nginx 默认有两种健康检查方式:

被动检查:也叫"连接失败检测"。Nginx 在转发请求的时候,如果发现连接被拒绝或者超时,就自动把这台后端标记为"不可用",在接下来的 fail_timeout 时间内不再往那边转。默认配置:

upstream backend {
    server 192.168.1.10 max_fails=3 fail_timeout=30s;
    server 192.168.1.11 max_fails=3 fail_timeout=30s;
}

max_fails=3 表示连续失败 3 次就标记为不可用。fail_timeout=30s 表示 30 秒后再次尝试。

主动检查:需要用到 nginx_upstream_check_module 这个第三方模块。Nginx 会主动向后端发送特定的请求(比如 HTTP 请求或 TCP 连接),根据响应判断服务器是否健康。

upstream backend {
    server 192.168.1.10;
    server 192.168.1.11;
    check interval=3000 rise=2 fall=5 timeout=1000 type=http;
    check_http_send "GET /health HTTP/1.0\r\n\r\n";
    check_http_expect_alive http_2xx http_3xx;
}

这段配置的意思是:每 3 秒检查一次,连续成功 2 次认为恢复,连续失败 5 次认为挂了。检查方式是发一个 HTTP GET 请求,期待返回 2xx 或 3xx 状态码。

主动检查的好处是能在问题恶化之前发现隐患,但会消耗一些资源。

动态负载均衡

传统的 Nginx 配置是写死在 nginx.conf 里的,改了之后得 nginx -s reload 才能生效。在大型互联网公司,这显然不够灵活。

动态负载均衡的意思是:后端服务器列表可以实时变化,不用重启 Nginx

实现方式有几种:

第一种,用 Nginx Plus(官方商业版),自带动态配置功能。

第二种,用 Consul + nginx-upsync-module。通过 Consul 做服务发现,nginx-upsync 模块定时拉取最新的服务器列表,自动更新 upstream 配置。

upstream backend {
    upsync 127.0.0.1:8500/v1/kv/upstreams/backend upsync_timeout=6m upsync_interval=500ms upsync_type=consul strong_dependency=off;
    upsync_dump_path /usr/local/nginx/conf/servers.conf;
    include /usr/local/nginx/conf/servers.conf;
}

这套方案在很多公司都用,适合微服务架构。

第三种,用 OpenResty + Lua,自己写脚本动态调整。这种方式最灵活,但开发成本也最高。

流量控制

线上流量就像洪水,有时候会突然暴涨。

Nginx 提供了 limit_reqlimit_conn 模块来做流量控制。

请求频率限制

limit_req_zone $binary_remote_addr zone=req_limit:10m rate=10r/s;

server {
    location /api/ {
        limit_req zone=req_limit burst=20 nodelay;
        proxy_pass http://backend;
    }
}

这段配置的意思是:每个 IP 每秒最多 10 个请求。burst=20 表示可以临时超出 20 个,超出的请求会被延迟处理或者直接拒绝。

连接数限制

limit_conn_zone $binary_remote_addr zone=conn_limit:10m;

server {
    location /api/ {
        limit_conn conn_limit 10;
        proxy_pass http://backend;
    }
}

每个 IP 最多保持 10 个连接。

流量控制是保护后端服务的重要手段,配合监控告警,能在第一时间把异常流量挡在外面。

会话保持(Sticky Session)

前面说了 IP 哈希可以做会话保持,但它不够灵活。如果客户端 IP 经常变(比如移动网络),IP 哈希就不好使了。

Nginx 的第三方模块 nginx-sticky-module 提供了基于 Cookie 的会话保持:

upstream backend {
    sticky cookie srv_id expires=1h domain=.example.com path=/;
    server 192.168.1.10;
    server 192.168.1.11;
}

Nginx 第一次收到客户端请求时,会在响应里塞一个 srv_id 的 Cookie,标记后端服务器。下次客户端再来,带着这个 Cookie,Nginx 就知道该转给哪台。

这种方式比 IP 哈希更精准,但也更复杂,Cookie 处理本身就有点重。

现在的互联网公司更倾向于无状态服务 + Redis 共享 Session 的方案,把 Session 信息统一存到 Redis 里,后端服务器都可以访问。这样负载均衡算法就完全不用考虑会话保持了,简单粗暴。


实战中的坑,我踩过的那些

理论说再多,不如真实案例来得深刻。

坑一:上游服务器 keepalive 设置不当

有一段时间,我们线上服务响应慢得离谱,但 CPU、内存、磁盘都很正常。

排查了很久,最后发现是 Nginx 上游 keepalive 连接数不够。

upstream backend {
    server 192.168.1.10;
    keepalive 32;
}

这个 keepalive 32 意思是每个 worker 进程与后端保持 32 个长连接。我们当时后端有 100 台机器,Nginx 有 8 个 worker,总连接数就达到 25600 个。后端服务的文件描述符没调优,很快就耗尽了。

解决办法:根据实际情况调整 keepalive 参数,同时配合后端的 ulimit 优化。

坑二:proxy_buffer 设置不合理

Nginx 在转发响应给客户端的时候,会先把后端的响应缓存到内存里,然后发给客户端。这个缓存区的大小如果不合适,就会出问题。

proxy_buffer_size 4k;
proxy_buffers 8 16k;
proxy_busy_buffers_size 32k;

proxy_buffers 8 16k 表示分配 8 个 16K 的缓冲区。如果后端响应特别大(比如大文件下载),缓冲区不够用,数据会写到磁盘临时文件,性能就崩了。

我们曾经因为没设置 proxy_temp_path,临时文件被写到了系统盘的 /tmp,结果磁盘 IO 飙高,整个系统卡顿。

解决办法:把临时文件路径改到大容量磁盘,并合理调整缓冲区大小。

坑三:健康检查触发误判

有一次做促销活动,流量暴涨,后端某些接口响应时间从 50ms 涨到了 200ms。Nginx 的健康检查把响应时间超过阈值的服务器判定为"不可用",大量服务器被踢出集群,雪上加霜。

解决办法:调高健康检查的超时时间,配合业务特性设置合理的阈值。健康检查要稳定,不能太敏感。

坑四:DNS 解析缓存导致 upstream 失效

我们用域名配置 upstream:

upstream backend {
    server backend1.example.com;
    server backend2.example.com;
}

Nginx 在启动时解析一次域名,之后就缓存起来。如果后端 IP 变了(云服务器重启、迁移),Nginx 不知道,还在往老 IP 发请求。

解决办法:用 resolver 指令配置 DNS 服务器,并设置 valid 参数让 Nginx 定期重新解析。


Nginx 负载均衡的连接管理细节

讲到这里,必须再说说 Nginx 是怎么管理连接池的,这块很多人没注意过,但非常关键。

Nginx 作为反向代理,每收到一个客户端请求,就需要建立一个到后端的连接。频繁创建和销毁连接开销很大,所以 Nginx 会维护一个连接池

当请求处理完,连接不会被立即关闭,而是放回连接池等待复用。keepalive 参数控制的就是这个连接池的大小。

但这里有个细节:keepalive 连接是在 worker 进程内的,不是跨 worker 共享的

什么意思?假设 Nginx 有 4 个 worker,每个 worker 都独立维护自己的连接池。如果客户端连接被分配到 worker 1,但 worker 1 的连接池满了,Nginx 不会去借 worker 2 的连接,而是建立新连接。

这就是为什么 keepalive 数值要合理,太小会导致频繁建立新连接,太大会浪费资源。

另外,keepalive_requests 参数控制的是单个长连接最多处理多少个请求,达到上限后连接会被关闭。默认是 1000,可以根据实际情况调整。


七层 vs 四层:什么时候该用什么

Nginx 的负载均衡工作在应用层(OSI 七层模型),能解析 HTTP/HTTPS 协议,根据 URL、Header、Cookie 等信息做转发决策。

但有些场景下,四层负载均衡更合适。

四层负载均衡工作在传输层(TCP/UDP),只根据 IP 和端口做转发,不解析应用层数据。LVS、HAProxy 都是典型的四层负载均衡器。

什么时候用四层?

  • 需要处理非 HTTP 协议(MySQL、Redis、游戏服务器)
  • 对性能要求极高,希望减少协议解析开销
  • 后端服务规模特别大,七层代理成为瓶颈

什么时候用七层?

  • 需要根据 URL、Header 做智能路由
  • 需要 SSL 卸载
  • 需要做灰度发布、A/B 测试

实际生产中,经常是四层 + 七层组合使用。LVS 做最外层的流量分发,把请求转发给多个 Nginx 集群;Nginx 做七层处理,根据业务规则转发给具体的后端服务。

我们公司现在的架构就是这种模式,LVS 在最外层抗大流量,Nginx 在中间做业务路由,后面才是应用服务。


灰度发布:负载均衡的进阶玩法

说一个很实用的场景:灰度发布。

新版本上线,不可能一次性全量切换,万一有 bug 就完蛋了。常规做法是先让一小部分用户用新版本,观察没问题再逐步放量。

Nginx 怎么实现灰度?

基于 Cookie

split_clients $cookie_version $backend {
    10% "backend_v2";
    90% "backend_v1";
}

server {
    location / {
        proxy_pass http://$backend;
    }
}

split_clients 模块根据 Cookie 的值做概率分配,10% 的请求到新版本,90% 到老版本。

基于 IP 段

geo $backend {
    default backend_v1;
    192.168.1.0/24 backend_v2;
    10.0.0.0/8 backend_v2;
}

内网测试用户走新版本,外部用户走老版本。

基于 Header

map $http_x_green $backend {
    default backend_v1;
    "true" backend_v2;
}

通过自定义 Header 标记,比如前端在测试时加上 X-Green: true,请求就走到新版本。

灰度发布是个大学问,Nginx 的这几个指令只是最基础的实现。真正的灰度还要配合监控、告警、回滚机制,才能在生产环境安全使用。


性能调优:让 Nginx 飞起来

最后聊聊性能调优,这块实战性很强。

worker 进程数设置

worker_processes auto;  # 自动设置为 CPU 核心数

或者手动指定为核心数。

每个 worker 的连接数

events {
    worker_connections 10240;
}

理论最大值是 worker_processes * worker_connections,但实际上还要考虑文件描述符、内存等限制。

文件描述符限制

worker_rlimit_nofile 65535;

Linux 默认一个进程最多打开 1024 个文件描述符,太少了。生产环境必须调大。

Gzip 压缩

gzip on;
gzip_min_length 1k;
gzip_comp_level 6;
gzip_types text/plain text/css application/json application/javascript;

压缩响应数据,减少网络传输量。但压缩本身消耗 CPU,所以 gzip_comp_level 不要调太高,6 是一个平衡点。

缓存静态资源

location ~* \.(jpg|jpeg|png|gif|ico|css|js)$ {
    expires 7d;
    access_log off;
}

给静态资源设置缓存时间,减少重复请求。

日志优化

access_log /var/log/nginx/access.log main buffer=32k flush=5s;

日志写入是磁盘 IO 操作,加 buffer 能减少 IO 次数。但日志丢失的风险会增大,要权衡。


写在最后

Nginx 负载均衡这块,表面上看是几个配置指令的事,但深入下去,涉及进程模型、连接管理、算法原理、健康检查、流量控制等方方面面。

我见过太多人只会照着网上的模板配,出了问题就抓瞎。真正的运维不是配置工程师,而是要理解背后的原理,知道为什么这么配,出了事能快速定位。

负载均衡说到底是在多个资源之间合理分配任务,以达到最优的系统性能。这个思想在很多领域都通用,不仅仅是 Nginx。

如果你能把今天讲的这些内容真正消化掉,下次面试被问到 Nginx 负载均衡(虽然你说不要面试痕迹,但多学点没坏处),或者生产环境遇到相关问题,都能从容应对。

关注我,后续会写更多 Nginx 实战、调优、踩坑的内容。

下期打算写 Nginx 的内存管理机制,从源码层面剖析 Nginx 是怎么高效使用内存的,感兴趣的话点个在看,更新了你就能收到。


公众号:耕云躬行录
个人博客:躬行笔记

文章目录

博主介绍

热爱技术的云计算运维工程师,Python全栈工程师,分享开发经验与生活感悟。
欢迎关注我的微信公众号@运维躬行录,领取海量学习资料

微信二维码