Skip to content

Nginx负载均衡、健康判断与重试全过程

负载均衡不是“轮流找一台服务器”这么简单。一次代理请求至少要经历:选择候选节点、检查节点是否可用、建立或复用连接、发送请求、判断失败类型、决定是否重试、更新节点运行状态,再把响应返回客户端。任何一步设计错误,都可能造成流量倾斜、重复下单、重试风暴或数据库雪崩。

学习目标

学完后应能:

  1. 区分四层负载均衡和七层HTTP负载均衡。
  2. 解释Nginx upstream从配置解析到请求选节点的完整过程。
  3. 区分加权轮询、least_conn、ip_hash、通用hash和随机两选一。
  4. 解释weight为什么不是每十个请求严格按比例分配。
  5. 说明被动失败判断、max_failsfail_timeout和主动健康检查的区别。
  6. 解释 proxy_next_upstream 何时重试以及非幂等请求的重复执行风险。
  7. 区分upstream keepalive缓存、活跃连接数和后端总容量。
  8. 解释Session粘滞为什么不能代替共享Session和无状态设计。
  9. 处理扩缩容、DNS变化、慢节点、连接倾斜和重试风暴。
  10. 使用访问日志与upstream变量定位502、504、流量不均和单节点P99异常。

一、负载均衡解决什么,不解决什么

mermaid
flowchart TD
    A["客户端只访问统一入口"] --> B["Nginx"]
    B --> C["order-api-1"]
    B --> D["order-api-2"]
    B --> E["order-api-3"]

它主要解决:

  • 把请求分给多个后端实例。
  • 横向扩容入口后的业务处理能力。
  • 在部分节点失败时尝试其他节点。
  • 按权重、连接数或Key实现不同分配策略。
  • 隐藏后端实例地址。

它不自动解决:

  • Nginx自身单点。
  • 数据库容量不足。
  • Java线程池、连接池耗尽。
  • Session共享。
  • 非幂等请求重试。
  • 数据一致性。
  • 所有类型的健康检查。
  • 节点跨机房和跨可用区故障域。

二、四层和七层负载均衡

类型主要依据能看到什么常见用途
四层IP、端口、TCP/UDP连接不必解析完整HTTP高吞吐TCP转发、TLS透传
七层Host、URI、Header、Cookie等HTTP信息理解HTTP请求和响应API路由、Header治理、缓存、限流

本页主要讲Nginx HTTP upstream,即七层代理。Nginx stream模块可做TCP/UDP代理,但健康、重试和日志语义不同,不能把HTTP指令直接搬过去。

三、一次请求的完整选节点过程

mermaid
flowchart TD
    A["请求命中proxy_pass"] --> B["找到upstream配置"]
    B --> C["按算法生成候选节点"]
    C --> D["跳过down或暂时不可用节点"]
    D --> E["复用空闲连接或新建TCP连接"]
    E --> F["发送上游请求"]
    F --> G{"是否获得可接受响应"}
    G -- "是" --> H["转发响应并更新统计"]
    G -- "否" --> I["按失败类型更新节点状态"]
    I --> J{"重试策略、次数和时间是否允许"}
    J -- "是" --> K["选择另一个候选节点"]
    K --> E
    J -- "否" --> L["向客户端返回502/504等错误"]

选择算法只负责“先尝试谁”,并不单独决定:

  • 连接能否建立。
  • 请求是否已经写给后端。
  • 后端是否已经提交事务。
  • 哪些响应算失败。
  • 是否可以重试。
  • 客户端最终看到什么。

四、最小upstream配置

nginx
upstream order_api {
    server 10.0.1.11:8080;
    server 10.0.1.12:8080;
}

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

    location /api/ {
        proxy_pass http://order_api/;
    }
}

启动或reload时,Nginx解析upstream名称、节点地址和参数,为每个Worker建立运行时选择结构。域名解析时机和动态更新能力取决于配置写法、Nginx版本、resolver和商业/开源能力,不能假定DNS变化一定实时生效。

五、默认加权轮询

未声明其他算法时,使用加权轮询思路:

nginx
upstream order_api {
    server 10.0.1.11:8080 weight=1;
    server 10.0.1.12:8080 weight=1;
}

长期、节点持续健康且请求成本接近时,两台节点获得的请求数量趋近1:1。

5.1 为什么不是简单下标加一

现代Nginx加权轮询会维护节点权重状态,让不同权重在短窗口内也尽量平滑分布,而不是先把大权重节点连续打满再切换。

例如权重:

text
A=5,B=1,C=1

理想长期比例约5:1:1,但不保证任意连续7个请求都严格如此,因为还受到:

  • 节点失败和恢复。
  • 多Worker各自选择状态。
  • reload。
  • 并发时序。
  • 请求重试。
  • 长连接和请求耗时差异。

六、weight表示能力比例,不是绝对QPS

nginx
upstream order_api {
    server 10.0.1.11:8080 weight=3;
    server 10.0.1.12:8080 weight=1;
}

在稳定条件下,请求数量趋近3:1。但weight不理解每个请求成本:

  • /health可能1ms。
  • /report/export可能30s。
  • 一个请求只查缓存。
  • 另一个请求执行复杂SQL。

请求数3:1不等于CPU、内存和数据库负载3:1。业务差异很大时,还需要路由拆分、限流、异步化和容量模型。

七、least_conn最少连接

nginx
upstream order_api {
    least_conn;
    server 10.0.1.11:8080;
    server 10.0.1.12:8080;
}

它优先选择当前活跃连接相对更少的节点,适合请求耗时差异较大、长连接较多的场景。

7.1 为什么不等于最小CPU

连接少的节点不一定最空闲:

  • 一个连接正在执行超重报表。
  • 大量请求已进入Java内部队列,但Nginx连接状态不能完整代表线程池压力。
  • GC暂停时连接数可能不高但响应极慢。
  • 数据库连接池已经耗尽。

least_conn只使用Nginx可观察的连接状态,不是全链路负载感知。

7.2 权重仍可能参与

节点配置weight时,最少连接算法会结合权重比较相对负载,不应简单理解为“谁绝对连接数最小就选谁”。

八、ip_hash会话粘滞

nginx
upstream order_api {
    ip_hash;
    server 10.0.1.11:8080;
    server 10.0.1.12:8080;
}

相同客户端地址通常映射到同一节点,可降低本地Session不共享带来的影响。

8.1 为什么会严重倾斜

大量用户可能经过同一个:

  • 企业NAT出口。
  • 移动运营商代理。
  • CDN或上层负载均衡。
  • API Gateway。

Nginx如果看到的地址都是同一个代理IP,很多用户会被哈希到同一节点。必须先建立可信真实IP链路,但即使拿到真实IP,移动网络切换也会改变地址。

8.2 为什么不能解决Session设计

节点宕机、扩缩容或映射变化时,请求仍会切到其他节点。本地Session可能丢失。商业系统更常用:

  • 无状态Token。
  • Redis等共享Session。
  • 业务状态进入数据库或专门状态服务。

粘滞只能减少切换,不提供Session高可用和一致性。

九、通用hash与一致性hash

nginx
upstream cache_backend {
    hash $request_uri consistent;
    server 10.0.2.11:8080;
    server 10.0.2.12:8080;
    server 10.0.2.13:8080;
}

可按URI、租户、用户等Key映射节点。consistent用于降低节点增减时Key大规模迁移,常见于缓存分片或希望同Key落到同节点的场景。

风险:

  • 热Key仍会压到单节点。
  • Key分布不均会造成倾斜。
  • 节点容量不同需要权重与数据设计。
  • 后端是有状态分片时,Nginx哈希不能代替数据迁移协议。
  • Key包含敏感或不稳定值会影响审计与命中。

旧教程中的 url_hashhash_method crc32常来自第三方模块;现代标准能力应使用当前Nginx版本支持的 hash ... consistent,并先执行 nginx -t

十、随机与随机两选一

部分现代Nginx版本支持类似:

nginx
upstream order_api {
    random two least_conn;
    server 10.0.1.11:8080;
    server 10.0.1.12:8080;
    server 10.0.1.13:8080;
}

思路是随机抽取少量候选,再从候选中按连接状态选择,减少超大集群全量比较成本,同时获得较好负载分布。实际语法与版本支持必须通过目标版本文档、nginx -Vnginx -t确认。

十一、fair和least_time不能混为一谈

旧文章常写:

nginx
fair;

它通常来自第三方模块,不是所有开源Nginx二进制都支持。NGINX Plus还提供基于响应时间等指标的商业能力,例如least_time,但也不能假定社区版具备。

遇到:

text
unknown directive "fair"

应检查:

bash
nginx -V
nginx -T

确认模块来源、版本、维护状态和供应链风险,而不是下载来历不明的二进制覆盖生产Nginx。

十二、节点参数逐项理解

nginx
upstream order_api {
    server 10.0.1.11:8080 weight=2 max_fails=3 fail_timeout=10s max_conns=200;
    server 10.0.1.12:8080 weight=1 max_fails=3 fail_timeout=10s max_conns=100;
    server 10.0.1.13:8080 backup;
}
参数主要含义常见误区
weight参与选节点的相对权重不代表绝对QPS或CPU比例
max_fails在窗口内允许的失败尝试次数不会主动定时探测业务健康
fail_timeout失败统计窗口及暂时不可用时间相关边界不是所有请求总超时
max_conns限制到节点的活跃连接数量空闲keepalive和多Worker统计边界要按版本验证
backup主节点不可用时才使用某些算法不兼容
down人工标记不参与选择修改后要reload并观察

指令兼容性会随算法、版本和开源/商业发行版变化,配置前必须查目标版本文档。

十三、被动健康判断是什么

开源Nginx常用请求流量本身做被动判断:

text
真实请求选择节点
→ 建连失败、超时或命中配置的失败条件
→ 记录一次失败尝试
→ 窗口内达到max_fails
→ 节点在fail_timeout相关时间内暂时不参与普通选择
→ 之后重新尝试节点

它不是独立健康检查线程定时请求 /health。没有业务请求时,就没有新的被动探测证据。

13.1 哪些响应算失败

由协议模块和 proxy_next_upstream 等配置共同决定。连接错误、超时通常属于失败;HTTP 500、502、503、504等是否触发下一节点,要看配置。普通业务404通常不应把节点判死,因为它可能是合法业务响应。

13.2 单节点upstream边界

只有一个节点时,为避免把唯一节点永久判为不可用,某些失败参数会按特殊规则处理。不能用单节点实验推断多节点摘除行为。

十四、主动健康检查不是开源版通用内置

旧配置:

nginx
check interval=3000 rise=2 fall=5 timeout=1000 type=http;

通常来自第三方健康检查模块,不是标准开源Nginx通用指令。NGINX Plus提供商业主动健康检查能力;不同发行版或平台也可能提供自己的模块。

主动检查思路:

mermaid
flowchart TD
    A["定时向健康URI发送探测"] --> B{"响应状态和内容是否符合规则"}
    B -- "连续失败达到阈值" --> C["标记不可用"]
    B -- "成功" --> D["累计恢复成功次数"]
    D --> E{"达到恢复阈值"}
    E -- "是" --> F["重新加入流量"]

健康URI也不能只返回进程存活。应区分:

  • liveness:进程是否活着。
  • readiness:是否准备接业务流量。
  • dependency:关键依赖是否可用。
  • 外部业务探测:完整入口是否成功。

把所有下游都塞进readiness可能引发级联摘流;完全不检查关键初始化又会让未准备节点接流量。

十五、max_fails与fail_timeout实例

nginx
server 10.0.1.11:8080 max_fails=3 fail_timeout=10s;

可理解为:在相关10秒窗口内,若符合失败条件的尝试达到3次,节点会在一段相关时间内被视为不可用,之后再尝试恢复。精确计时和状态共享边界以当前版本实现与文档为准。

它不是:

  • 每10秒主动检查一次。
  • 节点失败后永久删除。
  • 所有HTTP 4xx/5xx都计失败。
  • 保证请求不会返回错误。
  • 保证后端业务没有执行。

十六、proxy_next_upstream决定哪些失败可换节点

示例:

nginx
location /api/ {
    proxy_pass http://order_api;
    proxy_connect_timeout 2s;
    proxy_read_timeout 15s;

    proxy_next_upstream error timeout http_502 http_503 http_504;
    proxy_next_upstream_tries 2;
    proxy_next_upstream_timeout 5s;
}

含义:

  • proxy_next_upstream:哪些情况允许尝试下一节点。
  • tries:尝试次数上限,具体计数包含初次尝试的语义要按版本文档确认。
  • timeout:跨多次尝试的总时间边界之一。

重试只能在仍可安全切换的阶段发生。响应已经部分发送给客户端后,通常不能透明改换另一个后端重新返回完整响应。

十七、为什么重试可能造成重复下单

mermaid
sequenceDiagram
    participant N as Nginx
    participant A as App A
    participant D as Database
    participant B as App B
    N->>A: POST /orders
    A->>D: 插入订单并提交
    D-->>A: 提交成功
    A--xN: 响应返回前连接断开
    N->>B: 重试同一个POST
    B->>D: 再次插入订单

从Nginx视角看,第一次没有拿到有效响应;从业务视角看,数据库可能已经提交。若直接重试POST,可能重复创建订单、重复扣款或重复发消息。

治理:

  • 非幂等请求默认谨慎重试。
  • 订单、支付使用幂等Key。
  • 数据库唯一约束兜底。
  • 状态机拒绝非法重复流转。
  • 消息和外部调用有去重与补偿。
  • 记录每次upstream尝试链路。

配置 non_idempotent 允许更多非幂等请求重试时风险更高,不能只为降低错误率而开启。

十八、GET也不一定天然安全

HTTP语义上GET应安全、幂等,但旧系统可能错误地用GET执行写操作,例如:

text
GET /coupon/claim
GET /task/run

Nginx按方法语义重试时无法理解业务副作用。因此首先要修正API设计,并在数据库与业务层实现幂等,而不是把安全完全交给代理层猜测。

十九、重试风暴如何形成

text
后端容量下降50%
→ 原请求开始超时
→ Nginx重试到其他节点
→ 剩余节点收到原流量加重试流量
→ 延迟进一步升高
→ 更多请求触发重试
→ 所有节点和数据库被打满

限制:

  • 少量明确重试次数。
  • 跨尝试总超时。
  • 入口限流。
  • 熔断和降级。
  • 后端容量保护。
  • 客户端、网关、服务SDK避免层层重复重试。

如果客户端、Nginx、Feign和数据库驱动各重试3次,最坏尝试次数可能乘法放大。

二十、upstream keepalive完整边界

nginx
upstream order_api {
    server 10.0.1.11:8080;
    server 10.0.1.12:8080;
    keepalive 32;
}

location /api/ {
    proxy_http_version 1.1;
    proxy_set_header Connection "";
    proxy_pass http://order_api;
}

典型过程:

text
请求选择节点
→ 尝试从当前Worker空闲连接缓存取连接
→ 有可用连接则复用
→ 没有则新建TCP连接
→ 请求完成
→ 连接符合条件时放回空闲缓存

keepalive 32通常表示每个Worker为该upstream缓存的空闲连接数量,不是:

  • 最大总连接数。
  • 最大并发请求数。
  • 所有Worker共享的32条连接。
  • 后端连接池保护上限。

4个Worker理论上可能各有自己的空闲缓存。还要考虑活跃连接、后端keep-alive超时和连接关闭竞争。

二十一、为什么Keep-Alive可能产生连接倾斜

扩容一个新后端后,旧节点上已有大量可复用连接,新节点连接较少。短时间内:

  • 旧连接继续被复用。
  • 新节点尚未获得同等连接池热度。
  • 长连接不会立刻迁移。
  • HTTP/2单连接可承载多个stream时连接数更不能代表请求比例。

因此扩容后流量不会保证瞬时均匀,需要观察请求数、连接数和响应时间,而不是只看upstream配置已包含新IP。

二十二、max_conns不是完整后端保护

nginx
server 10.0.1.11:8080 max_conns=200;

它用于限制到节点的活跃连接数量,但:

  • 多Worker是否共享计数取决于共享zone、版本和配置。
  • 空闲keepalive连接可能不计入活跃连接,却仍占后端FD。
  • HTTP/2多路复用改变连接与请求关系。
  • Java线程池、队列和数据库连接池的安全并发不一定等于200。

后端保护还需应用限流、线程池隔离、连接池、熔断和数据库容量。

二十三、共享zone解决什么

部分upstream能力可使用共享内存zone,让Worker共享节点配置和运行状态:

nginx
upstream order_api {
    zone order_api_zone 64k;
    server 10.0.1.11:8080;
    server 10.0.1.12:8080;
}

是否需要zone、哪些状态共享、动态DNS和商业能力如何依赖zone,会随版本与功能变化。不能只为“性能”盲目加zone;应根据具体指令文档配置大小并验证reload和状态。

二十四、DNS与动态服务发现

静态域名:

nginx
server app.internal:8080;

不一定意味着每个请求都重新查询DNS。很多配置在启动或reload时解析,后端IP变化后可能继续使用旧地址。

现代版本可结合resolver、resolve参数和共享zone实现一定动态更新,但开源/商业版本、语法和支持时间不同,必须查目标版本。

需要验证:

  • DNS TTL。
  • NXDOMAIN和超时。
  • 多A/AAAA记录。
  • 节点删除与新增。
  • 旧Keep-Alive连接。
  • reload行为。
  • Docker/Kubernetes服务发现边界。

在Kubernetes中通常代理Service稳定地址,而不是直接把短生命周期Pod IP静态写进Nginx。

二十五、backup与故障域

nginx
upstream order_api {
    server 10.0.1.11:8080;
    server 10.0.1.12:8080;
    server 10.0.2.11:8080 backup;
}

backup在主节点不可用时参与,但它不自动保证:

  • 备用节点数据与主节点一致。
  • 备用机房数据库容量足够。
  • 网络与DNS能切换。
  • Session和缓存已预热。
  • 某些hash/random算法兼容backup。

备用容量长期不接流量还可能存在配置漂移。生产应定期演练和小流量验证。

二十六、slow_start与恢复洪峰

节点恢复后如果立即按完整weight接流量,JIT、连接池、缓存和类加载可能尚未预热,导致再次超时。

慢启动思路:

text
节点恢复
→ 从较低有效权重开始
→ 逐步增加流量
→ 观察错误率和延迟
→ 达到目标权重

slow_start等具体能力可能属于NGINX Plus或受版本/算法限制,开源环境可由编排平台金丝雀、权重分阶段变更或上层负载均衡实现。不要在不支持的二进制上直接复制指令。

二十七、会话与Cookie粘滞

商业版或第三方模块可能支持Cookie型sticky会话。它比IP哈希更接近用户会话,但仍有:

  • Cookie丢失和跨域限制。
  • 节点故障后的重新映射。
  • 扩缩容与版本发布。
  • 恶意固定或篡改风险。
  • 本地Session数据丢失。

入口粘滞不是业务状态存储。核心系统应尽量无状态或使用共享状态服务。

二十八、为什么单个慢节点会拖高整体P99

三台节点:

text
A P99=50ms
B P99=60ms
C P99=5s

轮询仍把约三分之一请求送到C,整体尾延迟会明显升高。C不一定触发被动摘除,因为它最终在超时前返回200。

治理:

  • 观察每个 $upstream_addr 的响应时间分布。
  • 应用自身健康和线程池指标。
  • 设置合理超时,但避免误杀。
  • 主动健康/异常实例摘流。
  • 权重降低或发布回滚。
  • 查慢SQL、GC、CPU throttling和依赖延迟。

“节点没宕机”不等于“节点健康”。

二十九、日志必须记录每次upstream尝试

nginx
log_format upstream_main
    '$request_id $remote_addr "$request" status=$status '
    'request_time=$request_time '
    'upstream_addr=$upstream_addr '
    'upstream_status=$upstream_status '
    'connect=$upstream_connect_time '
    'header=$upstream_header_time '
    'response=$upstream_response_time';

发生重试时,这些upstream变量可能以逗号或分隔形式记录多次尝试。应保留顺序关系,才能判断:

text
先访问A连接超时
→ 再访问B返回200
→ 客户端最终看到200

只看最终 $status=200 会掩盖A正在故障和重试流量。

三十、流量不均排查

  1. 统计每个upstream地址请求数,不只看连接数。
  2. 检查算法和weight。
  3. 检查ip_hash或hash Key分布。
  4. 检查真实IP是否被上层代理统一。
  5. 检查失败、重试和节点暂时不可用状态。
  6. 检查Worker、共享zone与reload。
  7. 检查长连接和Keep-Alive复用。
  8. 检查新节点是否已被DNS解析并加入运行状态。
  9. 对比每节点响应时间和业务请求成本。

不能只看“机器CPU不一样”就认定算法失效,节点硬件、JVM、流量类型和缓存命中也可能不同。

三十一、502与504排查

502

常见于:

  • DNS解析失败。
  • 连接被拒绝。
  • 上游协议错误。
  • 后端提前关闭。
  • 无效响应。
  • 所有候选节点不可用。

504

常见于:

  • 建连超时。
  • 后端读取超时。
  • 慢SQL。
  • Java线程池/连接池等待。
  • 下游接口慢。
  • 重试总时间过长。

使用error log和upstream时间变量区分初次节点、重试节点、连接阶段和响应阶段。

三十二、可运行三节点Demo

先启动三个本地后端示意服务,分别监听8081、8082、8083并返回自己的节点名。可以使用三个最小Spring Boot服务、三个Nginx return server或团队已有回显镜像。

Nginx配置:

nginx
events {
    worker_connections 1024;
}

http {
    log_format lb '$request_id upstream=$upstream_addr '
                  'upstream_status=$upstream_status '
                  'upstream_time=$upstream_response_time '
                  'status=$status request_time=$request_time';
    access_log /dev/stdout lb;
    error_log /dev/stderr notice;

    upstream order_api {
        least_conn;
        server 127.0.0.1:8081 max_fails=2 fail_timeout=10s;
        server 127.0.0.1:8082 max_fails=2 fail_timeout=10s;
        server 127.0.0.1:8083 max_fails=2 fail_timeout=10s;
        keepalive 16;
    }

    server {
        listen 8090;

        location / {
            proxy_http_version 1.1;
            proxy_set_header Connection "";
            proxy_set_header X-Request-Id $request_id;
            proxy_pass http://order_api;
            proxy_connect_timeout 1s;
            proxy_read_timeout 5s;
            proxy_next_upstream error timeout http_502 http_503 http_504;
            proxy_next_upstream_tries 2;
            proxy_next_upstream_timeout 3s;
        }
    }
}

验证:

bash
nginx -t
for i in 1 2 3 4 5 6 7 8 9; do
  curl -s http://127.0.0.1:8090/
done

实验项:

  1. 记录三节点分布。
  2. 让8082延迟2秒,观察least_conn和P99。
  3. 停止8082,观察error log与重试链。
  4. 恢复8082,观察何时重新获得请求。
  5. 把算法改为轮询和hash,比较分布。
  6. 对POST模拟提交后断连,解释为什么不能只靠代理重试。

三十三、商业场景一:扩容消费者式“短暂有效”

接口积压时增加应用实例,短时间延迟下降,随后再次变慢。可能原因:

  • 入口放行更多并发,数据库成为新瓶颈。
  • 每实例连接池总量叠加,MySQL连接数耗尽。
  • 热Key、热点行和锁竞争没有被横向扩容分散。
  • 新实例缓存未预热,回源流量上升。
  • 重试量随实例数增加。
  • 单节点慢请求仍占资源。

与消息队列扩消费者后再次堆积类似,必须比较输入速率、实际处理速率和最慢共享依赖,不能只看实例数。

三十四、商业场景二:支付回调被重试两次

现象:同一个支付回调在两个后端实例都执行,业务产生重复记账尝试。

证据:upstream日志显示第一次节点响应前断连,Nginx随后尝试第二节点;数据库显示第一次可能已提交。

治理:

  • 支付流水号唯一约束。
  • 幂等状态机。
  • 回调原始报文落库。
  • 重复回调返回一致结果。
  • 代理层不随意为非幂等方法开启重试。
  • 对账与补偿。

三十五、商业场景三:NAT导致ip_hash倾斜

现象:办公用户大量请求集中到一台节点,其他节点空闲。

根因:所有用户经过同一企业出口,Nginx看到相同客户端地址,ip_hash得到同一结果。

修复:确认可信代理链和真实IP,但更根本的是去除本地Session依赖,改用无状态Token或共享Session,再选择轮询/least_conn等策略。

三十六、商业场景四:恢复节点反复被打死

现象:节点重启后刚加入流量,CPU、JIT、连接池和缓存尚未稳定,立即接满权重,随后再次超时并被摘除。

治理:

  • readiness包含必要初始化。
  • 金丝雀小流量。
  • 分阶段提高weight。
  • 预热JIT、连接池和热点缓存。
  • 观察错误率与P99后再放量。
  • 使用平台或商业能力实现慢启动。

三十七、面试标准回答

Nginx负载均衡一次请求怎么走

请求命中proxy_pass后,Worker找到upstream,按轮询、least_conn或hash等算法生成候选节点,跳过down和暂时不可用节点,复用空闲连接或新建连接并发送请求;失败时按proxy_next_upstream判断是否记录失败和换节点,同时受次数与总时间限制,最终转发成功响应或返回502/504。

轮询、least_conn和ip_hash怎么选

请求成本接近且服务无状态时用默认加权轮询;请求时长差异大或长连接较多可评估least_conn,但连接少不等于CPU低;ip_hash可提供弱粘滞,但NAT会倾斜、节点变化会重映射,不能代替共享Session和无状态设计。缓存分片可评估consistent hash,但要处理热Key和数据迁移。

max_fails和fail_timeout是主动健康检查吗

不是。开源Nginx通常根据真实代理请求中的连接错误、超时和配置的失败响应做被动判断;达到max_fails后在fail_timeout相关时间内暂时跳过节点,再尝试恢复。check interval常来自第三方模块,NGINX Plus才有标准商业主动健康能力,不能混用。

Nginx重试为什么可能重复下单

第一个后端可能已经提交数据库,但响应返回Nginx前连接断开;Nginx只看到失败并把POST重试到第二节点,就可能重复执行。代理层无法证明业务是否提交,因此非幂等请求要谨慎重试,订单支付必须有幂等Key、唯一约束、状态机和补偿。

upstream keepalive 32是什么意思

通常表示每个Worker为该upstream缓存最多32条空闲可复用连接,不是总连接上限、并发上限或所有Worker共享32条。活跃连接可以额外存在,多Worker会放大空闲连接,后端FD、keep-alive超时和连接池仍要单独规划。

为什么扩Nginx或应用实例后系统反而更慢

扩容入口和应用会提高进入共享依赖的并发,总数据库连接、慢SQL、热点锁、Redis热Key和重试流量可能放大;瓶颈从应用转移到数据库后,P99和504再次上升。必须按入口、应用、线程池、连接池、DB、Redis和下游做端到端容量分析。

三十八、学习验收

  1. 画出选节点、连接、失败、重试和返回全过程。
  2. 用长期比例解释weight,不承诺固定短窗口比例。
  3. 比较轮询、least_conn、ip_hash和consistent hash边界。
  4. 说明fair、主动check、least_time和slow_start的开源/商业/第三方边界。
  5. 制造两次失败,观察max_fails与fail_timeout行为。
  6. 分别配置哪些失败允许next upstream,并解释总次数和时间限制。
  7. 设计一个提交成功但响应断开的POST,给出幂等治理。
  8. 计算多Worker upstream keepalive的连接放大。
  9. 扩容新节点,观察长连接和Keep-Alive导致的短期倾斜。
  10. 修改DNS记录,证明目标版本何时更新节点。
  11. 用upstream日志还原一次“首节点失败、次节点成功”。
  12. 定位一次NAT导致的ip_hash倾斜。
  13. 定位一次慢节点拖高整体P99但未被摘除。
  14. 写出节点恢复后的预热和渐进放量流程。

关联知识点