Nginx负载均衡原理与实践:调度算法、健康检查与故障排查

Nginx负载均衡原理与实践:调度算法、健康检查与故障排查
在实际架构设计和运维工作中隔一段时间就会有人问负载均衡是什么很多人的第一反应是“多买几台服务器把请求分一分”。这句话从结果上看不算错但离真正解决问题还差得很远。负载均衡指的是把客户端请求按照一定策略分发到一组后端服务器上的过程它的目标不只是“让每台机器都干活”还包括提高整体吞吐量、消除单点故障、在后端节点异常时自动剔除流量以及在扩容缩容时保持服务稳定。如果只把负载均衡理解成“加台服务器”很快会遇到一类典型场景服务器加了流量并没有分散某台机器过载其他机器空闲后端宕机时请求依然源源不断打到故障节点用户登录状态随机丢失一会儿能登录一会儿不能。本文会从负载均衡的工作机制讲起再用 Nginx 搭建一个最小可复现的负载均衡环境接着解释调度算法、健康检查、会话保持等关键参数最后给出故障演练方法、常见问题排查链路和生产环境建议。学完后你能在小型项目里独立搭建负载均衡也能在面试或方案评审时把“为什么不能靠加服务器解决”讲清楚。1. 先纠正一个误解负载均衡不只是“加一台服务器”1.1 负载均衡到底解决什么问题负载均衡解决的是“多个服务节点如何协作对外提供同一份能力”的问题。一个系统一开始只有一台服务器用户量小的时候完全够用。但随着请求量上升单台机器的 CPU、内存、连接数、带宽都会成为瓶颈。加服务器是对的但问题随之而来请求到达入口时怎么知道该转发给哪一台如果按固定规则分配给某台机器那台机器又会过载。如果随机分发用户第二次请求可能落到另一台机器而会话状态留在上一台。如果某台后端已经挂了入口还继续往它转发用户看到的就是超时和 502。这些恰恰是负载均衡要解决的问题。所以负载均衡器的职责不只是“转发”它至少承担四件事调度根据轮询、权重、哈希或最少连接数等策略决定请求去哪个后端。健康检查探测后端是否存活发现问题后自动停止向其分发流量。故障转移后端不可用时把请求重新交给其他可用节点减少用户可见故障。伸缩管理新增或下线机器时不需要用户改配置入口只需更新节点列表。1.2 只加服务器的典型翻车场景只看“加服务器”这一层最常见的翻车场景有三个。第一个是流量倾斜。比如两台服务器配置相同但其中一台老旧、网络带宽低用简单轮询平均分发结果老机器先被打满新机器闲置。没有权重和健康反馈机制扩容等于白扩。第二个是会话丢失。用户第一次请求落到 A 机器登录状态存在 A 的本地内存里第二次请求被发到 B 机器B 没有这份状态于是用户被要求重新登录。没有会话保持或共享存储加再多的机器用户体验都是断裂的。第三个是故障放大。入口没有健康检查后端 B 已经宕机请求仍然按轮询分过去一部分表现为网络超时、502 错误、数据库连接被拖死甚至因为请求重试导致雪崩。这时候你会发现故障不是被负载均衡挡掉了而是被它放大了。1.3 负载均衡器的核心职责边界负载均衡器本身也是一个节点它负责接收流量、做决策、再转发。它不直接处理业务逻辑也不存储业务数据它的核心价值是用一套稳定的规则把后端资源组织起来。这带来一个重要推论负载均衡器自身不能成为新的单点。入门实验里一台 Nginx 就够了但生产环境至少要部署一对主备或集群配合虚拟 IP、DNS、云厂商的负载均衡产品使用。后面第 7 节会专门展开这部分。2. 负载均衡的分类与工作层级2.1 按实现位置划分DNS、四层、七层负载均衡可以出现在网络链路的很多位置常见的实现方式有三种。DNS 负载均衡是最轻量的一种。同一个域名配置多条 A 记录DNS 服务器根据策略返回不同 IP用户请求自然分散到不同机房或不同入口。它的优点是实现简单、不占额外资源缺点是 DNS 缓存生效慢故障切换依赖 TTL 刷新无法感知后端真实状态所以通常作为第一层粗粒度分流。四层负载均衡工作在网络传输层基于 IP、端口和协议类型做转发不解析 HTTP 内容。典型实现是 LVS、HAProxy 的 TCP 模式、云厂商的 NLB/SLB 四层监听。它性能高、延迟低适合数据库、Redis、消息队列、长连接等非 HTTP 协议的流量。七层负载均衡工作在应用层可以解析 HTTP 请求的 URL、Header、Cookie实现按路径转发、按域名分流、灰度发布、缓存等能力。典型实现是 Nginx、HAProxy 的 HTTP 模式、Traefik、云厂商的 ALB。它更灵活但解析成本更高极端高并发场景下吞吐低于四层方案。2.2 四层与七层负载均衡的差异理解四层和七层的差异能帮你做选型也能帮你定位问题。对比维度四层负载均衡七层负载均衡工作层级传输层IP端口应用层HTTP/HTTPS能看到的字段IP、端口、协议类型URL、Header、Cookie、请求体主要能力转发、会话保持、故障转移按路径路由、灰度、重写、缓存、认证性能高CPU 开销低相对低需要解析报文适用协议TCP、UDPHTTP、HTTPS、WebSocket典型场景数据库读写分离入口、Redis 集群入口Web 应用、API 网关、微服务对外入口常见产品LVS、HAProxy TCP 模式、云 NLBNginx、HAProxy HTTP 模式、云 ALB选型时的一般判断是业务协议是 HTTP 且需要精细路由优先考虑七层协议是 TCP/UDP 或对延迟敏感优先考虑四层大多数中大型系统会在入口用四层扛流量再在七层做业务路由两层配合使用。2.3 硬件、软件、云负载均衡怎么选硬件负载均衡的代表是 F5 等专用设备性能稳定、功能丰富但成本高、扩容不够灵活适合对性能和安全要求极高的核心机房场景。软件负载均衡的代表是 Nginx、HAProxy、LVS部署在普通 Linux 服务器上成本低、可控性强是中小团队的主流选择。云负载均衡指的是云平台提供的 LB 产品如 SLB、ALB、NLB按量付费、自带高可用和健康检查省去自己维护入口的高可用成本。对于新手建议先掌握软件方案中的 Nginx它配置直观、资料多、能同时演示四层和七层能力也能满足大多数 Web 场景。云负载均衡的用法虽然和 Nginx 不同但调度算法、健康检查、会话保持这些核心概念完全相通。3. 用 Nginx 搭建最小负载均衡环境3.1 环境准备与版本说明本文使用以下环境项目建议配置操作系统Ubuntu 22.04 LTS 或 CentOS 7Linux 均可Nginx1.18 及以上建议使用系统包管理安装后端服务Python 3用于模拟两个 HTTP 后端节点客户端curl用于验证请求分发与故障表现如果你用的是其他 Linux 发行版安装命令换成对应的包管理工具即可。生产环境落地前先确认 Nginx 版本和系统内核版本不同版本的参数默认值可能有差异。下面示例用于说明思路实际项目要结合自己的服务器地址、端口和目录调整。3.2 准备两个后端服务为了让负载均衡效果可观察两个后端服务要能返回不同的标识。这里用一个简单的 Python HTTP 服务模拟两个进程分别监听 8081 和 8082 端口。# backend.py import os from http.server import HTTPServer, BaseHTTPRequestHandler PORT int(os.environ.get(PORT, 8081)) class Handler(BaseHTTPRequestHandler): def do_GET(self): body backend-%d % PORT self.send_response(200) self.send_header(Content-Type, text/plain; charsetutf-8) self.send_header(Content-Length, str(len(body))) self.end_headers() self.wfile.write(body.encode()) def log_message(self, fmt, *args): print([backend-%d] %s % (PORT, args[0])) if __name__ __main__: server HTTPServer((, PORT), Handler) print(backend running on %d % PORT) server.serve_forever()分别启动两个后端进程PORT8081 python3 backend.py PORT8082 python3 backend.py在另一个终端验证两个节点都能单独访问curl http://127.0.0.1:8081/ curl http://127.0.0.1:8082/预期输出分别是backend-8081和backend-8082。这一步很重要它能确认后端本身没有问题后续出现 502 时才不会误判到后端。注意不要只验证程序能启动还要验证请求能返回预期内容。后端不返回正确响应负载均衡做得再漂亮也没有意义。3.3 编写 Nginx 负载均衡配置安装 Nginx# Ubuntu / Debian sudo apt update sudo apt install -y nginx # CentOS / RHEL sudo yum install -y nginx在/etc/nginx/conf.d/下新建一个配置文件命名为lb_demo.conf。Nginx 主配置中通常会 include 这个目录所以放在这里会被自动加载。配置内容如下upstream backend_cluster { server 127.0.0.1:8081; server 127.0.0.1:8082; } server { listen 80; server_name lb.demo.local; location / { proxy_pass http://backend_cluster; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } }这段配置的关键点有三个。第一upstream定义了一个后端服务器组名字叫backend_cluster里面包含两个节点。Nginx 默认使用轮询策略会把请求轮流分给 8081 和 8082。第二location /匹配所有请求proxy_pass把请求转发给backend_cluster这个上游组。注意proxy_pass这里写的是http://backend_cluster末尾没有斜杠、没有路径表示“保持客户端原始 URI 转发”。第三三个proxy_set_header负责传递客户端信息。负载均衡模式下后端看到的来源 IP 默认是 Nginx 服务器的 IP而不是真实客户端 IP。通过X-Real-IP和X-Forwarded-For把头信息透传过去后端才能拿到真实客户端地址。3.4 启动并验证请求分发修改完配置后先做语法检查sudo nginx -t如果输出syntax is ok和test is successful说明配置没有语法问题。然后重新加载配置sudo nginx -s reload不要使用nginx -s stop再start那会导致连接中断。reload会平滑加载新配置不中断现有请求处理。验证配置是否生效curl -H Host: lb.demo.local http://127.0.0.1/ curl -H Host: lb.demo.local http://127.0.0.1/ curl -H Host: lb.demo.local http://127.0.0.1/因为默认是轮询预期输出会交替出现backend-8081 backend-8082 backend-8081如果出现这个结果说明最小负载均衡环境已经跑通。请求经过 Nginx 的upstream后按轮询策略分发到了两个后端节点。4. 核心参数与调度算法详解4.1 upstream 的调度算法Nginx 的 upstream 默认支持几种调度方式选择哪种取决于业务场景。调度方式配置写法适用场景注意事项轮询默认无需额外配置后端配置均匀、无状态服务简单但不能感知后端实时负载加权轮询server 127.0.0.1:8081 weight3;后端机器性能不一时权重是比例不是绝对 QPSIP haship_hash;需要按客户端 IP 固定到节点客户端集中在一个出口时容易倾斜最少连接least_conn;请求处理时间差异大的场景需要后端能暴露连接数信息加权轮询是最实用的入门配置。比如 8081 机器性能是 8082 的两倍可以这样设置upstream backend_cluster { server 127.0.0.1:8081 weight2; server 127.0.0.1:8082 weight1; }此时 Nginx 会尽量按 2:1 的比例把请求分给两个节点。注意 weight 只影响调度比例不代表绝对的并发能力生产环境仍然需要根据压测数据调整。如果后端需要按用户维度固定节点可以使用ip_hashupstream backend_cluster { ip_hash; server 127.0.0.1:8081; server 127.0.0.1:8082; }ip_hash对客户端 IP 做哈希同一 IP 的请求通常会落到同一个后端。它适合会话存储在本地的场景但有一个明显缺陷如果大量用户来自同一个出口 IP比如公司出口 NAT请求会全部集中到一台后端哈希策略反而放大负载不均。4.2 健康检查与故障转移配置社区版 Nginx 的健康检查是“被动式”的只有当请求转发到某个节点并失败时Nginx 才会根据max_fails和fail_timeout对该节点做降权处理。upstream backend_cluster { server 127.0.0.1:8081 max_fails2 fail_timeout10s; server 127.0.0.1:8082 max_fails2 fail_timeout10s; }参数含义max_fails允许的最大失败次数默认值为 1。fail_timeout统计失败次数的时间窗口同时也是节点被标记不可用后的冷却时间默认值为 10 秒。以max_fails2 fail_timeout10s为例在 10 秒内如果 Nginx 向 8081 转发请求失败 2 次就会在接下来的 10 秒内认为 8081 不可用不再向其转发请求。10 秒后会尝试重新探测如果请求成功节点重新恢复。这种被动健康检查有两个局限只有真实请求失败才会触发摘除空闲节点的故障无法被及时发现。摘除条件是“失败次数”如果失败请求本来就少发现故障的时间会被拉长。生产环境如果使用云厂商负载均衡产品或 Nginx Plus可以配置主动健康检查周期性地向后端发送探测请求。开源方案中可以考虑使用 HAProxy 做前置四层入口或在 Nginx 前增加云负载均衡产品用产品自带的主动探测能力补齐这块短板。4.3 会话保持的取舍负载均衡把请求分散到多台后端后最直接的问题是 Session 不一致。解决这个问题有三条路线方案做法优点缺点客户端存储使用 JWT、Token状态放在客户端后端无状态扩容容易存在泄露风险需要签名和过期机制会话保持通过 ip_hash 或 sticky 把同一用户固定到同一节点改动小老系统兼容好节点下线时用户会话仍会丢节点负载可能不均集中存储把 Session 放到 Redis 等共享存储节点故障无感真正无状态引入额外依赖增加一次网络访问延迟在实际项目中如果从零开始设计推荐优先做集中存储或客户端 Token而不是依赖 ip_hash。因为 ip_hash 只解决了“同一 IP 去同一节点”的问题并没有解决“节点宕机后会话怎么办”。如果用户 IP 变化比如手机从 WiFi 切到 4G哈希结果变化会话仍然会丢。如果必须做会话保持且后端节点数量少、变化不频繁可以先用 ip_hash 顶住再逐步把会话迁移到 Redis。这样做的好处是改造风险小每步都可以独立验证。4.4 超时、重试与连接复用负载均衡配置中最容易在生产环境出问题的三个参数是连接超时、读取超时和重试策略。location / { proxy_pass http://backend_cluster; proxy_connect_timeout 5s; proxy_read_timeout 30s; proxy_send_timeout 30s; proxy_next_upstream error timeout http_502 http_503; }参数含义proxy_connect_timeout与后端建立 TCP 连接的超时时间默认 60 秒。对于内部服务推荐设置为 3 到 5 秒避免后端不可达时请求长时间挂起。proxy_read_timeout两次读操作之间的超时时间不是“总读取时间”。如果后端处理慢这个值需要适当调大。proxy_send_timeout两次写操作之间的超时时间影响上传场景。proxy_next_upstream当请求与当前后端通信失败时是否尝试下一个后端。常见配置值包括error、timeout、http_502、http_503、http_504。重试是把双刃剑。proxy_next_upstream能提升成功率但如果后端因为“更新数据”失败导致请求已经在后端执行了一半重试到另一台机器可能造成数据重复写入。对于写操作需要业务接口保证幂等否则不要随意开启重试。连接复用方面可以在 upstream 中配置upstream backend_cluster { server 127.0.0.1:8081; server 127.0.0.1:8082; keepalive 32; }keepalive 32表示 Nginx 会为每个 worker 进程缓存最多 32 个空闲的、到后端的 keepalive 连接减少频繁创建 TCP 连接的开销。注意这只影响 Nginx 到后端的连接不影响客户端到 Nginx 的连接。配置后还需要在location里加上proxy_http_version 1.1; proxy_set_header Connection ;后端使用 HTTP/1.1 长连接时必须清空Connection头否则后端可能认为连接不可复用。5. 运行验证与故障演练5.1 验证轮询与权重分发启动两个后端和 Nginx 后通过循环请求观察分发结果for i in $(seq 1 6); do curl -s -H Host: lb.demo.local http://127.0.0.1/ echo done默认轮询的预期输出是8081、8082交替。修改为weight2后在相同请求次数下输出会偏向 8081。这个实验能直观说明负载均衡的结果受调度算法和权重直接影响不是“把所有机器都加进去”就自动均匀。5.2 模拟后端宕机让 8082 进程停掉kill 8082 进程 PID然后持续发起请求for i in $(seq 1 10); do curl -s -o /dev/null -w %{http_code}\n -H Host: lb.demo.local http://127.0.0.1/ done在配置max_fails2 fail_timeout10s的情况下最初的几个请求可能还会出现 502。失败达到阈值后Nginx 会把 8082 标记为不可用后续请求全部由 8081 返回 200。大约 10 秒后Nginx 会再次尝试把请求发给 8082如果它已经恢复请求会重新被分发过去。这个实验要重点观察两个现象故障摘除有延迟不是“后端一挂下一跳立即全部切走”。摘除和恢复都依赖参数配置生产环境不能默认值直接上线。5.3 从日志和响应头定位请求路径排查负载均衡问题时光看 HTTP 状态码不够还需要知道每个请求到底被转发给了谁。在 Nginx 的 access log 中增加 upstream 相关字段是有效手段。在http块或server块中配置log_format upstream_log $remote_addr - $upstream_addr [$time_local] $request $status $upstream_status $request_time $upstream_response_time; access_log /var/log/nginx/upstream.log upstream_log;字段含义$upstream_addr实际处理请求的后端地址可能是多个逗号分隔。$upstream_status后端返回的状态码可能和$status不同。$request_time从 Nginx 收到请求到响应完成的总时间。$upstream_response_time后端处理响应的时间。刷几个请求后查看日志sudo tail -n 20 /var/log/nginx/upstream.log你会看到每个请求对应的后端 IP、状态码和处理耗时。这样就能回答“这个请求到底落在哪台机器上”的问题排错效率会高很多。6. 常见问题排查与排错链路6.1 配置修改后不生效这是负载均衡配置中最常见的问题通常不是 Nginx 的逻辑错了而是配置没有加载。排查顺序检查配置文件是否正确加载/etc/nginx/nginx.conf中是否 include 了你所在的目录。检查语法执行nginx -t有语法错误时 reload 会失败。检查是否执行了 reload修改配置后必须执行nginx -s reload或systemctl reload nginx只保存文件不会生效。检查是否改对了文件/etc/nginx/sites-enabled/和/etc/nginx/conf.d/都可能有配置注意重复配置导致同一个 server 块被覆盖。预防做法每次修改配置后固定执行两条命令sudo nginx -t sudo nginx -s reload的作用是语法检查通过后才执行 reload避免把错误配置加载进去。6.2 出现 502 Bad Gateway现象是客户端收到 502说明 Nginx 无法从上游获取有效响应。按以下顺序排查后端是否存活直接curl http://127.0.0.1:8082/如果连接失败先处理后端进程。端口是否正确ss -lntp | grep 8081查看端口监听状态。后端是否在别的机器上IP 地址、防火墙规则、安全组是否放通了 Nginx 到后端的端口。超时是否设置过小proxy_connect_timeout太小而服务启动慢也会产生 502。查看错误日志/var/log/nginx/error.log其中会明确指出 connect failed、upstream timed out 或 no live upstreams。no live upstreams while connecting to upstream这条错误直接说明upstream 中的所有节点都因为失败次数达到阈值被标记为不可用。这时要检查后端为什么全部不可用而不是只调整 Nginx 参数。6.3 登录状态随机丢失现象是用户访问站点时第一次能登录刷新后要求重新登录多次刷新才能稳定。根因通常是轮询策略把同一个用户的请求分散到了不同后端而 Session 只存在其中一台的本地内存中。检查方式查看 access log 的$upstream_addr确认同一用户的连续请求是否落在不同后端。处理方式有三种选择临时方案使用ip_hash让同一 IP 固定到同一节点但要注意出口 IP 集中的问题。推荐方案把 Session 迁移到 Redis 等共享存储让后端真正无状态。长期方案接口层改用 Token 或 JWT客户端持有状态后端只做校验。6.4 流量倾斜导致负载不均现象是某台后端 CPU 很高其他后端却很空闲或者某个节点请求量明显多于其他节点。可能原因和检查方式如下问题现象常见原因检查方式处理建议请求量集中于一个节点使用 ip_hash 且客户端出口 IP 集中查看 access log 来源 IP 分布改用轮询或 least_conn后端做无状态改造节点处理时间差异大后端配置不同或存在慢请求对比各节点 CPU、耗时指标设置 weight或改用 least_conn长连接集中在某一节点只配置了轮询未考虑连接持续时间查看连接数和 upstream 状态开启 keepalive结合 least_conn 调度某些路径天然热点接口设计缺陷缓存未生效分析慢日志和接口调用量优先优化接口、加缓存负载均衡只是兜底负载不均的排查原则先看数据再调参数。没有 access log 和后端监控指标任何配置调整都是猜。7. 生产环境最佳实践与扩展方向7.1 学习环境与生产环境的差异清单第 3 节的示例是学习环境的最小配置直接照搬到生产环境会出问题。下面这张清单列出了关键差异维度学习环境生产环境配置文件写在 conf.d 下按项目拆分配置使用模板管理纳入版本控制日志默认 access_log自定义日志格式记录 upstream 字段按天切割健康检查被动模式即可使用云负载均衡或 Nginx Plus 的主动探测会话后端写死不关心Redis 共享存储或 Token 化负载均衡器高可用单台 Nginx双机热备 虚拟 IP或云负载均衡产品安全无HTTPS 终结、请求大小限制、访问控制、WAF 前置回滚方案无保留上一版本配置文件发布后监控错误率监控手动 curl接入告警关注 5xx 率、upstream 失败率、延迟指标7.2 负载均衡器自身不能是单点很多团队在做负载均衡时只考虑后端集群却忽略了一个事实入口的 Nginx 本身也是单点。它一挂整个集群都不可达。生产环境的常见做法是双机主备。两台 Nginx 节点共享一个虚拟 IPKeepalived 负责监控和切换。正常情况下虚拟 IP 绑定在主节点主节点故障后备用节点接管虚拟 IP继续对外提供服务。客户端不需要感知切换只要在短时间内重新建立连接即可。如果使用云平台直接把负载均衡器换成云厂商的托管产品由平台保证入口高可用这样可以把精力放在后端的业务扩展和稳定性上。这是大多数中小团队更实际的选型。7.3 再往前走服务发现、自动扩缩容与全链路可观测负载均衡解决了“请求如何分到一组固定节点”的问题但随着节点规模扩大手动维护 upstream 里的服务器列表会越来越痛苦。每次扩容都要登录入口机器改配置、reload既容易出错也无法应对流量突增。扩展方向有三个按优先级排序第一服务发现。把后端节点的注册和发现交给服务注册中心负载均衡器动态获取可用节点列表。开源生态中Consul、Etcd、Nacos 配合 Nginx 的动态上游模块可以实现这一点。第二自动扩缩容。当负载均衡的指标达到阈值时由编排系统自动增加或减少后端实例并自动同步到负载均衡配置中。容器化部署下配合 Kubernetes 的 Service 和 Ingress这一过程可以基本自动化。第三全链路可观测。负载均衡只是链路的一环真正排查问题时需要把“客户端发出请求、入口转发、后端处理、依赖服务返回”整条链路串起来。为每个请求生成 Trace ID在负载均衡层透传给后端日志中记录$request_id和$upstream_addr配合链路追踪工具才能快速定位问题节点。负载均衡不是终点而是整个分布式系统入口处的一个组件。先理解它解决了什么问题、哪些问题它解决不了再一步步补充服务发现、可观测性和自动化扩缩容能力这样的演进路径对任何规模的团队都适用。对新手来说最有价值的练习不是把配置背下来而是亲手完成一次“后端宕机后流量自动摘除再恢复”的演练并把这个实验的安全边界、触发条件和验证方式记录清楚。

最新新闻

日新闻

周新闻

月新闻