跳到主要内容

负载均衡

负载均衡是分布式系统保证高可用和可扩展性的关键技术。在微服务架构中,一个服务通常有多个实例,负载均衡决定了请求应该发送到哪个实例。

为什么需要负载均衡

理解负载均衡的价值,需要先看看没有负载均衡时面临的问题。

单实例的局限性

当一个服务只有一个实例时,所有请求都打到这个实例上。这个实例成为系统的瓶颈和单点故障。如果实例宕机,服务就完全不可用。即使实例正常运行,大量请求也可能导致响应缓慢甚至崩溃。

多实例带来的挑战

部署多个实例可以解决单点问题和性能问题,但带来了新问题:客户端如何知道有哪些实例可用?请求应该发送到哪个实例?某个实例宕机了怎么办?不同实例的性能差异如何处理?

负载均衡器负责解决这些问题。它维护可用实例列表,根据策略选择实例,监控实例健康状态,在实例故障时自动剔除。

服务端与客户端负载均衡

负载均衡有两种实现方式:服务端负载均衡和客户端负载均衡。

服务端负载均衡在服务提供者前面部署一个独立的负载均衡器(如 Nginx、F5)。客户端请求发送到负载均衡器,由它转发到后端实例。这种方式的好处是客户端简单,不需要知道后端实例。缺点是负载均衡器本身可能成为瓶颈和单点故障。

客户端负载均衡将负载均衡逻辑放在客户端。客户端从注册中心获取实例列表,自己决定请求发送到哪个实例。这种方式没有中间层,性能更好,也不会有单点故障。但客户端实现复杂了,需要维护实例列表和负载均衡逻辑。

Spring Cloud 采用客户端负载均衡模式,Spring Cloud LoadBalancer 是官方提供的负载均衡组件。

Spring Cloud LoadBalancer 入门

Spring Cloud LoadBalancer 是 Spring Cloud 官方的客户端负载均衡器,用于替代已停止维护的 Netflix Ribbon。

添加依赖

在 pom.xml 中添加依赖:

<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>

如果之前使用了 Ribbon,需要禁用它:

spring:
cloud:
loadbalancer:
ribbon:
enabled: false

使用 RestTemplate 进行负载均衡

在 RestTemplate 上添加 @LoadBalanced 注解,即可启用负载均衡:

@Configuration
public class RestTemplateConfig {

@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
}

使用时直接通过服务名调用:

@Service
public class OrderService {

@Autowired
private RestTemplate restTemplate;

public User getUser(Long userId) {
// user-service 是服务名,LoadBalancer 会自动选择实例
String url = "http://user-service/user/" + userId;
return restTemplate.getForObject(url, User.class);
}
}

LoadBalancer 会从注册中心获取 user-service 的实例列表,然后根据负载均衡策略选择一个实例,将请求发送到该实例。

使用 WebClient 进行负载均衡

WebClient 是 Spring WebFlux 提供的响应式 HTTP 客户端,也支持负载均衡:

@Configuration
public class WebClientConfig {

@Bean
@LoadBalanced
public WebClient.Builder webClientBuilder() {
return WebClient.builder();
}
}

使用方式:

@Service
public class OrderService {

@Autowired
private WebClient.Builder webClientBuilder;

public Mono<User> getUser(Long userId) {
return webClientBuilder.build()
.get()
.uri("http://user-service/user/" + userId)
.retrieve()
.bodyToMono(User.class);
}
}

使用 RestClient 进行负载均衡

Spring Boot 3.2 引入了 RestClient,它是 RestTemplate 的现代替代品:

@Configuration
public class RestClientConfig {

@Bean
@LoadBalanced
public RestClient.Builder restClientBuilder() {
return RestClient.builder();
}
}

使用方式:

@Service
public class OrderService {

@Autowired
private RestClient.Builder restClientBuilder;

public User getUser(Long userId) {
RestClient restClient = restClientBuilder.build();
return restClient.get()
.uri("http://user-service/user/" + userId)
.retrieve()
.body(User.class);
}
}

负载均衡策略

Spring Cloud LoadBalancer 内置两种基本策略:轮询和随机。默认使用轮询策略。

轮询策略

轮询策略按顺序依次选择每个实例。假设有三个实例 A、B、C,请求会依次发送到 A、B、C、A、B、C...这种方式简单公平,适合实例性能相近的场景。

轮询是默认策略,无需额外配置。

随机策略

随机策略从实例列表中随机选择一个实例。对于大量请求,随机策略在统计上也能实现均匀分布。

要使用随机策略,需要自定义配置:

public class RandomLoadBalancerConfig {

@Bean
ReactorLoadBalancer<ServiceInstance> randomLoadBalancer(
Environment environment,
LoadBalancerClientFactory factory) {
String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
return new RandomLoadBalancer(
factory.getLazyProvider(name, ServiceInstanceListSupplier.class),
name
);
}
}

然后在服务消费端应用这个配置:

@Configuration
@LoadBalancerClient(value = "user-service", configuration = RandomLoadBalancerConfig.class)
public class LoadBalancerConfiguration {
}

注意配置类不要添加 @Configuration 注解,或者放在 component scan 扫描范围之外,否则会影响所有服务的负载均衡。

高级负载均衡特性

Spring Cloud LoadBalancer 提供了多种高级特性,满足不同的业务场景。

权重负载均衡

当实例性能不同时,可以通过权重控制流量分配。高性能实例分配更多请求,低性能实例分配较少请求。

启用权重负载均衡:

spring:
cloud:
loadbalancer:
configurations: weighted

或者在自定义配置中使用:

public class WeightedLoadBalancerConfig {

@Bean
public ServiceInstanceListSupplier serviceInstanceListSupplier(
ConfigurableApplicationContext context) {
return ServiceInstanceListSupplier.builder()
.withDiscoveryClient()
.withWeighted()
.withCaching()
.build(context);
}
}

权重通过服务实例的元数据配置。在 Nacos 中注册服务时设置:

spring:
cloud:
nacos:
discovery:
metadata:
weight: 80 # 权重值

权重值默认为 1,值越大分配的请求越多。如果某实例权重为 80,另一实例权重为 20,则前者获得约 80% 的请求。

区域感知负载均衡

在多区域部署时,优先选择同区域的实例可以减少网络延迟。只有当同区域没有可用实例时,才选择其他区域的实例。

启用区域感知:

spring:
cloud:
loadbalancer:
configurations: zone-preference

或者在自定义配置中使用:

public class ZoneLoadBalancerConfig {

@Bean
public ServiceInstanceListSupplier serviceInstanceListSupplier(
ConfigurableApplicationContext context) {
return ServiceInstanceListSupplier.builder()
.withDiscoveryClient()
.withCaching()
.withZonePreference()
.build(context);
}
}

配置当前服务所在区域:

spring:
cloud:
loadbalancer:
zone: shanghai

服务实例也需要在元数据中声明所属区域:

spring:
cloud:
nacos:
discovery:
metadata:
zone: shanghai

健康检查

负载均衡器默认依赖注册中心的健康状态,但有时需要更细粒度的健康检查。可以启用 LoadBalancer 的主动健康检查:

public class HealthCheckLoadBalancerConfig {

@Bean
public ServiceInstanceListSupplier serviceInstanceListSupplier(
ConfigurableApplicationContext context) {
return ServiceInstanceListSupplier.builder()
.withDiscoveryClient()
.withHealthChecks()
.build(context);
}
}

配置健康检查参数:

spring:
cloud:
loadbalancer:
health-check:
initial-delay: 0s # 初始延迟
interval: 25s # 检查间隔
path:
default: /actuator/health # 默认健康检查路径
user-service: /health # 特定服务的路径
port: 8080 # 健康检查端口(可选)

健康检查会定期向实例发送请求,如果实例不健康则从候选列表中移除。注意需要添加 spring-boot-starter-actuator 依赖来暴露健康检查端点。

会话保持(粘性会话)

某些场景需要将同一用户的请求始终路由到同一实例。Spring Cloud LoadBalancer 提供两种会话保持方式。

实例偏好保持:优先选择上次使用的实例,如果该实例不可用才选择其他实例。

public class StickySessionLoadBalancerConfig {

@Bean
public ServiceInstanceListSupplier serviceInstanceListSupplier(
ConfigurableApplicationContext context) {
return ServiceInstanceListSupplier.builder()
.withDiscoveryClient()
.withSameInstancePreference()
.build(context);
}
}

基于请求的会话保持:从请求 Cookie 中读取实例 ID,将请求路由到该实例。

spring:
cloud:
loadbalancer:
configurations: request-based-sticky-session
sticky-session:
add-service-instance-cookie: true # 自动添加实例ID到响应Cookie

默认 Cookie 名为 sc-lb-instance-id,可以通过 spring.cloud.loadbalancer.instance-id-cookie-name 修改。

子集选择

当服务实例数量很多时,每个客户端选择所有实例的一个子集可以减少连接数和状态维护开销。子集选择使用确定性算法,保证同一客户端始终选择相同的实例子集。

public class SubsetLoadBalancerConfig {

@Bean
public ServiceInstanceListSupplier serviceInstanceListSupplier(
ConfigurableApplicationContext context) {
return ServiceInstanceListSupplier.builder()
.withDiscoveryClient()
.withSubset()
.withCaching()
.build(context);
}
}

配置子集大小:

spring:
cloud:
loadbalancer:
subset:
size: 10 # 子集大小,默认100

缓存配置

LoadBalancer 默认使用缓存来存储服务实例列表,避免每次请求都查询注册中心。

缓存实现

Spring Cloud LoadBalancer 支持两种缓存实现。如果类路径下有 Caffeine,则使用 Caffeine 缓存;否则使用默认的 DefaultLoadBalancerCache

添加 Caffeine 依赖:

<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
</dependency>

缓存配置

spring:
cloud:
loadbalancer:
cache:
enabled: true # 启用缓存,默认true
ttl: 35s # 缓存过期时间,默认35秒
capacity: 256 # 缓存初始容量,默认256
caffeine:
spec: maximumSize=500,expireAfterWrite=30s # Caffeine自定义配置

生产环境建议启用缓存。如果注册中心已经做了缓存(如 Eureka),可以禁用 LoadBalancer 缓存避免双重缓存。

提示路由

提示路由允许根据请求中的提示信息选择特定实例。这对于金丝雀发布、A/B 测试等场景很有用。

配置提示路由

public class HintLoadBalancerConfig {

@Bean
public ServiceInstanceListSupplier serviceInstanceListSupplier(
ConfigurableApplicationContext context) {
return ServiceInstanceListSupplier.builder()
.withDiscoveryClient()
.withCaching()
.withHints()
.build(context);
}
}

使用提示

通过请求头传递提示:

// 客户端请求时添加提示头
webClient.get()
.uri("http://user-service/user/1")
.header("X-SC-LB-Hint", "canary") // 默认头名称
.retrieve()
.bodyToMono(User.class);

服务实例在元数据中声明支持的提示:

spring:
cloud:
nacos:
discovery:
metadata:
hint: canary # 该实例支持 canary 提示

当请求带有 X-SC-LB-Hint: canary 时,LoadBalancer 会优先选择元数据中 hint=canary 的实例。

自定义负载均衡策略

当内置策略不能满足需求时,可以实现自己的负载均衡策略。

实现 ReactorLoadBalancer

public class CustomLoadBalancer implements ReactorLoadBalancer<ServiceInstance> {

private final String serviceId;
private final ServiceInstanceListSupplier serviceInstanceListSupplier;

public CustomLoadBalancer(String serviceId,
ServiceInstanceListSupplier supplier) {
this.serviceId = serviceId;
this.serviceInstanceListSupplier = supplier;
}

@Override
public Mono<Response<ServiceInstance>> choose(Request request) {
return serviceInstanceListSupplier.get()
.next()
.map(instances -> {
// 自定义选择逻辑
ServiceInstance selected = selectInstance(instances);
return new DefaultResponse(selected);
});
}

private ServiceInstance selectInstance(List<ServiceInstance> instances) {
// 实现自己的选择算法
// 例如:根据实例的 CPU 使用率选择负载最低的
return instances.get(0);
}
}

注册自定义负载均衡器

public class CustomLoadBalancerConfig {

@Bean
ReactorLoadBalancer<ServiceInstance> customLoadBalancer(
Environment environment,
LoadBalancerClientFactory factory) {
String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
return new CustomLoadBalancer(
name,
factory.getLazyProvider(name, ServiceInstanceListSupplier.class)
);
}
}

监控与统计

Spring Cloud LoadBalancer 集成了 Micrometer,提供详细的负载均衡指标。

启用统计

spring:
cloud:
loadbalancer:
stats:
micrometer:
enabled: true

需要项目中包含 Micrometer 和 MeterRegistry(如 Spring Boot Actuator)。

可用指标

启用后,LoadBalancer 会注册以下指标:

loadbalancer.requests.active 是一个 Gauge,显示当前活跃请求数。

loadbalancer.requests.success 是一个 Timer,记录成功请求的执行时间。

loadbalancer.requests.failed 是一个 Timer,记录失败请求的执行时间。

loadbalancer.requests.discard 是一个 Counter,记录被丢弃的请求数(未获取到实例)。

这些指标带有服务名、实例 ID 等标签,便于在 Prometheus 或其他监控系统中聚合分析。

生命周期回调

通过实现 LoadBalancerLifecycle 接口,可以在负载均衡的不同阶段执行自定义逻辑:

@Component
public class MyLoadBalancerLifecycle implements LoadBalancerLifecycle {

@Override
public void onStart(Request request) {
// 负载均衡开始前
}

@Override
public void onStartRequest(Request request, Response response) {
// 请求发送前
}

@Override
public void onComplete(CompletionContext context) {
// 请求完成后
if (context.getThrowable() != null) {
// 请求失败
log.error("LoadBalancer request failed", context.getThrowable());
}
}
}

与 Nacos 集成

Spring Cloud LoadBalancer 与 Nacos 无缝集成,只需添加 Nacos 服务发现依赖:

<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>

配置 Nacos 地址:

spring:
cloud:
nacos:
discovery:
server-addr: localhost:8848

LoadBalancer 会自动使用 Nacos 作为服务实例来源。Nacos 的元数据可以用于权重、区域、提示等高级特性。

与 OpenFeign 配合

OpenFeign 默认使用 Spring Cloud LoadBalancer 进行负载均衡。只需要添加 LoadBalancer 依赖,Feign 客户端就会自动负载均衡:

@FeignClient("user-service")
public interface UserClient {

@GetMapping("/user/{id}")
User getUser(@PathVariable("id") Long id);
}

调用 Feign 客户端时,请求会自动负载均衡到 user-service 的多个实例。

最佳实践

选择合适的策略

实例性能相近时使用轮询或随机策略;实例性能差异大时使用权重策略;多区域部署时使用区域感知策略;需要会话保持时使用粘性会话策略。

合理配置缓存

生产环境必须启用缓存,缓存 TTL 应该略大于注册中心的心跳间隔。过短的 TTL 会增加注册中心压力,过长的 TTL 会导致实例变更感知延迟。

监控和告警

启用 Micrometer 统计,监控活跃请求数、失败请求数等指标。设置告警,当失败请求数异常增加时及时通知。

配合断路器使用

负载均衡器可以感知实例健康,但不能处理服务整体故障。建议配合 Sentinel 或 Resilience4j 使用,当服务大量失败时触发熔断。

小结

本章详细介绍了负载均衡的概念和 Spring Cloud LoadBalancer 的使用:

负载均衡解决了多实例请求分发的问题,Spring Cloud LoadBalancer 是 Spring Cloud 官方的客户端负载均衡组件,内置轮询和随机策略,支持权重、区域感知、健康检查、会话保持等高级特性,可以与 Nacos、OpenFeign 等组件无缝集成,建议启用缓存和监控,配合断路器使用提高系统容错能力。