Skip to content

客户端负载均衡

客户端负载均衡是微服务架构中实现服务高可用和横向扩展的关键技术之一。与传统的服务器端负载均衡不同,客户端负载均衡将负载均衡逻辑放在了服务消费者一端,由服务消费者自行决定请求发送到哪个服务提供者实例。

为什么需要客户端负载均衡?

在微服务架构中,一个服务通常会部署多个实例以提高服务的可用性和性能。当服务消费者调用服务提供者时,需要从多个服务实例中选择一个进行调用。客户端负载均衡具有以下优势:

  1. 减少单点故障:通过将请求分散到多个服务实例,避免单一实例故障导致整个服务不可用
  2. 提高系统吞吐量:合理分配请求可以充分利用所有服务实例的资源,提高系统整体处理能力
  3. 灵活的负载均衡策略:可以根据业务需求选择不同的负载均衡算法
  4. 减轻服务器负担:减少了对中央负载均衡器的依赖,分散了负载均衡的压力

服务器端负载均衡 vs 客户端负载均衡

服务器端负载均衡

传统的负载均衡通常在服务器端实现,如使用 Nginx、F5 等专业负载均衡设备。服务消费者只知道负载均衡器的地址,由负载均衡器将请求转发到实际的服务提供者。

优点

  • 对客户端透明,客户端不需要关心负载均衡细节
  • 便于集中式管理和配置

缺点

  • 负载均衡器可能成为性能瓶颈
  • 增加了网络跳转,可能导致额外的延迟
  • 负载均衡器故障会影响整个系统

客户端负载均衡

在客户端负载均衡模式下,服务消费者从服务注册中心获取所有可用的服务实例信息,然后根据某种规则选择一个实例进行调用。

优点

  • 减少了额外的网络跳转,降低请求延迟
  • 分散了负载均衡的压力,避免了中央负载均衡器的单点故障
  • 可以根据客户端的特定需求定制负载均衡策略

缺点

  • 增加了客户端的复杂性
  • 需要与服务发现机制紧密集成
  • 各个客户端的负载均衡策略可能不一致,导致流量分配不均衡

Spring Cloud 中的负载均衡解决方案

Spring Cloud 提供了多种客户端负载均衡的实现,最初主要是基于 Netflix Ribbon,后来推出了 Spring Cloud LoadBalancer 作为替代方案。

Spring Cloud Netflix Ribbon

Ribbon 是由 Netflix 开发的客户端负载均衡器,它与 Spring Cloud 生态系统无缝集成。

引入 Ribbon 依赖

xml
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>

注意:在 Spring Cloud 2020.0.0 版本之后,Ribbon 已被移除,推荐使用 Spring Cloud LoadBalancer。

配置 Ribbon

Ribbon 可以通过 YAML 或 Properties 文件进行配置:

yaml
ribbon:
  # 每台服务器最多重试次数,不包括第一次调用
  MaxAutoRetries: 1
  # 最多重试其他服务器的次数
  MaxAutoRetriesNextServer: 1
  # 是否所有操作都重试
  OkToRetryOnAllOperations: false
  # 连接超时时间
  ConnectTimeout: 3000
  # 读取超时时间
  ReadTimeout: 5000

# 为特定服务配置 Ribbon
service-name:
  ribbon:
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule

Ribbon 负载均衡策略

Ribbon 提供了多种内置的负载均衡策略:

  1. RoundRobinRule:轮询策略,默认策略,按顺序循环选择服务实例
  2. RandomRule:随机策略,随机选择服务实例
  3. RetryRule:重试策略,在一定时间内反复重试选择可用的服务实例
  4. WeightedResponseTimeRule:响应时间加权策略,根据实例的响应时间分配权重,响应时间越短权重越大
  5. BestAvailableRule:最小并发策略,选择并发请求数最小的实例
  6. AvailabilityFilteringRule:可用性过滤策略,过滤掉故障实例和并发请求数超过阈值的实例
  7. ZoneAvoidanceRule:区域感知策略,综合考虑服务所在区域的性能和可用性

自定义 Ribbon 策略

您可以通过实现 IRule 接口来自定义负载均衡策略:

java
public class CustomRule extends AbstractLoadBalancerRule {
    @Override
    public Server choose(Object key) {
        // 实现自定义的选择逻辑
        return null;
    }

    @Override
    public void initWithNiwsConfig(IClientConfig clientConfig) {
        // 初始化配置
    }
}

然后在配置类中定义:

java
@Configuration
public class RibbonConfig {
    @Bean
    public IRule ribbonRule() {
        return new CustomRule();
    }
}

Spring Cloud LoadBalancer

Spring Cloud LoadBalancer 是 Spring 官方提供的客户端负载均衡器,用于替代 Netflix Ribbon。它与 Spring WebFlux 和响应式编程更好地集成,并且对 Spring Cloud 的其他组件有更好的兼容性。

引入 LoadBalancer 依赖

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

如果项目中同时存在 Ribbon 和 LoadBalancer,可以通过以下配置禁用 Ribbon:

yaml
spring:
  cloud:
    loadbalancer:
      ribbon:
        enabled: false

配置 LoadBalancer

yaml
spring:
  cloud:
    loadbalancer:
      cache:
        # 开启缓存
        enabled: true
        # 缓存生存时间,单位秒
        ttl: 30
        # 缓存的服务实例的上限
        capacity: 256
      retry:
        # 启用重试机制
        enabled: true
        # 连接所选实例的最大重试次数
        max-retries-on-same-service-instance: 1
        # 更换实例重试的最大次数
        max-retries-on-next-service-instance: 1

LoadBalancer 负载均衡策略

Spring Cloud LoadBalancer 提供了两种内置策略:

  1. ReactorServiceInstanceLoadBalancer (RoundRobinLoadBalancer):轮询策略,默认策略
  2. RandomLoadBalancer:随机策略

自定义 LoadBalancer 策略

可以通过实现 ReactorServiceInstanceLoadBalancer 接口自定义负载均衡策略:

java
public class CustomLoadBalancer implements ReactorServiceInstanceLoadBalancer {
    private final String serviceId;
    private final ServiceInstanceListSupplier serviceInstanceListSupplier;

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

    @Override
    public Mono<Response<ServiceInstance>> choose(Request request) {
        // 实现自定义的负载均衡逻辑
        return serviceInstanceListSupplier.get()
                .next()
                .map(serviceInstances -> {
                    // 自定义选择逻辑
                    ServiceInstance instance = selectInstance(serviceInstances);
                    return new DefaultResponse(instance);
                });
    }

    private ServiceInstance selectInstance(List<ServiceInstance> instances) {
        // 实现自定义的选择算法
        if (instances.isEmpty()) {
            return null;
        }
        // 这里简单返回第一个实例,实际应该实现自定义逻辑
        return instances.get(0);
    }
}

然后通过 @Configuration 注册:

java
@Configuration
public class LoadBalancerConfig {
    @Bean
    public ServiceInstanceListSupplier discoveryClientServiceInstanceListSupplier(
            DiscoveryClient discoveryClient) {
        return ServiceInstanceListSupplier.builder()
                .withDiscoveryClient()
                .withCaching()
                .build(discoveryClient);
    }

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

在 Spring Cloud 中使用负载均衡

与 RestTemplate 集成

在使用 RestTemplate 时,只需添加 @LoadBalanced 注解即可启用负载均衡:

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

然后使用服务名称代替主机名和端口:

java
@Autowired
private RestTemplate restTemplate;

public String callService() {
    // 使用服务名称而不是具体的 URL
    return restTemplate.getForObject("http://service-name/api/resource", String.class);
}

与 WebClient 集成

对于响应式编程,可以使用支持负载均衡的 WebClient:

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

使用示例:

java
@Autowired
private WebClient.Builder webClientBuilder;

public Mono<String> callService() {
    return webClientBuilder.build()
            .get()
            .uri("http://service-name/api/resource")
            .retrieve()
            .bodyToMono(String.class);
}

与 OpenFeign 集成

Spring Cloud OpenFeign 内置了负载均衡支持:

java
@FeignClient(name = "service-name")
public interface ServiceClient {
    @GetMapping("/api/resource")
    String getResource();
}

负载均衡的高级功能

区域感知负载均衡

在多区域部署的情况下,可以启用区域感知负载均衡,优先选择同区域的服务实例:

yaml
spring:
  cloud:
    loadbalancer:
      zone: zone1
      zones:
        zone1: http://zone1-url
        zone2: http://zone2-url

权重负载均衡

对服务实例设置不同的权重:

java
@Bean
public ServiceInstanceListSupplier serviceInstanceListSupplier() {
    return ServiceInstanceListSupplier.builder()
            .withDiscoveryClient()
            .withWeighted(instanceWeight -> {
                // 根据实例信息返回权重值
                return 1.0; // 默认权重
            })
            .build();
}

金丝雀发布支持

在实施金丝雀发布时,可以通过自定义负载均衡策略控制流量分配:

java
public class CanaryLoadBalancer implements ReactorServiceInstanceLoadBalancer {
    @Override
    public Mono<Response<ServiceInstance>> choose(Request request) {
        // 基于请求参数或头信息决定是否路由到金丝雀实例
    }
}

最佳实践

  1. 根据业务需求选择合适的负载均衡策略:不同的业务场景可能需要不同的负载均衡策略,如对响应时间敏感的业务可以使用 WeightedResponseTimeRule

  2. 设置合理的超时和重试参数:避免过长的等待时间或过多的重试导致资源浪费

  3. 结合服务熔断:搭配 Circuit Breaker 使用,快速隔离故障服务

  4. 监控负载均衡效果:通过指标监控各服务实例的负载情况,及时调整负载均衡策略

  5. 使用缓存:启用 LoadBalancer 的缓存功能,减少服务发现的开销

  6. 考虑故障转移:在负载均衡策略中考虑故障转移逻辑,确保系统的韧性

  7. 定期更新服务列表:确保负载均衡器使用的服务实例列表是最新的

总结

客户端负载均衡是微服务架构中的重要组成部分,它通过在客户端实现负载分散,提高了系统的可用性和性能。Spring Cloud 提供了丰富的负载均衡实现,从早期的 Ribbon 到现在的 Spring Cloud LoadBalancer,支持多种负载均衡策略和灵活的配置选项。

在设计微服务系统时,应根据具体业务需求选择合适的负载均衡方案,并与服务注册发现、熔断降级等机制配合使用,构建健壮的微服务架构。