什么是负载均衡?一篇文章讲清常见负载均衡技术
一句话总结
负载均衡(Load Balancing)是一种将大量请求按照一定规则分发到多台后端服务器的技术,用来突破单机性能瓶颈、避免单点故障,并提高系统整体的吞吐量、可用性和资源利用率。
可以把它理解成餐厅里的领班:
新客人到店后,不是全部交给同一个服务员,而是由领班根据每个人的忙碌程度,将客人分配给不同服务员,避免有人忙不过来、有人却一直空闲。
一、为什么需要负载均衡
一个系统刚上线时,可能只有一台服务器:
用户请求
↓
服务器 A
所有请求都由服务器 A 处理。
这种架构简单、部署成本低,但随着业务增长,很快会遇到性能和稳定性问题。
1. 单机性能存在上限
一台服务器的资源始终有限:
- CPU 核心数有限;
- 内存容量有限;
- 网络带宽有限;
- 磁盘 IO 有限;
- 线程池容量有限;
- 数据库连接数有限。
当请求量持续增加时,服务器可能出现:
CPU 满载
内存不足
线程池耗尽
请求长时间排队
接口大量超时
应用直接崩溃
无论代码优化得多好,单台机器最终都会遇到物理上限。
2. 单点故障
如果整个服务只有一台服务器,一旦这台服务器发生故障:
服务器 A 宕机
↓
整个服务不可用
可能导致故障的原因很多:
- JVM 崩溃;
- 内存溢出;
- 磁盘损坏;
- 网络中断;
- 应用发布失败;
- 服务器重启;
- 机房断电;
- 人为误操作。
只有一个服务实例,就意味着它既是唯一处理节点,也是唯一故障点。
3. 多台服务器负载不均
即使部署了多台服务器,如果没有统一的流量调度机制,也可能出现:
服务器 A:CPU 95%
服务器 B:CPU 25%
服务器 C:CPU 10%
服务器 A 忙得快要崩溃,B 和 C 却没有被充分利用。
负载均衡需要解决的,就是这种“有的忙死、有的闲死”的问题。
4. 系统需要横向扩展
提高系统处理能力通常有两种方式。
纵向扩展
升级单台服务器的硬件:
8 核 CPU → 32 核 CPU
16 GB 内存 → 128 GB 内存
纵向扩展实现简单,但存在明显缺点:
- 硬件能力始终有上限;
- 高配置服务器价格昂贵;
- 服务器仍然可能是单点;
- 扩容时可能需要停机。
横向扩展
增加服务器数量:
1 台服务器
→ 3 台服务器
→ 10 台服务器
→ 100 台服务器
横向扩展更加灵活,但需要一个统一组件把请求合理分配到这些服务器上。
这个组件就是负载均衡器。
二、负载均衡的基本工作原理
部署负载均衡后,用户一般不会直接访问某台具体服务器,而是访问一个统一入口:
┌──→ 服务器 A
用户请求 → 负载均衡器 ├──→ 服务器 B
└──→ 服务器 C
负载均衡器的工作流程通常是:
1. 接收客户端请求
2. 查询当前可用的后端服务器
3. 根据负载均衡算法选择一个节点
4. 将请求转发给该节点
5. 获取后端响应
6. 将响应返回给客户端
客户端通常不知道真正处理请求的是服务器 A、B 还是 C。
对于客户端来说,它访问的始终是同一个域名或 IP 地址。
三、同一个用户的请求可能落到不同服务器
假设线上部署了三个完全相同的应用实例:
Node A
Node B
Node C
同一个用户连续发送三个请求,可能被分配成:
请求 1 → Node A
请求 2 → Node C
请求 3 → Node B
这是正常现象。
负载均衡器通常以“单次请求”为单位进行调度,而不是默认把一个用户永久绑定在某一台服务器上。
例如用户发起一次 AI 评分请求:
第一次提交 → Node A
前端等待一段时间没有收到响应,于是自动重试:
第二次提交 → Node B
此时 Node A 和 Node B 的 JVM 内存彼此隔离:
Node A 看不到 Node B 的本地锁
Node B 看不到 Node A 的执行状态
如果系统只使用 JVM 本地锁或本地 Single-flight,那么两个节点都可能真正调用一次 AI。
这也是为什么进入多实例环境后,很多单机方案会失效。
四、负载均衡解决了哪些问题
1. 提升系统吞吐量
假设一台服务器每秒最多处理 1,000 个请求。
部署三台服务器后,理论上可以承担更多流量:
服务器 A:1,000 QPS
服务器 B:1,000 QPS
服务器 C:1,000 QPS
整体处理能力可能接近:
3,000 QPS
实际系统不一定严格线性增长,因为数据库、缓存、消息队列和下游服务也可能成为瓶颈。
但横向增加服务器,通常能够显著提升系统吞吐量。
2. 避免单点故障
当服务器 A 宕机时,负载均衡器可以停止向它分配请求:
服务器 A:故障,停止接收流量
服务器 B:继续提供服务
服务器 C:继续提供服务
只要集群中仍然存在健康实例,整个服务就可以继续运行。
3. 提高资源利用率
负载均衡器可以根据节点的连接数、响应速度、权重或健康状态,把更多请求分配给压力较低的服务器。
这样可以避免一部分机器过载,而另一部分机器长期空闲。
4. 支持动态扩容
流量上升时,可以增加新的应用实例:
扩容前:
A、B、C
扩容后:
A、B、C、D、E
新节点完成启动并通过健康检查后,负载均衡器就可以逐渐向它们分配请求。
5. 支持滚动发布
系统升级时,不需要一次性关闭所有服务器。
可以逐台升级:
将 Node A 从负载均衡中摘除
→ 等待正在执行的请求结束
→ 升级 Node A
→ 健康检查通过
→ Node A 重新加入集群
再升级 Node B
再升级 Node C
这样可以在不中断整个服务的情况下完成版本发布。
五、常见的负载均衡算法
负载均衡器需要解决一个核心问题:
当前这个请求,应该交给哪台服务器处理?
不同算法适合不同的业务场景。
1. 轮询:Round Robin
轮询是最容易理解的算法。
负载均衡器按照固定顺序依次分配请求:
请求 1 → Node A
请求 2 → Node B
请求 3 → Node C
请求 4 → Node A
请求 5 → Node B
优点
- 实现简单;
- 不需要收集复杂指标;
- 请求数量通常分配得比较均匀;
- 适合节点配置相似的集群。
缺点
它只关心请求数量,不关心服务器当前的实际压力。
例如:
请求 1 执行 10 毫秒
请求 2 执行 30 秒
轮询仍然把它们当作两个相同重量的请求。
适用场景
适合:
- 无状态 HTTP 接口;
- 请求耗时比较接近;
- 后端服务器配置相似;
- 普通查询和 CRUD 服务。
2. 加权轮询:Weighted Round Robin
如果后端服务器的性能不同,可以给它们设置不同权重:
Node A:权重 5
Node B:权重 3
Node C:权重 2
一段时间内,流量比例大约为:
Node A:50%
Node B:30%
Node C:20%
适用场景
适合:
- 新旧服务器混合部署;
- 节点硬件配置不同;
- 灰度发布;
- 新版本只接收少量流量;
- 某些节点可以承担更多请求。
例如灰度发布时:
旧版本集群:权重 90
新版本集群:权重 10
先让 10% 的用户进入新版本,确认稳定后再逐渐提高权重。
3. 最少连接:Least Connections
最少连接算法会将新请求分配给当前连接数最少的服务器。
例如:
Node A:100 个连接
Node B:30 个连接
Node C:60 个连接
新请求会优先进入 Node B。
优点
相比轮询,它更能反映服务器当前是否繁忙。
缺点
连接数少,不一定代表真正负载低。
例如:
Node A:
100 个简单查询连接
Node B:
30 个大模型推理连接
虽然 Node B 连接更少,但实际 CPU 和内存压力可能更高。
适用场景
适合:
- WebSocket;
- SSE;
- 长轮询;
- 文件下载;
- 数据库连接代理;
- 请求耗时差异较大的系统。
4. 加权最少连接
加权最少连接是在最少连接的基础上,再考虑服务器性能。
例如性能更强的 Node A,即使连接数略多,也可能继续获得新请求。
这种算法兼顾:
当前连接数
+
服务器处理能力
在服务器配置差异较大的环境中,比普通最少连接更实用。
5. 最短响应时间:Least Response Time
该算法会综合考虑:
- 节点当前连接数量;
- 最近一段时间的平均响应时间;
- 节点健康状态;
- 可能还包括错误率。
优先选择近期响应速度较快的服务器。
优点
能够更真实地反映后端节点的实际服务能力。
缺点
- 需要持续采集运行指标;
- 算法更复杂;
- 短暂抖动可能影响流量分配;
- 指标采集本身存在延迟。
6. 随机:Random
负载均衡器随机选择一个健康节点。
请求 → 随机选择 A、B 或 C
看起来很随意,但在请求量足够大、节点数量较多时,随机分配通常也能达到比较均匀的效果。
优点
- 实现简单;
- 不需要维护复杂状态;
- 算法开销很小。
缺点
- 短时间内可能分配不均;
- 不感知节点压力;
- 不适合节点能力差异较大的场景。
7. IP Hash
IP Hash 会根据客户端 IP 计算哈希值:
hash(clientIp) → 某个后端节点
同一个客户端 IP 通常会被映射到同一台服务器。
优点
可以实现简单的会话保持。
缺点
- 大量用户可能共用同一个出口 IP;
- 用户切换网络后 IP 会变化;
- 代理、NAT 会影响真实 IP;
- 节点增减时映射可能大规模变化;
- 容易造成流量不均。
因此,IP Hash 可以用于简单场景,但不应该被视为可靠的用户身份绑定机制。
8. 一致性哈希:Consistent Hashing
普通哈希在服务器数量发生变化时,可能导致大量 Key 重新映射。
例如原来有三台服务器:
hash(key) % 3
增加到四台后:
hash(key) % 4
大部分 Key 的映射结果都会改变。
一致性哈希的目标是:
增加或删除节点时,只让少部分 Key 重新分配。
它常用于:
- 分布式缓存;
- 数据分片;
- 同一业务 Key 路由到固定节点;
- 本地缓存命中优化;
- 有状态服务路由。
实际实现中通常还会引入虚拟节点,避免真实节点在哈希环上分布不均。
需要注意:
一致性哈希只能让路由更加稳定,不能代替分布式锁、共享状态或分布式 Single-flight。
9. 根据 URL、Header 或 Cookie 路由
七层负载均衡器可以读取 HTTP 请求内容,并根据业务规则分流。
例如根据 URL:
/api/order/** → 订单服务
/api/user/** → 用户服务
/api/ai/** → AI 服务
根据 Header:
X-Version: v2 → 新版本集群
其他请求 → 稳定版本集群
根据 Cookie:
灰度用户 → 新版本
普通用户 → 旧版本
常见用途
- 灰度发布;
- A/B 测试;
- 多租户隔离;
- 多语言系统;
- 地域路由;
- 新旧接口迁移;
- 移动端和网页端分流。
六、四层负载均衡与七层负载均衡
负载均衡通常分为四层和七层。
这里的“层”来自网络模型。
1. 四层负载均衡:L4
四层负载均衡主要基于:
IP 地址
端口
TCP
UDP
进行转发。
它一般不关心 HTTP 路径、Cookie 或业务参数。
例如:
客户端访问 10.0.0.1:443
↓
转发到 Node B:8443
优点
- 性能高;
- 转发开销低;
- 可以支持 TCP、UDP 等协议;
- 适合高并发和长连接。
缺点
- 无法根据 URL 路径分流;
- 无法方便地读取 HTTP Header;
- 业务路由能力较弱。
常见实现
- LVS;
- HAProxy TCP 模式;
- 云厂商网络型负载均衡器;
- 部分 Kubernetes Service 转发能力。
2. 七层负载均衡:L7
七层负载均衡能够理解 HTTP、HTTPS 等应用层协议。
它可以根据以下信息进行路由:
- 域名;
- URL;
- HTTP 方法;
- Header;
- Cookie;
- 查询参数;
- 客户端类型。
例如:
api.example.com/** → API 集群
static.example.com/** → 静态资源集群
www.example.com/ai/** → AI 服务集群
优点
- 路由规则灵活;
- 支持 TLS 终止;
- 支持限流、鉴权和请求重写;
- 支持灰度发布;
- 更容易做访问日志和安全治理。
缺点
- 处理开销通常高于四层;
- 配置更加复杂;
- 负载均衡器需要解析应用层协议。
常见实现
- Nginx;
- HAProxy HTTP 模式;
- Envoy;
- API Gateway;
- Kubernetes Ingress;
- 云应用型负载均衡器。
七、服务端负载均衡与客户端负载均衡
1. 服务端负载均衡
客户端只知道负载均衡器的地址:
客户端
↓
负载均衡器
↓
后端服务 A、B、C
节点选择由负载均衡器负责。
常见实现
- Nginx;
- 云负载均衡器;
- API Gateway;
- Kubernetes Ingress。
优点
- 客户端实现简单;
- 路由策略集中管理;
- 后端节点对客户端透明。
缺点
- 所有请求多经过一层代理;
- 负载均衡层需要保证高可用;
- 可能增加少量网络延迟。
2. 客户端负载均衡
客户端从注册中心获取服务节点列表,然后自己选择一个节点调用:
客户端
↓
注册中心返回:A、B、C
↓
客户端选择 Node B
↓
直接调用 Node B
这种方式常见于微服务内部调用。
常见实现
- Spring Cloud LoadBalancer;
- Dubbo;
- gRPC 客户端负载均衡;
- 服务发现组件; -部分 Service Mesh 架构。
优点
- 少一层集中代理;
- 客户端可以选择适合自己的策略;
- 适合高频微服务调用。
缺点
- 每个客户端都要维护节点列表;
- 策略升级更加复杂;
- 服务发现数据可能存在短暂延迟;
- 客户端可能调用已经下线的节点。
八、DNS 负载均衡
DNS 可以让同一个域名解析到多个 IP:
example.com
→ 1.1.1.1
→ 2.2.2.2
→ 3.3.3.3
不同用户可能获得不同 IP,从而实现粗粒度流量分配。
优点
- 实现简单;
- 适合跨地区调度;
- 不需要所有流量经过同一个代理节点;
- 可以用于多机房入口。
缺点
- DNS 缓存会导致切换不及时;
- 很难精确控制单次请求;
- 客户端可能长期缓存失效 IP;
- 健康检查粒度较粗。
生产系统中,DNS 负载均衡通常会和其他负载均衡层结合:
DNS
↓
地域负载均衡器
↓
机房内部负载均衡器
↓
应用实例
九、常见负载均衡实现方式
1. 软件负载均衡
部署在普通服务器或虚拟机上的软件:
- Nginx;
- HAProxy;
- LVS;
- Envoy。
特点
- 成本较低;
- 配置灵活;
- 易于自动化部署;
- 需要自行维护高可用。
2. 硬件负载均衡
使用专用硬件设备处理流量。
特点
- 性能强;
- 功能成熟;
- 稳定性高;
- 价格和维护成本较高。
过去大型企业、金融和电信系统使用较多。
3. 云负载均衡
由云厂商提供托管服务。
特点
- 自动扩缩容;
- 集成健康检查;
- 支持证书管理;
- 支持多可用区;
- 不需要维护底层设备;
- 通常按实例或流量计费。
适合云原生和快速扩展的系统。
十、健康检查是负载均衡的基础
负载均衡器不仅要知道有哪些服务器,还要知道哪些服务器当前能够正常接收流量。
这就需要健康检查。
1. TCP 健康检查
负载均衡器尝试连接后端端口:
端口连接成功
→ 初步判断节点健康
优点
- 简单;
- 开销小;
- 检查速度快。
缺点
应用可能已经不可用,但端口仍然可以连接。
例如 JVM 出现严重线程阻塞,但端口还开着。
2. HTTP 健康检查
负载均衡器定期访问:
/health
/ready
/actuator/health
如果返回预期状态码和响应内容,就认为节点健康。
相比 TCP 检查,HTTP 检查可以判断应用本身是否真正可用。
3. 存活检查和就绪检查
两者最好分开。
存活检查:Liveness
回答:
这个进程还活着吗?
失败后,容器平台可能重启应用。
就绪检查:Readiness
回答:
这个实例现在是否适合接收流量?
例如应用端口已经启动,但:
- 数据库连接池尚未初始化;
- 缓存还没有准备好;
- 模型资源仍在加载;
- 配置尚未同步完成。
此时进程虽然活着,却不应该接收正式流量。
4. 健康检查也会误判
检查太宽松:
节点已经无法正常服务
但负载均衡器仍认为它健康
检查太严格:
下游出现短暂抖动
所有节点同时被摘除
因此通常需要合理设置:
- 检查间隔;
- 检查超时;
- 连续失败次数;
- 连续成功次数;
- 节点恢复等待时间。
十一、什么是会话保持
有些旧系统会把用户登录状态保存在某个应用实例的本地内存中。
例如用户第一次请求落到 Node A:
Node A 保存了用户 Session
第二次请求却落到 Node B:
Node B 没有该 Session
用户可能突然变成未登录状态。
为了解决这个问题,可以使用会话保持,也叫 Sticky Session。
它的目标是:
同一个用户
尽量继续访问同一个服务器
常见方式包括:
- Cookie 绑定;
- IP Hash;
- Session ID 路由;
- 网关粘性标识。
不过会话保持也有缺点:
- 流量可能分配不均;
- 节点故障后 Session 仍会丢失;
- 扩容后用户映射不容易调整;
- 应用越来越依赖本地状态。
更推荐的方式通常是:
应用实例无状态
+
Session 存到 Redis、数据库或共享存储
这样无论请求落到哪个节点,都可以正常处理。
十二、负载均衡并不等于请求数量完全相同
负载均衡的“均衡”,不代表每台服务器始终处理完全相同数量的请求。
即使三台服务器都接收到 100 个请求,它们的实际压力也可能完全不同:
Node A:
100 个简单数据库查询
Node B:
100 个大文件上传
Node C:
100 个 AI 推理请求
请求数量相同,但 CPU、内存、网络和执行时间完全不同。
所以成熟的负载均衡策略可能综合考虑:
- 活跃连接数;
- CPU 使用率;
- 内存使用率;
- 平均响应时间;
- 请求错误率;
- 任务队列长度;
- 节点处理能力;
- 业务请求类型。
十三、负载均衡器自己会不会成为单点
会。
如果系统只有一个负载均衡器:
用户
↓
唯一负载均衡器
↓
后端服务器集群
负载均衡器一旦宕机,即使所有后端服务器都正常,用户也无法访问。
因此,负载均衡层本身也需要高可用。
常见方案包括:
- 部署主备负载均衡器;
- 使用 Keepalived 和虚拟 IP;
- 使用多台负载均衡器;
- 使用云厂商托管负载均衡;
- 跨可用区部署;
- 使用 DNS 故障切换。
负载均衡解决了后端服务器的单点问题,但不能让负载均衡器自己变成新的单点。
十四、真实生产环境通常有多层负载均衡
大型系统通常不只使用一层负载均衡。
典型请求链路可能是:
用户
↓
DNS 或全球流量调度
↓
云负载均衡器
↓
Nginx 或 API Gateway
↓
Kubernetes Service
↓
应用实例
↓
客户端负载均衡
↓
下游微服务
不同层承担不同职责:
DNS
负责跨地域和跨机房调度
云负载均衡器
负责公网入口和四层流量转发
Nginx / API Gateway
负责 HTTP 路由、限流、鉴权和灰度发布
Kubernetes Service
负责将流量转发到不同 Pod
客户端负载均衡
负责微服务内部选择服务实例
十五、负载均衡常见风险
1. 重试风暴
如果客户端、网关和服务内部都配置了重试,一次失败可能被放大成大量请求。
例如:
客户端重试 3 次
× 网关重试 2 次
× 内部服务重试 3 次
= 最多 18 次调用
因此,需要统一设计重试策略,并配合:
- 超时控制;
- 指数退避;
- 随机抖动;
- 熔断;
- 限流;
- 幂等;
- Single-flight。
2. 长连接分配不均
WebSocket、SSE、视频流等连接可能持续很长时间。
如果使用普通轮询,某些节点可能积累大量长连接。
这类场景更适合:
- 最少连接;
- 加权最少连接;
- 独立长连接集群;
- 优雅下线机制。
3. 获取真实客户端 IP
请求经过负载均衡器后,应用看到的源 IP 可能是负载均衡器地址。
通常需要读取:
X-Forwarded-For
X-Real-IP
Forwarded
但应用不能无条件信任公网请求携带的这些 Header,否则攻击者可以伪造 IP。
一般应该只信任由内部网关或可信代理写入的 Header。
4. TLS 终止
HTTPS 可以在负载均衡器处完成解密:
客户端 HTTPS
↓
负载均衡器解密
↓
后端 HTTP 或 HTTPS
这样可以统一管理证书,减少后端应用的证书维护压力。
但也需要考虑负载均衡器与后端服务器之间的数据是否需要继续加密。
5. 优雅下线
服务器升级或关闭前,不应该直接杀掉进程。
正确流程通常是:
从负载均衡器中摘除节点
→ 停止接收新请求
→ 等待正在执行的请求完成
→ 关闭应用
否则正在处理中的用户请求可能突然中断。
十六、负载均衡与分布式 Single-flight 的关系
负载均衡解决的问题是:
一个新请求应该交给哪台服务器处理?
分布式 Single-flight 解决的问题是:
如果多台服务器同时收到了语义相同的请求,应该由谁真正执行?
两者处在不同层次。
负载均衡
负责分发请求
分布式 Single-flight
负责合并跨节点重复执行
例如两个完全相同的 AI 请求:
请求 1 → Node A
请求 2 → Node B
从负载均衡角度看,这种分配是正常的,因为两个节点都健康。
但从业务角度看,两个请求可能本来只需要调用一次 AI。
所以负载均衡不会自动解决:
- 重复提交;
- 业务幂等;
- 重复消费;
- 跨节点请求合并;
- 重复外部接口调用。
这些问题还需要配合:
- 分布式 Single-flight;
- 业务幂等键;
- 分布式锁;
- 唯一约束;
- 共享缓存;
- 任务状态机。
十七、如何选择负载均衡策略
| 业务场景 | 推荐策略 |
|---|---|
| 普通无状态 HTTP 接口 | 轮询或加权轮询 |
| 服务器配置不同 | 加权轮询 |
| 请求耗时差异大 | 最少连接或最短响应时间 |
| WebSocket、SSE、长连接 | 最少连接 |
| 简单会话保持 | Cookie Sticky 或 IP Hash |
| 分布式缓存和分片 | 一致性哈希 |
| 灰度发布 | 权重、Header 或 Cookie 路由 |
| 多地域部署 | DNS 或全球流量调度 |
| 微服务内部调用 | 客户端负载均衡 |
| 根据 HTTP 路径分流 | 七层负载均衡 |
| TCP、UDP 高性能转发 | 四层负载均衡 |
总结
负载均衡的核心作用,是把原本集中在单台服务器上的请求,合理分配到多台服务器。
它主要解决:
单机性能瓶颈
单点故障
服务器负载不均
资源利用率低
系统横向扩展
滚动发布和故障摘除
一个典型的线上结构是:
┌──→ Node A
用户请求 → 负载均衡器 ├──→ Node B
└──→ Node C
同一个用户的不同请求可能落到不同服务器,这是多实例系统中的正常现象。
因此,在设计登录状态、缓存、定时任务、消息消费、业务幂等和 Single-flight 时,都不能继续假设:
同一个用户的请求一定会由同一个 JVM 处理。
更准确的系统设计前提应该是:
任意一次请求都可能进入任意一个健康实例;任意实例都可能随时扩容、下线、重启或者发生故障。
当应用在这个前提下仍然可以正确运行时,它才真正具备进入分布式环境的基础。