Ribbon负载均衡的实现流程简要分析

SpringCloud中使用Netflix方案做分布式时,只需要在RestTemplate的bean定义上加一个注解@LoadBalanced,无需做其它任何操作就可以开启负载均衡,怎么做到的呢?
不从@LoadBalanced开始倒推,我觉得简单描述正向实现流程,更容易理解

从RestTemplate入手

Ribbon的负载均衡实现,其实就是利用了RestTemplate上的可自定义拦截器功能,给RestTemplate的bean定义上添加@LoadBalanced就是为了给RestTemplate对象添加上指定的拦截器
RestTemplate上具有的拦截器功能来自于RestTemplate的父类InterceptingHttpAccessor

public class RestTemplate extends InterceptingHttpAccessor implements RestOperations
public abstract class InterceptingHttpAccessor extends HttpAccessor {

  private final List<ClientHttpRequestInterceptor> interceptors = new ArrayList<>();

  public void setInterceptors(List<ClientHttpRequestInterceptor> interceptors) {
    // Take getInterceptors() List as-is when passed in here
    if (this.interceptors != interceptors) {
          this.interceptors.clear();
          this.interceptors.addAll(interceptors);
          AnnotationAwareOrderComparator.sort(this.interceptors);
    }
    //省略其它代码
  }
}

接下来再看下是怎么加上的拦截器,自动加上的,就先看自动化配置LoadBalancerAutoConfiguration,相关配置如下

public class LoadBalancerAutoConfiguration {

      @Bean
      public SmartInitializingSingleton loadBalancedRestTemplateInitializerDeprecated(
                  final ObjectProvider<List<RestTemplateCustomizer>> restTemplateCustomizers) {
            return () -> restTemplateCustomizers.ifAvailable(customizers -> {
                  for (RestTemplate restTemplate : LoadBalancerAutoConfiguration.this.restTemplates) {
                        for (RestTemplateCustomizer customizer : customizers) {
                              //执行RestTemplateCustomizer.customize()方法,对restTemplate进行增强
                              customizer.customize(restTemplate);
                        }
                  }
            });
      }

      @Bean
      @ConditionalOnMissingBean
      public LoadBalancerRequestFactory loadBalancerRequestFactory(
                  LoadBalancerClient loadBalancerClient) {
            return new LoadBalancerRequestFactory(loadBalancerClient, this.transformers);
      }

      @Configuration
      @ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate")
      static class LoadBalancerInterceptorConfig {

            //定义LoadBalancerInterceptor
            @Bean
            public LoadBalancerInterceptor ribbonInterceptor(
                        LoadBalancerClient loadBalancerClient,
                        LoadBalancerRequestFactory requestFactory) {
                  return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);
            }

            @Bean
            @ConditionalOnMissingBean
            public RestTemplateCustomizer restTemplateCustomizer(
                        final LoadBalancerInterceptor loadBalancerInterceptor) {
                  return restTemplate -> {
                        List<ClientHttpRequestInterceptor> list = new ArrayList<>(
                                    restTemplate.getInterceptors());
                        list.add(loadBalancerInterceptor);
                        //将定义LoadBalancerInterceptor拦截器添加到restTemplate的拦截器列表中
                        restTemplate.setInterceptors(list);
                  };
            }

      }
}

LoadBalancerInterceptor里怎么做的呢?看下

public class LoadBalancerInterceptor implements ClientHttpRequestInterceptor {

	private LoadBalancerClient loadBalancer;

	private LoadBalancerRequestFactory requestFactory;

	public LoadBalancerInterceptor(LoadBalancerClient loadBalancer,
			LoadBalancerRequestFactory requestFactory) {
		this.loadBalancer = loadBalancer;
		this.requestFactory = requestFactory;
	}

	public LoadBalancerInterceptor(LoadBalancerClient loadBalancer) {
		// for backwards compatibility
		this(loadBalancer, new LoadBalancerRequestFactory(loadBalancer));
	}

	@Override
	public ClientHttpResponse intercept(final HttpRequest request, final byte[] body,
			final ClientHttpRequestExecution execution) throws IOException {
		final URI originalUri = request.getURI();
		String serviceName = originalUri.getHost();
		Assert.state(serviceName != null,
				"Request URI does not contain a valid hostname: " + originalUri);
		return this.loadBalancer.execute(serviceName,
				this.requestFactory.createRequest(request, body, execution));
	}

}

发现了,从服务调用URI中获取到serviceName,然后再传给LoadBalancerClient

如果去看注解@LoadBalanced的定义,也能发现里面提到了LoadBalancerClient

/**
 * Annotation to mark a RestTemplate bean to be configured to use a LoadBalancerClient.
 * @author Spencer Gibb
 */
@Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Qualifier
public @interface LoadBalanced {

}

看下LoadBalancerClient,就是个接口

public interface LoadBalancerClient extends ServiceInstanceChooser {

      // 根据传入的serviceId,从负载均衡器中挑选服务实例执行请求内容
      <T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException;

      // 根据传入的serviceId,用指定的服务实例执行请求内容
      <T> T execute(String serviceId, ServiceInstance serviceInstance,LoadBalancerRequest<T> request) throws IOException;

      // 将服务实例转换成实际的URI信息
      URI reconstructURI(ServiceInstance instance, URI original);

}

再看下父类ServiceInstanceChooser

public interface ServiceInstanceChooser {

      // 从负载均衡器中挑选服务实例
      ServiceInstance choose(String serviceId);

}

而LoadBalancerClient的实现呢,只有一个,就是RibbonLoadBalancerClient,那么主要逻辑肯定就在这里面了

public class RibbonLoadBalancerClient implements LoadBalancerClient {

	private SpringClientFactory clientFactory;

	public RibbonLoadBalancerClient(SpringClientFactory clientFactory) {
		this.clientFactory = clientFactory;
	}

        @Override
        public URI reconstructURI(ServiceInstance instance, URI original) {
              Assert.notNull(instance, "instance can not be null");
              String serviceId = instance.getServiceId();
              //从springClientFactory中根据serviceId获取到RibbonLoadBalancerContext
              RibbonLoadBalancerContext context = this.clientFactory.getLoadBalancerContext(serviceId);

              URI uri;
              Server server;
              if (instance instanceof RibbonServer) {
                    RibbonServer ribbonServer = (RibbonServer) instance;
                    server = ribbonServer.getServer();
                    uri = updateToSecureConnectionIfNeeded(original, ribbonServer);
              }
              else {
                    server = new Server(instance.getScheme(), instance.getHost(),
                                instance.getPort());
                    IClientConfig clientConfig = clientFactory.getClientConfig(serviceId);
                    ServerIntrospector serverIntrospector = serverIntrospector(serviceId);
                    uri = updateToSecureConnectionIfNeeded(original, clientConfig,
                                serverIntrospector, server);
              }
              //RibbonLoadBalancerContext.reconstructURIWithServer(),将服务名的调用URL转换成真实IP地址的实际操作逻辑
              return context.reconstructURIWithServer(server, uri);
        }
        //代码太多,省略其它代码
}

里面还有些其它代码,就不一一贴出来了,其它的基本都是对LoadBalancerClient的实现,看这个重构URI的方法就能看到RibbonLoadBalancerContext和IClientConfig都是从SpringClientFactory clientFactory里取出来的,而且也能看到RibbonLoadBalancerClient的构造函数也只需要这一个属性,有SpringClientFactory就能创建出来RibbonLoadBalancerClient对象,那这个肯定很重要,看下是什么

public class SpringClientFactory extends NamedContextFactory<RibbonClientSpecification> {

      static final String NAMESPACE = "ribbon";

      public SpringClientFactory() {
            super(RibbonClientConfiguration.class, NAMESPACE, "ribbon.client.name");
      }

      public ILoadBalancer getLoadBalancer(String name) {
            return getInstance(name, ILoadBalancer.class);
      }

      public IClientConfig getClientConfig(String name) {
            return getInstance(name, IClientConfig.class);
      }

      public RibbonLoadBalancerContext getLoadBalancerContext(String serviceId) {
            return getInstance(serviceId, RibbonLoadBalancerContext.class);
      }

      @Override
      public <C> C getInstance(String name, Class<C> type) {
            C instance = super.getInstance(name, type);
            if (instance != null) {
                  return instance;
            }
            IClientConfig config = getInstance(name, IClientConfig.class);
            return instantiateWithConfig(getContext(name), type, config);
      }
      //获取负载均衡器
      protected ILoadBalancer getLoadBalancer(String serviceId) {
	  return this.clientFactory.getLoadBalancer(serviceId);
      }
      //省略其它代码
}

SpringClientFactory是NamedContextFactory的子类,这些获取ILoadBalancer、IClientConfig、RibbonLoadBalancerContext的方法,都是调用父类中的getInstance(name, type)而已

public abstract class NamedContextFactory<C extends NamedContextFactory.Specification> implements DisposableBean, ApplicationContextAware {

      private final String propertySourceName;

      private final String propertyName;

      //存放AnnotationConfigApplicationContext的容器,每一个服务都对应一个AnnotationConfigApplicationContext,没有就创建
      private Map<String, AnnotationConfigApplicationContext> contexts = new ConcurrentHashMap<>();

      private Map<String, C> configurations = new ConcurrentHashMap<>();

      private ApplicationContext parent;

      private Class<?> defaultConfigType;

      public NamedContextFactory(Class<?> defaultConfigType, String propertySourceName,
                  String propertyName) {
            this.defaultConfigType = defaultConfigType;
            this.propertySourceName = propertySourceName;
            this.propertyName = propertyName;
      }

      protected AnnotationConfigApplicationContext getContext(String name) {
            if (!this.contexts.containsKey(name)) {
                  synchronized (this.contexts) {
                        if (!this.contexts.containsKey(name)) {
                              this.contexts.put(name, createContext(name));
                        }
                  }
            }
            return this.contexts.get(name);
      }

      //通过name(也就是serviceId)获取到AnnotationConfigApplicationContext,然后再从AnnotationConfigApplicationContext里取到type类型的bean
      public <T> T getInstance(String name, Class<T> type) {
            AnnotationConfigApplicationContext context = getContext(name);
            if (BeanFactoryUtils.beanNamesForTypeIncludingAncestors(context,type).length > 0) {
                  return context.getBean(type);
            }
            return null;
      }
}

到这也就基本到头了,NamedContextFactory是专门定义出来的,每一个需要调用的微服务都创建一个AnnotationConfigApplicationContext,并存放各自的bean,也就是说IOC容器分离开了,没有混在一起,这样可以做到针对不同的微服务情况做不同的配置
再接下来,就是找到这些配置,或者自定义这些bean的配置就可以了,也就是负载均衡器、负载均衡策略等的配置了

自动配置

在RibbonAutoConfiguration中有如下相关配置

public class RibbonAutoConfiguration {

      @Bean
      public SpringClientFactory springClientFactory() {
            SpringClientFactory factory = new SpringClientFactory();
            factory.setConfigurations(this.configurations);
            return factory;
      }

      @Bean
      @ConditionalOnMissingBean(LoadBalancerClient.class)
      public LoadBalancerClient loadBalancerClient() {
            return new RibbonLoadBalancerClient(springClientFactory());
      }
      //省略其它配置
}

创建SpringClientFactory时需要设置一个属性List configurations,这个集合里的值是这么来的:

  1. 来自于RibbonClientConfigurationRegistrar中registerBeanDefinitions()方法的执行
  2. RibbonClientConfigurationRegistrar会执行是因为@RibbonClient上有个@Import(RibbonClientConfigurationRegistrar.class)
  3. @RibbonClient是@RibbonClients注解里的属性
  4. @RibbonClients又在RibbonAutoConfiguration和RibbonEurekaAutoConfiguration上有了使用,并且在EurekaRibbonClientConfiguration上标注时,做了配置@RibbonClients(defaultConfiguration =
  5. EurekaRibbonClientConfiguration中呢又包含EurekaClientConfig(eureka.client相关属性配置类),EurekaInstanceConfig(eureka.instance相关属性配置类)

就这样一长串的关联,就能把相关bean的配置给引入到了SpringClientFactory中,并可以按服务给分别设定了IOC容器并做了bean注入(PS:我也只是看出来了相关性,没看那么具体)

通过这两个Bean的配置,IOC容器中就有了RibbonLoadBalancerClient对象

在RibbonClientConfiguration中,有如下相关配置

public class RibbonClientConfiguration {

      @Bean
      @ConditionalOnMissingBean
      public IClientConfig ribbonClientConfig() {
            DefaultClientConfigImpl config = new DefaultClientConfigImpl();
            config.loadProperties(this.name);
            config.set(CommonClientConfigKey.ConnectTimeout, DEFAULT_CONNECT_TIMEOUT);
            config.set(CommonClientConfigKey.ReadTimeout, DEFAULT_READ_TIMEOUT);
            config.set(CommonClientConfigKey.GZipPayload, DEFAULT_GZIP_PAYLOAD);
            return config;
      }

      @Bean
      @ConditionalOnMissingBean
      public IRule ribbonRule(IClientConfig config) {
            if (this.propertiesFactory.isSet(IRule.class, name)) {
                  return this.propertiesFactory.get(IRule.class, config, name);
            }
            ZoneAvoidanceRule rule = new ZoneAvoidanceRule();
            rule.initWithNiwsConfig(config);
            return rule;
      }

      @Bean
      @ConditionalOnMissingBean
      public IPing ribbonPing(IClientConfig config) {
            if (this.propertiesFactory.isSet(IPing.class, name)) {
                  return this.propertiesFactory.get(IPing.class, config, name);
            }
            return new DummyPing();
      }

      @Bean
      @ConditionalOnMissingBean
      public ILoadBalancer ribbonLoadBalancer(IClientConfig config,
                  ServerList<Server> serverList, ServerListFilter<Server> serverListFilter,
                  IRule rule, IPing ping, ServerListUpdater serverListUpdater) {
            if (this.propertiesFactory.isSet(ILoadBalancer.class, name)) {
                  return this.propertiesFactory.get(ILoadBalancer.class, config, name);
            }
            return new ZoneAwareLoadBalancer<>(config, rule, ping, serverList,
                        serverListFilter, serverListUpdater);
      }

      @Bean
      @ConditionalOnMissingBean
      public RibbonLoadBalancerContext ribbonLoadBalancerContext(ILoadBalancer loadBalancer,
                  IClientConfig config, RetryHandler retryHandler) {
            return new RibbonLoadBalancerContext(loadBalancer, config, retryHandler);
      }
}

可以看到,默认的负载均衡器ILoadBalancer配置是ZoneAwareLoadBalancer,默认的负载策略IRule配置是ZoneAvoidanceRule,默认的实例状态检查IPing配置是DummyPing
这几个也是我们常用的自定义的配置项,可以针对不同需要进行配置,@ConditionalOnMissingBean,如果有手动配置了,那么自动配置便不会生效了

自定义配置

负载均衡器ZoneAwareLoadBalancer是实现好的区域感知均衡器,对同Zone区域的服务友好会优先调用,继承自DynamicServerListLoadBalancer动态服务列表均衡器,DynamicServerListLoadBalancer又继承自BaseLoadBalancer基础均衡器,各项都有了基本实现,如果有需要进行负载均衡器调整的话,得需要自己去实现了,可以通过继承DynamicServerListLoadBalancer或BaseLoadBalancer来做

默认的负载策略IRule配置是ZoneAvoidanceRule,也是对区域友好的,要调整的话,这项有其它的选择,在IRule下有很多实现,如:

  1. RandomRule随机策略
  2. RoundRobinRule线性轮询策略
  3. RetryRule重试机制策略(基于其它策略的具备重试机制选择策略[默认RoundRobinRule]和超时时间[默认500毫秒]的配置)
  4. WeightedResponseTimeRule响应时间权重策略(继承自RoundRobinRule并扩展了按服务响应时间计算权重)
  5. BestAvailableRule最佳可用策略(过滤掉故障实例,选出并发请求数最小的实例)
  6. AvailabilityFilteringRule可用性过滤器策略(选取没有故障且没有超过指定的可配置并发阀值的实例)

默认的实例状态检查IPing配置是DummyPing,Dummy就是“假的”的意思,看代码里也是,检查时什么操作都没做,直接返回true,其它可选配置有:

  1. NIWSDiscoveryPing(不做ping检查,只是判断下服务状态是否是UP)
  2. NoOpPing无操作检查(也是啥都不做)
  3. PingConstant(通过一个可设置的boolean变量来控制检查结果)
  4. PingUrl连通性检查(可指定要ping的url路径)

总结

大概流程在这了,写的不好,请见谅,有错误的地方欢迎指正,感谢~

posted @ 2022-04-11 16:54  lixuelong  阅读(179)  评论(0编辑  收藏  举报