ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

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

2022-04-11 17:01:26  阅读:145  来源: 互联网

标签:负载 简要 return name class config public Ribbon String


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());
      }
      //省略其它配置
}

通过这两个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下有很多实现,如RandomRule随机策略、RoundRobinRule线性轮询策略、RetryRule重试机制策略(基于其它策略[默认RoundRobinRule]和超时时间[默认500毫秒]的配置)、WeightedResponseTimeRule权重策略(继承自RoundRobinRule并扩展了按服务响应时间计算权重)、BestAvailableRule最佳可用策略(过滤掉故障实例,选出并发请求数最小的实例)、AvailabilityFilteringRule可用性过滤器策略(选取没有故障且没有超过指定的可配置并发阀值的实例)

默认的实例状态检查IPing配置是DummyPing,Dummy就是“假的”的意思,看代码里也是,检查时什么操作都没做,直接返回true,其它可选配置有:NIWSDiscoveryPing、NoOpPing无操作检查(也是啥都不做)、PingConstant(通过一个可设置的boolean变量来控制检查结果)、PingUrl连通性检查(可指定要ping的url路径)

总结

大概流程在这了,感觉观看

标签:负载,简要,return,name,class,config,public,Ribbon,String
来源: https://www.cnblogs.com/lixuelong/p/16130805.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有