负载均衡
开篇:怎么把请求均匀分配给多台服务器?
想象一家餐厅有 5 个收银台,门口站着一个引导员。顾客进来了,引导员把他带到其中一个收银台。如果引导员只往第一个收银台排人,那 1 号排成了长龙,其他 4 个空着,效率极低。
聪明的引导员会怎么做?可以轮着来(1号、2号、3号...),也可以看哪个队最短就往哪个排,还可以根据收银员的熟练程度分配不同的人数。
这就是负载均衡在做的事情:把大量的请求合理地分配到多台服务器上,避免某台服务器被压垮,其他服务器却闲着。
一、负载均衡策略
负载均衡算法可以分为两大类:静态策略和动态策略。
静态策略
| 策略 | 原理 | 适用场景 |
|---|---|---|
| 轮询(Round Robin) | 按顺序依次分配 | 服务器配置相同 |
| 随机(Random) | 随机选择一台 | 简单场景 |
| 加权轮询(Weighted Round Robin) | 按权重比例分配 | 服务器配置不同(好机器多分点) |
| 哈希(Hash) | 根据请求特征 hash 到固定节点 | 需要会话保持的场景 |
| 一致性哈希(Consistent Hash) | 哈希环,节点增减只影响相邻数据 | 缓存场景,减少数据迁移 |
动态策略
| 策略 | 原理 | 适用场景 |
|---|---|---|
| 最少连接(Least Connections) | 选择当前连接数最少的 | 长连接场景 |
| 最快响应(Fastest) | 选择响应时间最短的 | 对延迟敏感的场景 |
| 响应时间加权(Weighted Response Time) | 响应越快权重越高 | 综合考虑性能 |
最常用的是轮询和加权轮询,简单高效,适用于大多数场景。如果服务器之间配置差异大,用加权;如果差异不大,直接轮询。
一个冷知识:Nginx 默认用的就是加权轮询。Dubbo 默认用的是随机策略。
二、客户端 vs 服务端负载均衡
负载均衡分为两种模式,它们的区别在于"谁来做分配决策"。
服务端负载均衡
在服务器端放一个负载均衡器(如 Nginx、LVS),所有请求先到负载均衡器,再由它分发到后端服务器。客户端不需要知道有多少台服务器,只需要知道负载均衡器的地址。
优点:客户端简单,不需要关心后端有几台机器。
缺点:负载均衡器本身可能成为瓶颈和单点故障。
客户端负载均衡
客户端自己维护一份服务器列表(通常从注册中心获取),自己选择请求哪台服务器。Ribbon 和 Spring Cloud LoadBalancer 就是这种模式。
优点:去掉了中间的负载均衡器,没有单点瓶颈,更灵活。
缺点:客户端变复杂了,需要自己维护服务列表和负载均衡逻辑。
在微服务架构中,客户端负载均衡是主流。因为服务注册中心已经提供了服务列表,客户端天然就能拿到所有实例地址,自己做负载均衡是顺理成章的事。
四层 vs 七层负载均衡
根据 OSI 模型的层次,负载均衡还可以分为:
- 四层负载均衡:工作在传输层(TCP/UDP),根据 IP + 端口转发。如 LVS、F5
- 七层负载均衡:工作在应用层(HTTP),可以根据 URL、Header、Cookie 等做更精细的路由。如 Nginx、HAProxy
四层性能更好(不需要解析应用层协议),七层更灵活(可以基于业务规则路由)。
三、Ribbon 到 Spring Cloud LoadBalancer 的演进
Ribbon 时代
Ribbon 是 Netflix 开源的客户端负载均衡框架,曾经是 Spring Cloud 的默认选择。
使用方式非常简单,在 RestTemplate 上加一个 @LoadBalanced 注解:
@Configuration
public class RestConfig {
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
}然后用服务名代替具体 IP 进行调用:
// 不用写具体 IP,用服务名即可
String result = restTemplate.getForObject(
"http://order-service/api/orders/123", String.class);原理:@LoadBalanced 会给 RestTemplate 注入一个 LoadBalancerInterceptor 拦截器。每次发请求时,拦截器会从服务名(如 order-service)中解析出真实的服务列表,根据负载均衡策略选择一个实例,替换成真实的 IP:Port。
Ribbon 提供了多种负载均衡规则:
| 规则 | 说明 |
|---|---|
| RoundRobinRule | 轮询(默认在无 Zone 时) |
| RandomRule | 随机 |
| RetryRule | 在指定时间内不断重试 |
| WeightedResponseTimeRule | 根据响应时间加权 |
| ZoneAvoidanceRule | 默认规则,多区域环境选最佳区域 |
Ribbon 默认是懒加载的,第一次调用时才创建对应的 RibbonClient,这会导致首次请求比较慢。可以通过配置
ribbon.eager-load.enabled=true来开启饥饿加载。
Spring Cloud LoadBalancer 时代
从 Spring Cloud 2020 开始,Ribbon 被标记为维护模式(不再添加新特性),官方推荐使用 Spring Cloud LoadBalancer 作为替代。
LoadBalancer 相比 Ribbon 的优势:
- 支持响应式编程(WebClient + WebFlux)
- 架构更简洁,与 Spring 生态深度集成
- 活跃维护
默认提供两种策略:
RoundRobinLoadBalancer(轮询,默认)RandomLoadBalancer(随机)
迁移方法
从 Ribbon 迁移到 LoadBalancer 很简单:
<!-- 排除 Ribbon -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- 引入 LoadBalancer -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>业务代码基本不用改,@LoadBalanced 注解继续用就行。
四、常见面试题精选
Q1:什么是负载均衡?有哪些常见算法?
负载均衡是把请求分摊到多台服务器上的技术。常见算法有:轮询(按顺序轮流)、加权轮询(按权重比例分配)、随机、最少连接(选当前连接最少的)、一致性哈希(节点增减只影响少量数据)。
Q2:Ribbon 和 Spring Cloud LoadBalancer 的区别?
Ribbon 是 Netflix 开源的客户端负载均衡器,功能丰富但已停更。Spring Cloud LoadBalancer 是官方推荐的替代品,支持响应式编程,架构更简洁。新项目直接用 LoadBalancer,老项目逐步迁移。
Q3:客户端负载均衡和服务端负载均衡有什么区别?
服务端负载均衡(如 Nginx):请求先到中间的负载均衡器,再转发到后端。客户端负载均衡(如 Ribbon):客户端自己维护服务列表,自己选择目标服务器。微服务架构中客户端负载均衡是主流,因为注册中心已经提供了服务列表。
小结
负载均衡看似简单,但在微服务架构中至关重要。记住这几个要点:
- 算法选择:大多数场景轮询就够了,服务器配置差异大用加权轮询
- 微服务用客户端负载均衡:Ribbon -> Spring Cloud LoadBalancer 的演进路线
- 四层 vs 七层:对外用七层(Nginx),内部服务间用客户端负载均衡
- 新项目用 LoadBalancer:Ribbon 已停更,迁移成本很低
负载均衡在实际架构中的位置
在一个完整的微服务架构中,负载均衡通常出现在多个层级:
- DNS 层:通过 DNS 解析到不同的 IP,实现最粗粒度的负载均衡
- Nginx 层:反向代理 + 七层负载均衡,通常做南北流量(外部用户到网关)的分发
- 网关层:Spring Cloud Gateway 可以做路由和负载均衡
- 服务间:通过 Ribbon / LoadBalancer 做客户端负载均衡(东西流量)
每一层的负载均衡策略可以不同,根据场景选择最合适的。外层更侧重高可用和流量分发,内层更侧重性能和服务治理。
面试技巧:被问到负载均衡时,不要只说算法。从架构层次(DNS -> Nginx -> 网关 -> 服务间)去讲,展示你对全链路的理解,会让面试官眼前一亮。