负载均衡在生产系统里最难的部分,不是把请求转发出去,而是在服务抖动、扩容、下线、超时和流量突增时,仍然让系统尽量稳住。
1. 健康检查
负载均衡器必须知道后端实例是否可用。
常见健康检查方式:
- TCP 端口是否能连通。
- HTTP 健康检查接口是否返回成功。
- 应用是否完成启动。
- 依赖的数据库、缓存是否可用。
比如:
GET /health如果某台机器健康检查失败,负载均衡器就应该把它从可用列表里摘掉。
但健康检查不能太粗糙。
如果只检查端口是否开放,可能会出现:
端口还活着
应用线程池已经打满
数据库连接已经耗尽
用户请求仍然失败所以健康检查要尽量反映应用是否真的能处理请求。
2. 慢启动
新实例刚上线时,不应该立刻接满流量。
原因包括:
- JVM 或运行时需要预热。
- 缓存还没有建立。
- 数据库连接池还没稳定。
- 刚启动时性能可能不稳定。
慢启动的思路是:
刚上线:接 10% 权重
运行一段时间:接 30% 权重
继续稳定:接 100% 权重这样可以避免新实例刚上线就被大流量打垮。
3. 优雅下线
服务发布或缩容时,不能直接把进程杀掉。
比较合理的流程是:
- 先从负载均衡可用列表中摘除实例。
- 不再接收新请求。
- 等待旧请求处理完成。
- 超过最大等待时间后再退出。
摘流量 -> 等待存量请求完成 -> 关闭服务如果没有优雅下线,用户可能会遇到请求执行到一半连接断开。
4. 超时
负载均衡不能无限等后端响应。
每一层都应该有超时设置:
Client timeout
Load Balancer timeout
Service timeout
Database timeout如果没有超时,请求可能一直挂着,占用连接、线程和内存。
超时太短会误伤正常慢请求,超时太长又会拖垮系统,所以要结合接口类型设置。
5. 重试
重试可以提高短暂故障下的成功率,但也可能放大流量。
比如原本每秒 1000 个请求,如果每个失败请求都重试 3 次,后端压力可能突然变成好几倍。
所以重试要注意:
- 只对幂等请求重试。
- 设置最大重试次数。
- 使用退避策略。
- 避免所有客户端同时重试。
- 不要对已经明显过载的服务继续重试。
重试不是万能药,用不好会让故障扩散。
6. 会话保持
有些系统需要同一个用户固定打到同一台机器。
常见原因:
- 服务端保存了 session。
- 本地缓存依赖用户上下文。
- 长连接需要保持在同一个实例上。
常见做法:
- 根据 Cookie 做路由。
- 根据用户 ID 做哈希。
- 根据 IP 做哈希。
但从架构上看,尽量减少对本地状态的依赖会更稳。
如果 session 可以放到 Redis 这类共享存储中,后端实例就更容易横向扩展。
7. 限流和过载保护
负载均衡层常常也是限流入口。
当流量超过系统承受能力时,如果完全不限制,所有后端可能一起被打挂。
常见方式:
- 按 IP 限流。
- 按用户限流。
- 按接口限流。
- 按服务整体 QPS 限流。
- 排队或快速失败。
限流的目的不是“拒绝用户”,而是保护系统核心能力不被拖垮。
8. 灰度发布
负载均衡也常用于灰度发布。
比如新版本先接少量流量:
v1: 95%
v2: 5%观察错误率、延迟、业务指标没有异常后,再逐步扩大流量。
灰度发布的价值在于:不要让一个有问题的新版本一次性影响所有用户。
9. 可观测性
负载均衡层应该能回答这些问题:
- 当前每个实例分到了多少流量?
- 哪些实例失败率高?
- 哪些接口延迟变高?
- 请求被限流了多少?
- 重试发生了多少次?
- 健康检查失败了多少次?
没有监控,负载均衡就像一个黑盒。
出了问题时,只知道“服务慢了”,但不知道是入口层、某个实例、网络、数据库还是下游服务出了问题。
10. 小结
负载均衡的稳定性设计,可以按这条线理解:
- 健康检查:发现不可用实例。
- 慢启动:保护新上线实例。
- 优雅下线:保护正在处理的请求。
- 超时:避免资源被长期占用。
- 重试:处理短暂失败,但不能放大故障。
- 会话保持:解决状态绑定,但尽量减少本地状态。
- 限流:系统过载时保护核心服务。
- 灰度:新版本先小流量验证。
- 监控:让流量分配和故障状态可见。
所以生产里的负载均衡不是一个简单算法,而是一整套流量治理体系。
留言板