负载均衡
这一课你将能做到
- 说清 L4 与 L7 负载均衡的决策依据差异,并能判断本站每条流量该走哪层
- 手写一段生产可用的 Nginx upstream 配置(加权、健康检查、故障转移、超时)
- 讲透一致性哈希的原理与虚拟节点作用,达到能白板画环的程度
为什么要学
水平扩展加了几台机器后,流量怎么分过去?答案是负载均衡。它还有两个隐藏职责:摘除故障节点(某实例挂了自动不再给它发流量)和滚动发布的执行者(升级时先把一台摘下来,发布完再放回去)。
面试高频三连:「四层和七层负载均衡的区别?」「轮询和一致性哈希分别什么时候用?」「负载均衡器自己挂了怎么办?」——最后一个能答好的人不多,本课一并讲清。
人话:负载均衡就是餐厅领位员。L4 领位员只看「你们几个人」(IP+端口),把人往有空桌的区域引,动作极快;L7 领位员还会问「吃火锅还是西餐」(URL/Header),把你带到对的档口,更聪明但更慢。领位员自己不能是单点,得有两个领位员互为备份。
先建立图像
本站流量分层:
Internet
│
│ DNS 轮询 / 云 LB(L4,高吞吐入口,防 LB 单点)
▼
Nginx ×2(L7,Keepalived 主备)
│ 按 URL 分流:
├── /api/** → upstream: spring-boot × N
├── /judge/**(WS) → upstream: judge-gateway(ip_hash 保会话)
└── /static/** → 本地缓存 / 直接回 CDN
L4 与 L7 对照:
| 层级 | 决策依据 | 能力 | 性能 | 产品 |
|---|
| L4 传输层 | IP + 端口 | 仅转发 TCP/UDP 流量 | 极高(不解析内容) | 云 NLB、LVS、DPVS |
| L7 应用层 | URL/Header/Cookie | 路径路由、TLS 终结、限流、改写 | 较高(解析报文) | Nginx、ALB、Ingress |
判题平台的判断实例:判题机回传结果的内部通道走 L4(纯转发、量大);对用户的 /api/** 走 L7(要做限流、按路径路由到题目服务或判题服务)。
五种算法选型速查:
| 算法 | 逻辑 | 适用 | 本站场景 |
|---|
| Round Robin | 依次轮转 | 实例同质、请求耗时接近 | 题目列表 API |
| Weighted RR | 按权重分流 | 机器配置不齐 / 灰度发布 | 新版本先 10% 流量 |
| Least Connections | 发给当前连接最少的 | 请求耗时长短不一 | 判题状态轮询(混长短请求) |
| IP Hash / Cookie | 同客户端落同实例 | 本地会话兜底 | WebSocket 判题推送 |
| Consistent Hash | 哈希环定位节点 | 缓存分片、有状态路由 | Redis 集群分片(面试最爱) |
跟着做
步骤 1:写一段生产可用的 Nginx 配置
# /etc/nginx/conf.d/java-nav.conf
upstream java_nav_api {
least_conn; # 长短请求混合,选最少连接
server 10.0.1.11:8080 weight=5 max_fails=2 fail_timeout=10s;
server 10.0.1.12:8080 weight=5 max_fails=2 fail_timeout=10s;
server 10.0.1.13:8080 weight=3 max_fails=2 fail_timeout=10s; # 小机器低权重
server 10.0.1.14:8080 backup; # 备机,前面全挂才启用
keepalive 64; # 与后端的长连接池,省握手
}
server {
listen 443 ssl;
server_name javanav.example.com;
location /api/ {
proxy_pass http://java_nav_api;
proxy_http_version 1.1;
proxy_set_header Connection ""; # 配合 keepalive
proxy_set_header X-Real-IP $remote_addr; # 后端取真实 IP 做限流
proxy_next_upstream error timeout http_502; # 失败自动换下一台重试
proxy_connect_timeout 2s; # 连不上快速切节点
proxy_read_timeout 10s; # 读超时兜底
}
}
逐项解释:max_fails=2 fail_timeout=10s 表示 10 秒窗口内失败 2 次就摘除 10 秒——这就是被动的健康检查;proxy_next_upstream 让单请求失败时自动换节点,用户无感。
步骤 2:验证负载分发与故障转移
# 1. 三个后端日志里各打一行实例名,压测看分发
ab -n 3000 -c 100 http://localhost/api/problems/page/1
# 2. 手动 kill 掉 12 号实例,观察 Nginx error.log 出现
# "upstream server temporarily disabled",流量自动落在其余两台
# 3. 重启 12 号,10 秒后自动回归轮转
步骤 3:一致性哈希白板推演(面试必画)
普通取模 hash(key) % N 的问题:N 从 3 变 4,几乎所有 key 的落点都变——Redis 分片场景等于全量缓存失效。
一致性哈希把节点和 key 都映射到 0~2^32 的环上,key 顺时针找最近的节点:
node A key k1 ──▶ 落 A
/ \
k1 k2 环上顺时针:k1→A,k2→B,k3→C
\ /
node D ── node B
\ /
node C ◀── key k3
B 宕机:只有落在 B 弧段上的 key 迁移到 C,其余 key 不动(迁移量 1/N)
数据倾斜问题与虚拟节点:物理节点少时弧段不均。给每个物理节点生成 150 个虚拟节点打散在环上,落点统计就均匀了——Redis Cluster 的 16384 slot、Kafka 分区分配都是同一思想。
步骤 4:滚动发布配合优雅下线
# 1. 摘流量:调用 Nginx API 或改 upstream 注释后 nginx -s reload
# 2. 等待实例处理完存量请求(Spring Boot 配置)
server.shutdown: graceful # 优雅停机,等线程处理完再退出
spring.lifecycle.timeout-per-shutdown-phase: 30s
# 3. 发新版 → 健康检查通过 → 放回流量 → 下一台
不配 graceful 的后果:实例收到 SIGKILL 时正在处理的请求全部 502——发布高峰期的报错大多是这个原因。
讲清楚要点
- L4 看地址、L7 看内容:需要按 URL/用户分流、终结 TLS、做应用层限流就是 L7;纯粹转发大流量(内部 RPC、消息通道)用 L4 更快。两层常串联:云 LB(L4) → Nginx(L7) → 实例。
- 算法跟着「请求耗时是否均匀」选:耗时均匀轮询即可;长短请求混合(判题状态轮询)用最少连接,否则快实例会被慢请求连累排队。
- 一致性哈希的核心价值是「增减节点只迁移 1/N」:用于有状态数据的分片路由(Redis 集群、分布式缓存),无状态 HTTP 转发用不上它,别在 API 负载均衡上硬套。
- 健康检查分被动和主动:Nginx 开源版被动(靠失败计数摘除),商业版/云 LB 主动探测(定时打
/actuator/health)。探活间隔要小于故障感知要求,又不能太密打挂后端。
- LB 自己不能是单点:Nginx 主备 + Keepalived 虚拟 IP 漂移,或直接用云厂商多可用区 LB。面试答「LB 挂了怎么办」就答这两条。
你可能会懵的地方
-
负载均衡、反向代理、API 网关是一回事吗?
反向代理是「替后端接收请求」,负载均衡是它的能力之一;API 网关在反向代理之上叠加鉴权、限流、聚合、协议转换。Nginx 可以三者都做,Spring Cloud Gateway 专注网关层。区别在职责,不在产品。
-
为什么判题 WebSocket 要会话保持?
WS 是长连接,握手后状态留在建立连接的那台实例上。ip_hash 能保住会话,但宕机会断线——更好的做法是 WS 网关 + Redis 广播消息,让任何实例都能处理。
-
Nginx 和 LVS 什么关系,为什么大厂两层都用?
LVS 工作在内核态 L4,单机转发能力远超 Nginx;Nginx 工作在用户态 L7,功能丰富。经典架构 LVS 做第一层扛量,Nginx 做第二层做路由。小流量用云 LB + Nginx 两层足够。
-
一致性哈希和 Redis Cluster 的 slot 方案哪个好?
思想同源(都是减少扩缩容时的迁移量)。slot 方案把哈希空间预切成 16384 份再分配给节点,运维更直观、迁移粒度可控;一致性哈希更通用但需要虚拟节点调参。面试说清两者同属「哈希分片 + 减少再平衡成本」即可。
-
健康检查间隔设多少合适?
常见 25 秒、连续 23 次失败才摘除。太密会误杀(瞬时 GC 停顿被当故障),太疏故障感知慢。配合重试机制(proxy_next_upstream)可以把感知压力转给请求级故障转移。
错法对照
| 错法 | 问题 | 正解 |
|---|
| 所有请求 Round Robin 一把梭 | 长短请求混合时快实例空转、慢实例压垮 | Least Connections 或按耗时分类路由 |
| 用 ip_hash 做 API 会话保持 | 节点宕机全员断线、负载倾斜 | 会话放 Redis,无状态转发 |
| 健康检查只探端口不探业务 | 进程活着但 DB 连不上照样接流量 | 探 /actuator/health(含 DB/Redis 依赖项) |
| 后端实例配了 keepalive,Nginx 没配 | 每请求新建 TCP,延迟和端口浪费 | proxy_http_version 1.1 + Connection "" |
| 发布直接 kill -9 | 存量请求全部 502 | graceful shutdown + 先摘流量 |
卡点急救
- 某实例反复被摘又恢复(抖动):多半是它真有问题——看该实例的 GC 日志和线程池,长尾停顿触发失败计数;也可能 Nginx 与它之间网络抖动,检查同机房路由。
- 流量分配严重不均:检查机器规格是否一致(不一致该上权重);开启了 least_conn 但后端有长连接任务占着连接数;或用了 ip_hash 而某几个大客户 IP 流量占比过高。
- 发布后部分请求 502:确认三点——旧实例是否 graceful 停机、Nginx 是否等旧连接排空(
worker_shutdown_timeout)、新实例是否先过健康检查再放量。标准流程是摘流量→排空→重启→探活→回流量。
面试常问
-
四层和七层负载均衡的区别,分别举应用场景?
L4 基于 IP+端口转发(不解析应用层),性能高,用于 TCP 大流量、数据库代理、LB 的最外层;L7 解析 HTTP(URL/Header/Cookie),能做路径路由、限流、灰度,用于业务入口。可举本站例子:内部判题结果通道 L4,用户 API 入口 L7。
-
一致性哈希原理?虚拟节点解决什么问题?
哈希环 + 顺时针最近节点,节点增减只影响相邻弧段(迁移 1/N);虚拟节点把每个物理节点打散成上百个环上点,解决节点少导致的负载倾斜,同时提升节点数变化时的平滑度。应用:Redis Cluster slot 思想同源、分布式缓存路由、Kafka 消费者分配。
-
负载均衡器本身挂了怎么办?
三层答案:① 主备 + Keepalived/VRRP 虚拟 IP 自动漂移;② 云环境直接用多可用区托管 LB;③ DNS 层再兜底(多 LB 记录 + 健康检查摘除)。核心思想:任何一层都要消除单点,LB 也不例外。
自测三问
- 题目列表 API 和判题 WebSocket 推送分别该用什么算法?为什么?
- N=4 时节点从 4 扩到 5,普通取模和一致性哈希分别迁移多少 key?
- graceful shutdown 没配置时,滚动发布会出现什么现象?完整发布流程应该是怎样的?
小结一句话
负载均衡让水平扩展真正可用——选对层级(L4/L7)与算法(RR/最少连接/一致性哈希),探活摘除与优雅下线是稳定性的另一半。
延伸