一、背景

在微服务架构中,服务通常有多个实例,负载均衡的作用是将请求分发到多个服务实例上,以达到高可用和扩展性的目的。Spring Cloud 提供了多种负载均衡的实现方式,主要分为客户端负载均衡和服务端负载均衡。在Spring Cloud中,我们通常使用的是客户端负载均衡,即Ribbon(虽然Ribbon已经进入维护模式,但很多项目仍在用)以及Spring Cloud LoadBalancer(Spring官方推出的替代方案)。

  1. 服务端负载均衡

    • 概念:在服务消费者和服务提供者之间有一个独立的负载均衡服务器(如Nginx、HAProxy、F5)。
    • 工作流程:消费者直接请求负载均衡器,由负载均衡器将请求转发给后端的某个服务实例。
    • 优点:对消费者透明,集中管理。
    • 缺点:可能成为性能瓶颈和单点故障点。
  2. 客户端负载均衡

    • 概念:负载均衡的逻辑集成在服务消费者内部。
    • 工作流程:
      1. 服务消费者从服务注册中心(如Nacos、Eureka)获取所有提供者的地址列表。
      2. 消费者内置的负载均衡器(如Ribbon、Spring Cloud LoadBalancer)根据特定算法(如轮询、随机、最小连接数等),直接从列表中选择一个实例。
      3. 消费者直接向选中的实例发起请求。
    • 优点:
      • 去中心化:没有中间代理,性能更高,避免了单点瓶颈。
      • 更智能:客户端可以结合本地信息(如服务健康状况、区域距离)做出更灵活的路由决策。

Spring Cloud中负载均衡主要有两种实现:Ribbon和Spring Cloud LoadBalancer。

二、核心概念

  • ServiceInstance: 代表一个服务实例,包含实例的元数据,如主机名、端口、是否安全等。
  • ServiceInstanceListSupplier: 这是 服务实例列表提供者。它的职责是从服务注册中心(如 Nacos)获取指定服务的所有健康实例列表。这是负载均衡的数据来源。
  • LoadBalancerClient: 负载均衡客户端,用于从服务实例列表中选择一个实例,并执行请求。
  • ReactorLoadBalancer: 这是响应式编程模型下的负载均衡器接口,也是当前 LoadBalancerClient 的默认实现(BlockingLoadBalancerClient 内部委托给它)。它定义了选择实例的核心方法。
  • LoadBalancerRequestFactory: 用于创建 LoadBalancerRequest,这个请求对象封装了实际的调用逻辑。
  • LoadBalancerRequestTransformer: 请求转换器,可以在实际请求发出前对请求(如 RestTemplate 的 HttpRequest 或 WebClient 的 ClientRequest)进行修改,例如添加特定的 Header。

三、实现原理

以最经典的 @LoadBalanced + RestTemplate 为例,详细拆解其工作流程:

1.启动阶段:装配与初始化
  • 注解启用: 在配置类上使用 @LoadBalanced 注解修饰一个 RestTemplate 的 Bean。这个注解引入了一个 LoadBalancerAutoConfiguration 配置类。
  • 自动配置: Spring Cloud 的自动配置机制会为这个特殊的 RestTemplate 添加一个 LoadBalancerInterceptor 拦截器。
  • 组件装配: Spring Cloud 会根据你的依赖(例如 spring-cloud-starter-loadbalancer 和 spring-cloud-starter-alibaba-nacos-discovery)自动装配好 LoadBalancerClient(通常是 BlockingLoadBalancerClient)、ServiceInstanceListSupplier(如 NacosServiceInstanceListSupplier)和 ReactorLoadBalancer(如 RoundRobinLoadBalancer)等组件。
2.调用阶段:拦截与执行

当你使用 @LoadBalanced 的 RestTemplate 发起一个包含服务名的请求时(例如 restTemplate.getForObject(“http://user-service/api/v1/user/1”, User.class)),整个流程如下:

  1. 拦截请求:LoadBalancerInterceptor 会拦截这次 HTTP 调用。它从请求的 URI 中提取出服务名 user-service。
  2. 调用 LoadBalancerClient:拦截器调用 LoadBalancerClient.execute(serviceId, request) 方法。
  3. 选择服务实例:BlockingLoadBalancerClient 内部会委托给 ReactorLoadBalancer 进行实例选择。
    • 获取实例列表: ReactorLoadBalancer 调用 ServiceInstanceListSupplier.get() 方法。ServiceInstanceListSupplier 会与 Nacos Server 通信(或从本地缓存中),获取所有健康的、名为 user-service 的服务实例列表,例如:[192.168.1.101:8080, 192.168.1.102:8080, 192.168.1.103:8080]。
    • 应用负载均衡算法: ReactorLoadBalancer 接收到实例列表后,会应用配置的负载均衡算法(如轮询),从列表中选出一个实例(例如 192.168.1.102:8080)。
  4. 重构 URI:LoadBalancerClient 使用选中的 ServiceInstance 信息,将原始的 URI http://user-service/api/v1/user/1 重构为具体的实例地址 http://192.168.1.102:8080/api/v1/user/1。
  5. 发起实际调用:LoadBalancerClient 使用 LoadBalancerRequestFactory 创建的 LoadBalancerRequest 对象,通过 ClientHttpRequestInterceptor 链(其中可能包括你配置的 LoadBalancerRequestTransformer)来最终发起一个真正的 HTTP 请求到 http://192.168.1.102:8080/api/v1/user/1。
  6. 处理响应:将得到的响应返回给 RestTemplate,最终返回给调用者。

四、Spring Cloud 中实现负载均衡的方式

在Spring Cloud中,实现负载均衡的方式主要有以下几种:

使用Ribbon(已进入维护模式,但仍在广泛使用)
  • 使用步骤:
    • 添加依赖:在服务消费者项目的pom.xml中添加Ribbon依赖(通常通过引入spring-cloud-starter-netflix-ribbon)。
      <dependency>
          <groupId>org.springframework.cloud</groupId>
          <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
      </dependency>
      
    • 启用负载均衡:在配置类中,创建一个RestTemplate的bean,并加上@LoadBalanced注解。
      @Configuration
      public class RestTemplateConfig {
      
          @Bean
          @LoadBalanced    // 关键注解:启用负载均衡
          public RestTemplate restTemplate() {
              return new RestTemplate();
          }
      }
      
    • 配置Ribbon的负载均衡策略:在application.yml中配置。
      service-provider:
        ribbon:
          NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
      
    • 通过服务名调用:在需要调用其他服务的地方,注入RestTemplate,然后使用服务名(而不是具体的IP和端口)进行调用。
      @Service
      public class MyService {
      
          @Autowired
          private RestTemplate restTemplate;
      
          public String callOtherService() {
              // 使用服务名(例如"service-provider")代替具体的地址
              String result = restTemplate.getForObject("http://service-provider/endpoint", String.class);
              return result;
          }
      }
      
使用Spring Cloud LoadBalancer(Spring官方推出的替代方案)
  • 使用步骤:
    • 添加依赖:在服务消费者项目的pom.xml中添加Spring Cloud LoadBalancer依赖。
      <dependency>
          <groupId>org.springframework.cloud</groupId>
          <artifactId>spring-cloud-starter-loadbalancer</artifactId>
      </dependency>
      
    • 配置负载均衡策略:Spring Cloud LoadBalancer默认提供了RoundRobinLoadBalancer(轮询)和RandomLoadBalancer(随机)两种策略。可以通过配置选择策略,也可以自定义策略。
      @Configuration
      public class LoadBalancerConfig {
      
          @Bean
          public ReactorLoadBalancer<ServiceInstance> randomLoadBalancer(Environment environment, LoadBalancerClientFactory loadBalancerClientFactory) {
              String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
              return new RandomLoadBalancer(loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class), name);
          }
      }
      
    • 使用LoadBalancerClient:可以直接注入LoadBalancerClient来手动选择服务实例。
      @Service
      public class MyService {
      
          @Autowired
          private LoadBalancerClient loadBalancerClient;
      
          public void doSomething() {
              ServiceInstance instance = loadBalancerClient.choose("service-provider");
              String url = instance.getUri() + "/endpoint";
              // 然后使用RestTemplate或WebClient等发送请求
          }
      }
      
使用OpenFeign(声明式的REST客户端,默认集成了负载均衡)
  • 使用步骤:
    • 添加依赖:在pom文件中添加OpenFeign的依赖。
      <dependency>
          <groupId>org.springframework.cloud</groupId>
          <artifactId>spring-cloud-starter-openfeign</artifactId>
      </dependency>
      
    • 启用Feign客户端:在启动类上添加@EnableFeignClients注解。
      @SpringBootApplication
      @EnableFeignClients
      public class Application {
      
          public static void main(String[] args) {
              SpringApplication.run(Application.class, args);
          }
      }
      
    • 定义Feign客户端接口:定义一个接口,使用@FeignClient注解,并指定服务名。
      @FeignClient(name = "service-provider")
      public interface ServiceProviderClient {
      
          @GetMapping("/endpoint")
          String callEndpoint();
      }
      
    • 使用Feign客户端:在需要的地方注入这个接口,并调用方法。
      @Service
      public class MyService {
      
          @Autowired
          private ServiceProviderClient serviceProviderClient;
      
          public String callOtherService() {
              return serviceProviderClient.callEndpoint();
          }
      }
      

五、负载均衡算法

Spring Cloud 提供了多种负载均衡算法,主要通过 Ribbon 和 Spring Cloud LoadBalancer 两个组件来实现。

5.1 Ribbon提供了以下常见的内置策略:
  • RoundRobinRule:轮询算法,按顺序依次选择服务实例。
  • RandomRule:随机算法,随机选择服务实例。
  • WeightedResponseTimeRule:权重算法,根据响应时间计算权重,响应时间越短,权重越大,被选中的概率越高。
  • BestAvailableRule:最小连接数算法,先过滤掉故障实例,然后选择并发请求最小的实例。
  • RetryRule:重试算法,先按照轮询算法选择实例,如果选择实例失败,则在指定时间内重试,选择可用的实例。
  • AvailabilityFilteringRule:先过滤掉故障实例和并发请求超过阈值的实例,然后轮询选择。
  • ZoneAvoidanceRule:默认规则,综合判断服务器所在区域的性能和服务的可用性,轮询选择。

配置Ribbon负载均衡算法:
可以通过新建配置类或者application.yml配置文件进行负载均衡器的设置。

  • 通过配置类设置:

    @Configuration
    public class RibbonLoadBalancerConfig {
        
        /**
         * 全局负载均衡配置
         */
        @Bean
        public IRule ribbonRule() {
            // 返回自定义的负载均衡规则
            return new WeightedResponseTimeRule();
        }
        
        /**
         * 特定服务的负载均衡配置
         */
        @RibbonClient(name = "user-service", configuration = UserServiceRibbonConfig.class)
        public class UserServiceRibbonConfig {
            
            @Bean
            public IRule ribbonRule() {
                // 为用户服务配置随机算法
                return new RandomRule();
            }
        }
        
        @RibbonClient(name = "product-service", configuration = ProductServiceRibbonConfig.class)
        public class ProductServiceRibbonConfig {
            
            @Bean
            public IRule ribbonRule() {
                // 为商品服务配置轮询算法
                return new RoundRobinRule();
            }
        }
    }
    
  • 通过配置文件设置:在application.yml中配置:对于名为user-service的服务,配置随机算法:

    user-service:
      ribbon:
        # 负载均衡算法配置
        NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RoundRobinRule
        # NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
        # NFLoadBalancerRuleClassName: com.netflix.loadbalancer.WeightedResponseTimeRule
        # NFLoadBalancerRuleClassName: com.netflix.loadbalancer.BestAvailableRule
        
        # 连接超时
        ConnectTimeout: 2000
        # 读取超时
        ReadTimeout: 5000
        # 最大重试次数
        MaxAutoRetries: 1
        # 最大重试下一个服务次数
        MaxAutoRetriesNextServer: 1
        # 是否对所有操作重试
        OkToRetryOnAllOperations: false
    
5.2 Spring Cloud LoadBalancer 负载均衡算法

Spring Cloud LoadBalancer默认提供了两种负载均衡策略:

  • RoundRobinLoadBalancer:轮询算法
  • RandomLoadBalancer:随机算法

配置Spring Cloud LoadBalancer负载均衡算法:
可以通过新建配置类或者application.yml配置文件进行负载均衡器的设置。

  • 通过配置类设置:全局的随机负载均衡策略
    @Configuration
    @LoadBalancerClient(name = "user-service", configuration = LoadBalancerConfiguration.class)
    public class LoadBalancerConfiguration {
    
        @Bean
        public ReactorLoadBalancer<ServiceInstance> randomLoadBalancer(Environment environment, LoadBalancerClientFactory loadBalancerClientFactory) {
            String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
            // 返回随机负载均衡策略
            return new RandomLoadBalancer(loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class), name);
        }
    }
    
  • 通过配置文件设置
    spring:
      cloud:
        loadbalancer:
          # 全局配置
          cache:
            enabled: true
            capacity: 256
          health-check:
            initial-delay: 2s
            interval: 30s
          
          # 特定服务配置
          configurations:
            user-service:
              # 使用随机算法
              name: random
            product-service:
              # 使用轮询算法  
              name: round_robin
    
5.3 自定义负载均衡策略

如果内置的算法不满足需求,我们可以自定义负载均衡算法。以Spring Cloud LoadBalancer为例,自定义一个权重负载均衡算法:

  1. 实现ReactorLoadBalancer接口
  2. 在配置中注册自定义的负载均衡器

示例:自定义权重负载均衡算法

public class WeightedLoadBalancer implements ReactorLoadBalancer<ServiceInstance> {

    private final ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider;
    private final String serviceId;

    public WeightedLoadBalancer(ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider, String serviceId) {
        this.serviceInstanceListSupplierProvider = serviceInstanceListSupplierProvider;
        this.serviceId = serviceId;
    }

    @Override
    public Mono<Response<ServiceInstance>> choose(Request request) {
        ServiceInstanceListSupplier supplier = serviceInstanceListSupplierProvider.getIfAvailable();
        return supplier.get().next().map(serviceInstances -> {
            // 根据权重选择实例
            ServiceInstance instance = chooseInstance(serviceInstances);
            if (instance != null) {
                return new DefaultResponse(instance);
            }
            return new EmptyResponse();
        });
    }

    private ServiceInstance chooseInstance(List<ServiceInstance> instances) {
        // 计算总权重
        int totalWeight = 0;
        for (ServiceInstance instance : instances) {
            int weight = getWeight(instance);
            totalWeight += weight;
        }

        // 随机一个数
        int random = new Random().nextInt(totalWeight);
        int current = 0;
        for (ServiceInstance instance : instances) {
            current += getWeight(instance);
            if (random < current) {
                return instance;
            }
        }
        return instances.get(0);
    }

    private int getWeight(ServiceInstance instance) {
        // 从元数据中获取权重,默认权重为1
        String weightStr = instance.getMetadata().get("weight");
        if (weightStr != null) {
            return Integer.parseInt(weightStr);
        }
        return 1;
    }
}

然后在配置类中注册这个负载均衡器:

@Configuration
@LoadBalancerClient(name = "user-service", configuration = CustomLoadBalancerConfiguration.class)
public class CustomLoadBalancerConfiguration {

    @Bean
    public ReactorLoadBalancer<ServiceInstance> weightedLoadBalancer(Environment environment, LoadBalancerClientFactory loadBalancerClientFactory) {
        String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
        // 返回自定义的负载均衡器
        return new WeightedLoadBalancer(loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class), name);
    }
}

六、与其他组件集成

  1. 与 OpenFeign 的集成
    OpenFeign 是一个声明式的 HTTP 客户端,它与 Spring Cloud LoadBalancer 的集成是 无缝且默认的。

    • 当你声明一个 Feign 客户端(如 @FeignClient(“user-service”))时,Spring Cloud 会自动为其配置一个负载均衡器。
    • Feign 在底层会调用 LoadBalancerClient.choose(“user-service”) 来选取实例,然后构造并发送 HTTP 请求。
    • 其内部原理与 RestTemplate 类似,但实现细节被 Feign 的框架所封装。
  2. 与 Spring Cloud Gateway 的集成
    在 API 网关中,负载均衡同样至关重要。当在 Gateway 的路由配置中使用了 lb://user-service 这样的 URI 时:

    spring:
      cloud:
        gateway:
          routes:
            - id: user-service-route
              uri: lb://user-service # 使用 lb 协议,表示需要负载均衡
              predicates:
                - Path=/api/v1/user/**
    
    • Gateway 会识别 lb 协议。
    • 它内部的 ReactiveLoadBalancerClientFilter 会拦截请求。
    • 该过滤器调用 ReactorLoadBalancer 来为 user-service 选择一个实例。
    • 然后将请求转发到选中的具体实例上。
Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐