Spring Cloud loadbalancer 源码速读

"Spring Cloud loadbalancer 源码速读"

Posted by tablesheep on January 25, 2022

Spring Cloud loadbalancer

Spring Cloud 2020版本以后默认移除了对Netflix的依赖,使用Spring Cloud Commons提供抽象和公共类,用于不同的Spring Cloud实现。而对于负载均衡来说使用Spring Cloud loadbalancer 替代了Ribbon

主要的类

loadbalance

ReactiveLoadBalancerReactorLoadBalancer:负载均衡接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public interface ReactiveLoadBalancer<T> {

   /**
    * Default implementation of a request.
    */
   Request<DefaultRequestContext> REQUEST = new DefaultRequest<>();

   /**
    * Choose the next server based on the load balancing algorithm.
    * @param request - incoming request
    * @return publisher for the response
    */
   @SuppressWarnings("rawtypes")
   Publisher<Response<T>> choose(Request request);

   default Publisher<Response<T>> choose() { // conflicting name
      return choose(REQUEST);
   }
}

ReactorServiceInstanceLoadBalancer:基于ServiceInstance的负载均衡接口

1
2
public interface ReactorServiceInstanceLoadBalancer extends ReactorLoadBalancer<ServiceInstance> {
}

ReactiveLoadBalancer.FactoryReactiveLoadBalancer工厂

LoadBalancerClientFactory:基于ServiceInstanceReactiveLoadBalancer工厂,同时继承了NamedContextFactory

1
2
3
4
public class LoadBalancerClientFactory extends NamedContextFactory<LoadBalancerClientSpecification>
      implements ReactiveLoadBalancer.Factory<ServiceInstance> {
    ......
}

ServiceInstanceServiceInstanceListSupplier:服务实例&服务实例列表提供商

默认所有的Loadbalancer都实现了ReactorServiceInstanceLoadBalancer,所以它们实现的choose方法返回值最终都是Response<ServiceInstance>。实际上看过这几个LoadBalancer的实现会发现它们都是依赖于ServiceInstanceListSupplier提供的List<ServiceInstancel>,在使用各自的算法筛选ServiceInstance

Spring Cloud loadbalancer 配置

LoadBalancerAutoConfiguration:获取所有的负载均衡配置(LoadBalancerClientSpecificationLoadBalancerClientsProperties),注入LoadBalancerClientFactory

LoadBalancerClientConfiguration:负载均衡客户端默认配置,默认注入了RoundRobinLoadBalancer以及根据环境注入ServiceInstanceListSupplier

@LoadBalancerClients@LoadBalancerClientLoadBalancerClientSpecificationLoadBalancerClientConfigurationRegistrar:配置组件们

BlockingLoadBalancerClientAutoConfigurationRestTemplate负载均衡配置

配置主要流程

注解@LoadBalancerClients@LoadBalancerClient通过@Import导入LoadBalancerClientConfigurationRegistrar,而registrar根据注解的信息构建LoadBalancerClientSpecification类型的BeanDefinition,最后在自动装配时会获取所有的配置注入到LoadBalancerClientFactory中。

LoadBalancerClientFactory继承了org.springframework.cloud.context.named.NamedContextFactory ,配合@LoadBalancerClients或者@LoadBalancerClient,可以实现对于不同负载均衡实例的独立配置。(Spring Cloud Open Feign也是根据NamedContentFactory实现不同Feign的独立配置,大致上就是根据name构建子AnnotationConfigApplicationContext存储在Map中,获取Bean时从对应的Context获取从而实现配置的隔离)

ServiceInstanceListSupplier

Spring Cloud 提供了许多的ServiceInstanceListSupplier

DiscoveryClientServiceInstanceListSupplier:服务发现

CachingServiceInstanceListSupplier:缓存(结合Discovery使用时要注意二次缓存问题,比如在跟nacos使用时,因为nacos有缓存,所以并不需要它)

RetryAwareServiceInstanceListSupplier:重试相关,重试时不会选取失败的服务

HealthCheckServiceInstanceListSupplier:健康检查相关,只返回可用的服务

HintBasedServiceInstanceListSupplier:hint翻译是提示,其实就是根据标签进行负载均衡,可以实现简单的灰度路由

ZonePreferenceServiceInstanceListSupplier:根据区域进行负载均衡

……

可以通过ServiceInstanceListSupplierBuilder组合多种的ServiceInstanceListSupplier

loadbalancer自定义配置

根据文档,可以实现自己的ServiceInstanceListSupplierReactorLoadBalancer,配合@LoadBalancerClients@LoadBalancerClient实现自定义的配置,要注意的是实现的自定义类不要配合@Configuration使用,不然会变成全局配置。

custom_loadbalance

configuration_loadbalance

全局灰度路由

  • **使用HintBasedServiceInstanceListSupplier简单实现 **

    env:Spring Cloud 2020.0.x + Nacos,RPC使用feign

    服务有三个,调用链路是gateway -> provider-service -> provider1-service

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    
    /*******************************gateway、provider-service配置******************************/
      
    /**
     * 灰度配置类 
     **/
    public class GrayConfiguration {
      
        @Bean
        public ServiceInstanceListSupplier discoveryClientServiceInstanceListSupplier(
                ConfigurableApplicationContext context) {
            return ServiceInstanceListSupplier.builder()
                    .withDiscoveryClient()
                    .withHints()
                    .build(context);
        }
     }
      
    /*------------------------------------------------------------------------------*/
      
    /**
     * 在配置类上面配置loadBalancer
     **/
     @LoadBalancerClients(defaultConfiguration = GrayConfiguration.class)
      
     @LoadBalancerClient(name = "provider-service", configuration = GrayConfiguration.class)
      
    /*********************************网关配置文件********************************/
    spring:
      application:
        name: gateway
      cloud:
        loadbalancer:
          hint: //hint标志
            provider-service: gray
          hint-header-name: H-GRAY //http hint请求头
            
    /******************************provider-service配置***************************/
    /*---------------------------------配置文件-----------------------------------------*/
     spring:
      application:
        name: provider-service
      cloud:
        loadbalancer:
          hint: //hint标志
            provider1-service: gray
          hint-header-name: H-GRAY //http hint请求头
        nacos:
          discovery:
            metadata:
              hint: gray //服务元数据hint标记
      
    /*-----------------------------------servlet过滤器-------------------------------------------*/
    /*
    ** 将hint请求头放入threadlocal中,方便rpc服务获取
    */
    public static TransmittableThreadLocal<String> GRAY_VERSION = TransmittableThreadLocal.withInitial(() -> "");
      
    @Bean
    public OncePerRequestFilter grayFilter() {
        return new OncePerRequestFilter() {
            @Override
            protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
                String header = request.getHeader("H-GRAY");
                if (StringUtils.hasText(header)) {
                    GRAY_VERSION.set(header);
                }
                filterChain.doFilter(request, response);
            }
        };
    }
      
    /*-------------------------------------feign interceptor-----------------------------------------*/
    /*
    ** 将hint请求头放入RequestTemplate
    */
    @Bean
    public RequestInterceptor grayRequestInterceptor() {
        return new RequestInterceptor() {
            @Override
            public void apply(RequestTemplate template) {
                if (StringUtils.hasText(GRAY_VERSION.get())) {
                    template.header("H-GRAY", GRAY_VERSION.get());
                }
            }
        };
    }
                    
    /******************************provider1-service配置文件***************************/       
     spring:
      application:
        name: provider1-service
      cloud:
        nacos:
          discovery:
            metadata:
              hint: gray //服务元数据hint标记
    
  • 自定义ServiceInstanceListSupplierReactorLoadBalancer实现

    略🤔🤔🤔

References