Skip to content

Nginx 负载均衡与缓存 ​

S4-016建议学高级约 60 分钟JDK 21企业开发进阶 · Nginx

1. 本节定位 ​

《Nginx 安装与反向代理》讲了反向代理;本节讲它的两项核心能力:把流量分给谁(负载均衡)、能不能不回源(缓存)。

★ 本节的实测会回答两个问题 ✓✓:

text
① 后端快慢不一时,五种策略差多少?   → 4000 ms vs 1335 ms(差 3 倍)
② 加一层代理缓存能提升多少吞吐?     → 490 QPS → 13423 QPS(27 倍)

2. 学习目标 ​

  • 能说出五种负载均衡策略的差异与适用场景 ★★★;
  • 知道"轮询/随机"的前提是"后端一样强" ★★★;
  • 能说清一致性哈希的原理,以及虚拟节点的作用 ★★★;
  • 知道"搬家少"不等于"更好"(要看分布均匀度)★★★;
  • 知道代理缓存的收益来源与最危险的配置 ★★★。

3. 前置知识检查 ​

4. 为什么需要这个知识 ​

先看本节的实测:后端"两快一慢"时,五种策略的墙钟时间 ✓:

text
  策略                  分配结果                          估算总耗时
  轮询 round-robin      A=1000  B=1000  C=1000(慢)      4000 ms  ✗✗
  加权 weight=4:4:1     A=1332  B=1335  C=333             1335 ms  ✓
  随机 random           A=984   B=1025  C=991             3964 ms  ✗✗
  最少连接 least-conn   A=1333  B=1333  C=334             1336 ms  ✓✓
  IP 哈希 ip-hash       A=1005  B=990   C=1005            4020 ms

★★ 3000 个请求,墙钟时间差 3 倍 ✓✓ ——差别只在于"流量怎么分" ✓

5. 核心概念 ​

5.1 五种策略的取舍 ★★★ ​

策略假设优点缺点
轮询 ★所有后端一样强最简单 ✓慢节点被压垮 ✗✗
加权轮询 ★★人工知道快慢有效 ✓要人配、且负载变化时跟不上 ⚠️
随机 ★同轮询无状态、实现简单同样压垮慢节点 ✗✗
最少连接 ★★★无自动适应快慢 ✓✓需要维护连接计数 ✓
IP 哈希 ★★—会话粘性 ✓不是为均摊设计的(分布可能很差)⚠️

★ 核心结论 ✓✓:

轮询与随机假设"所有后端一样强" ✓

而真实集群里"部分机器更弱/更忙"是常态 ✗ —— 比如某台的 JVM 正在 Full GC、或它连的数据库更远 ✓

★★ "最少连接"为什么能自动适应 ✓✓:

text
它的分配依据是「当前负载 = 已分配数 × 单次耗时」——
慢节点的 assigned 增长慢 → 后续更少分给它 → 最终总负载被拉平 ✓

★ 实测证明了这一点 ✓:最少连接给出 1333 / 1333 / 334 —— 慢节点 C 只拿到 1/3 的量,因为它单次耗时是 4 倍 ✓✓

★ 而"加权"要得到同样效果,必须人工配 4:4:1 ⚠️ —— 配对了效果一样好(1335 ms),但配错就白搭 ✗

★★ 一条实战经验 ✓✓:

"慢节点"很少是硬件差异,多半是"有状态"造成的 ✓

这时最少连接会主动把流量"躲开"它 ✓✓ —— 这是一种天然的、无需人工干预的自愈能力 ✓

5.2 一致性哈希:加一个节点要搬多少数据 ★★★ ​

实测(10000 个 key,从 3 个节点扩到 4 个)✓:

text
  取模哈希(朴素方案)                        7496 个( 75.0%)
  一致性哈希(每节点 1 个虚拟节点)             700 个(  7.0%)
  一致性哈希(每节点 150 个虚拟节点)          3130 个( 31.3%)

分布均匀度(10000 个 key 落到 3 个节点):
  取模哈希                      node-A=3333  node-B=3333  node-C=3334   ← 完美均匀 ✓
  一致性哈希(1 个虚拟节点)     node-A=8610  node-B=690   node-C=700    ← 极不均 ✗✗
  一致性哈希(150 个虚拟节点)   node-A=5110  node-B=3370  node-C=1520   ← 明显改善 ✓

★★★ 这张表里藏着一个反直觉的结论 ✓✓:

"搬家少"不等于"更好" ✗✗

1 个虚拟节点时只搬了 7%(看起来最漂亮),但代价是分布极不均 —— A 拿了 86% 的 key ✗✗

那个"搬得少"是因为 A 太胖了,新节点只能从 A 身上切一小块 ✓

★ 所以评价一致性哈希要看两个指标 ✓✓:

指标含义目标
重分布比例 ★★加/减节点时搬多少 key越接近 1/N 越好(加第 4 台应为 25%)✓
分布均匀度 ★★★各节点分到的 key 是否均衡越接近 1/N 越好 ✓✓

★ 虚拟节点就是用来"提升均匀度"的 ✓✓:

只有 1 个虚拟节点时,节点在环上的位置是随机的 → 大概率分得很不均 ✗

150 个虚拟节点 = 每个物理节点在环上占 150 个位置 → 把不均摊平了 ✓✓

而均匀度提升后,重分布比例自然回到理论值附近(31.3% ≈ 25% + 波动) ✓

★ 这也解释了 Redis Cluster 为什么用 16384 个槽 ✓✓:

每个节点分到的槽数是由集群协商的,而槽的粒度足够细,所以分布天然均匀 ✓

本质上槽就是"虚拟节点"的一种实现 ✓

★ 与本模块已有内容的呼应 ✓✓:

用在哪为什么需要
分库分表 ★★★扩一个库时希望只搬 1/N 的数据 ✓
缓存集群(Redis Cluster)★★同上 ✓
带会话粘性的负载均衡ip-hash 是一致性哈希的简化版 ✓

5.3 代理缓存:收益与最危险的配置 ★★★ ​

实测 ✓✓:

text
  2000 个请求、20 个不同 URL(回源一次 2 ms)
  ① 全部回源:      4078 ms(490 QPS)✗
  ② 开启代理缓存:   149 ms(13423 QPS),命中 1980 次 / 回源 20 次 ✓✓
  → 吞吐提升约 27 倍(命中率 99%)

★ 收益幅度由两件事决定 ✓✓:

因素说明
命中率 ★★★URL 越集中(如商品详情页)收益越大;URL 高度分散时几乎没收益 ✗
回源成本 ★★后端本来就是内存操作时,收益有限 ✗

★★ 代理缓存最危险的配置(真实出过越权事故) ✓✓:

Nginx 默认的 cache key 是 $scheme$host$uri,不含 query string 的语义差异 ✗✗

如果后端根据 query 返回不同内容(如 ?userId=1),而 cache key 没带 query ——

会把 A 用户的数据返回给 B 用户 ✗✗

★ 四条必须检查的配置 ✓✓:

nginx
proxy_cache_path /data/cache levels=1:2 keys_zone=mycache:100m inactive=60m;

proxy_cache mycache;
proxy_cache_valid 200 10m;                        # 只缓存 200,时长按业务定
proxy_cache_key "$scheme$host$uri$is_args$args";  # ★ 必须带 query!
proxy_cache_bypass $cookie_session $arg_nocache;  # 登录态/显式禁止的请求不走缓存

★★ 一条硬原则 ✓✓:

带用户态的内容一律不要用共享缓存 ✓

"缓存越靠前,越要小心缓存了不该共享的东西" ✓✓

(这与 《缓存一致性与更新策略》 的一致性问题同源:缓存的位置越靠前,影响面越大)

5.4 上游配置速查 ★★ ​

nginx
upstream backend {
    # 加权轮询(默认就是轮询)
    server 10.0.0.1:8080 weight=4;              # 权重
    server 10.0.0.2:8080 weight=4 max_fails=3 fail_timeout=30s;   # 失败 3 次摘除 30 秒
    server 10.0.0.3:8080 backup;                # 备用节点(主节点全挂才用)
    keepalive 32;                               # ★ 与后端保持长连接

    least_conn;                                 # 最少连接(也可以写 ip_hash / random two)
}

★ 三个实战要点 ✓✓:

#要点说明
①max_fails 是"被动健康检查" ★★靠真实请求失败发现故障 → 发现前那几个请求会真的失败 ✗
②backup + down 是灰度与维护的标准手法 ★★★新节点低权重试流量 → 逐步加 → 出问题立刻 down ✓✓
③ip_hash 与"无状态服务"是矛盾的 ★★★更好的做法是把会话外置(Redis),让服务真正无状态 ✓✓

6. 递进示例 ​

6.1 最小示例:两台后端 ​

nginx
upstream backend {
    server 10.0.0.1:8080;
    server 10.0.0.2:8080;
}

6.2 业务示例:灰度发布 ​

nginx
upstream backend {
    server 10.0.0.1:8080 weight=9;      # 老版本 90%
    server 10.0.0.2:8080 weight=1;      # 新版本 10%
}
# 观察没问题 → 逐步调成 5:5 → 0:10 → 下线老节点 ✓

6.3 反例:用 ip_hash 却指望它均摊 ​

nginx
upstream backend {
    ip_hash;      # ✗ 它的目标是"同一 IP 固定后端",不是"平均分配"
}

★ 拿它当均摊方案,遇到"少数公司出口 IP 贡献大部分流量"时会严重倾斜 ✗✗

7. 完整可运行代码与实测 ​

代码:labs/distributed-lab/src/main/java/cn/kb/learn/dist/proxy/LoadBalanceLesson.java

运行:

powershell
cd labs
.\mvnw.cmd -pl distributed-lab compile
java "-Dstdout.encoding=UTF-8" -cp distributed-lab/target/classes cn.kb.learn.dist.proxy.LoadBalanceLesson

实测输出(关键部分):

text
===== 1. 五种负载均衡策略 =====
  轮询 round-robin      A=1000 B=1000 C=1000     4000 ms  慢节点会被压垮 ✗
  加权 weight=4:4:1     A=1332 B=1335 C=333      1335 ms  需人工配权重 ⚠️
  随机 random           A=984  B=1025 C=991      3964 ms  慢节点会被压垮 ✗
  最少连接 least-conn   A=1333 B=1333 C=334      1336 ms  自动适应快慢 ✓✓

===== 2. 一致性哈希 =====
  取模哈希                        7496 个(75.0%)
  一致性哈希(1 个虚拟节点)        700 个( 7.0%)
  一致性哈希(150 个虚拟节点)     3130 个(31.3%)

  取模哈希                   node-A=3333  node-B=3333  node-C=3334
  一致性哈希(1 个虚拟节点)  node-A=8610  node-B=690   node-C=700    ← 极不均 ✗✗
  一致性哈希(150 个虚拟节点)node-A=5110  node-B=3370  node-C=1520

===== 3. 代理缓存 =====
  ① 全部回源:      4078 ms(490 QPS)✗
  ② 开启代理缓存:   149 ms(13423 QPS),命中 1980 次 / 回源 20 次 ✓✓

★ 三条可落地的结论 ✓:

#结论依据
①后端快慢不一时,用最少连接(或配对好的加权)4000 ms → 1335 ms ✓
②一致性哈希要配足够的虚拟节点,否则分布极不均A 拿 86% ✗✗
③代理缓存收益巨大,但 cache key 必须带 query27 倍提升 ✓

8. Debug 观察点 ​

  1. 把某台后端的处理时间改成 10 倍:观察最少连接把它"躲开" ✓✓;
  2. 把虚拟节点数从 150 改成 1:观察分布瞬间失衡 ✗✗;
  3. 把 cache key 改成不含 query:验证"不同参数返回同一份缓存" ⚠️(越权风险);
  4. 把 max_fails 设为 0:观察故障节点一直被重试 ✗;
  5. 给一台挂掉的节点配 backup:验证主节点全挂时流量切过去 ✓。

9. 常见错误 ​

#错误说明
1以为轮询总是好的 ✗✗慢节点会被压垮 ✓
2用 ip_hash 做均摊 ✗✗它是为粘性设计的 ✓
3一致性哈希不配虚拟节点 ✗✗分布极不均 ✓✓
4用"搬家少"评价一致性哈希 ✗还要看分布均匀度 ✓
5代理缓存 key 不带 query ✗✗可能返回别人的数据 ✓✓
6给带登录态的响应配共享缓存 ✗✗越权 ✓
7以为 max_fails 能提前发现故障 ✗它是被动的,先失败几个请求 ✓

10. 动手实验 ​

目标:亲手验证"策略选择"与"缓存收益"。

步骤:

  1. 跑 LoadBalanceLesson,记录五种策略的墙钟时间 ✓;
  2. 把慢节点的 cost 从 4 倍改成 10 倍,看最少连接是否进一步减少给它的量 ★★;
  3. 把虚拟节点数改成 1 / 10 / 150,画出分布均匀度曲线 ★★;
  4. 算出"你的业务缓存命中率"(热门 URL 占比),判断值不值得加代理缓存 ✓;
  5. 检查你的 nginx.conf 里 cache key 是否带 query ★★★。

验收方式:能说出"你的系统该用哪种策略、为什么"。

扩展任务:设计一个"两级缓存"方案:

  • Nginx 代理缓存(第一级)+ Redis(第二级)+ DB ✓;
  • 设计各自的 TTL 与失效策略 ✓;
  • 说明:为什么"越靠前的缓存 TTL 越短"(提示:失效传播的延迟)★★

11. 练习题 ​

  1. 判断:轮询是最公平的负载均衡策略。(对 / 错)
  2. 填空:一致性哈希中,虚拟节点的作用是 ______;加第 4 个节点时理论重分布比例是 ______。
  3. 简答:为什么"一致性哈希搬家 7%"看起来更好,但实际更差?
  4. (配置阅读)
nginx
upstream backend { ip_hash; }
proxy_cache_key "$host$uri";

这两行分别有什么问题? 5. (设计)给"秒杀"设计网关层方案:要挡住超量请求,又要让真实用户能抢到。

参考答案(要点)
  1. 错。它假设所有后端一样强 ✗ ——后端快慢不一时会压垮慢节点 ✓★
  2. 让分布更均匀;25%(1/4) ✓
  3. 因为那 7% 是"新节点只能从某个过胖的节点切一小块" ✗ —— 分布是 A=8610 / B=690 / C=700,A 拿了 86% ✗✗ —— 均匀度差意味着 A 是热点,加节点也救不了它 ✓✓
  4. ip_hash 只适合会话粘性,不适合均摊(NAT 后大量用户共用 IP 会倾斜)✗; cache key 不含 query → 不同参数会命中同一份缓存,可能越权 ✗✗
  5. 要点:网关层限流(令牌桶允许小突发)+ 提交前校验(验证码/答题)+ 库存预扣 ✓; 把"请求"和"下单"分开:先排队获取资格,再有资格的人下单 ✓✓

12. 掌握标准 ​

  • [ ] 能说出五种策略的差异与适用场景 ★★★;
  • [ ] 知道最少连接的自适应原理,并能说出它的优势 ★★★;
  • [ ] 能说清一致性哈希的原理,以及虚拟节点的作用 ★★★;
  • [ ] 知道"搬家少"不等于"更好",要看均匀度 ★★★;
  • [ ] 知道代理缓存的收益来源,以及 cache key 的危险 ★★★;
  • [ ] 知道 backup / down 怎么用于灰度与维护 ★★。

13. 扩展资源 ​

14. 下一步学习 ​

《Netty 与网络编程概览》:BIO 与 NIO 的真实差距,以及 Netty 替你封装了什么。

后续知识

暂无

进度只保存在本机浏览器,可随时清除

个人学习项目:AI 生成内容均经人工复核,第三方资源仅作索引