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,这个集合里的值是这么来的:
- 来自于RibbonClientConfigurationRegistrar中registerBeanDefinitions()方法的执行
- RibbonClientConfigurationRegistrar会执行是因为@RibbonClient上有个@Import(RibbonClientConfigurationRegistrar.class)
- @RibbonClient是@RibbonClients注解里的属性
- @RibbonClients又在RibbonAutoConfiguration和RibbonEurekaAutoConfiguration上有了使用,并且在EurekaRibbonClientConfiguration上标注时,做了配置@RibbonClients(defaultConfiguration =
- 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下有很多实现,如:
- RandomRule随机策略
- RoundRobinRule线性轮询策略
- RetryRule重试机制策略(基于其它策略的具备重试机制选择策略[默认RoundRobinRule]和超时时间[默认500毫秒]的配置)
- WeightedResponseTimeRule响应时间权重策略(继承自RoundRobinRule并扩展了按服务响应时间计算权重)
- BestAvailableRule最佳可用策略(过滤掉故障实例,选出并发请求数最小的实例)
- AvailabilityFilteringRule可用性过滤器策略(选取没有故障且没有超过指定的可配置并发阀值的实例)
默认的实例状态检查IPing配置是DummyPing,Dummy就是“假的”的意思,看代码里也是,检查时什么操作都没做,直接返回true,其它可选配置有:
- NIWSDiscoveryPing(不做ping检查,只是判断下服务状态是否是UP)
- NoOpPing无操作检查(也是啥都不做)
- PingConstant(通过一个可设置的boolean变量来控制检查结果)
- PingUrl连通性检查(可指定要ping的url路径)
总结
大概流程在这了,写的不好,请见谅,有错误的地方欢迎指正,感谢~
Ribbon负载均衡的实现流程简要分析的更多相关文章
- 小D课堂 - 新版本微服务springcloud+Docker教程_4-03 高级篇幅之Ribbon负载均衡源码分析实战
		笔记 3.高级篇幅之Ribbon负载均衡源码分析实战 简介: 讲解ribbon服务间调用负载均衡源码分析 1.完善下单接口 2.分析@LoadBalanced ... 
- 四. Ribbon负载均衡服务调用
		1. 概述 1.1 Ribbon是什么 SpringCloud Ribbon是基于Netflix Ribbon实现的一套客户端,是负载均衡的工具. Ribbon是Netflix发布的开源项目,主要功能 ... 
- 微服务(三) Eureka注册中心和Ribbon负载均衡
		1. Eureka注册中心 1.1 Eureka的结构和作用 在上一篇文章中 微服务(二)服务拆分及远程调用 order-service在发起远程调用的时候,该如何得知user-service实例的i ... 
- (4)什么是Ribbon负载均衡
		4.Ribbon负载均衡 上一节中,我们添加了@LoadBalanced注解,即可实现负载均衡功能,这是什么原理呢? 4.1.负载均衡原理 SpringCloud底层其实是利用了一个名为Ribbon的 ... 
- SpringCloud系列——Ribbon 负载均衡
		前言 Ribbon是一个客户端负载均衡器,它提供了对HTTP和TCP客户端的行为的大量控制.我们在上篇(猛戳:SpringCloud系列——Feign 服务调用)已经实现了多个服务之间的Feign调用 ... 
- Spring Cloud微服务Ribbon负载均衡/Zuul网关使用
		客户端负载均衡,当服务节点出现问题时进行调节或是在正常情况下进行 服务调度.所谓的负载均衡,就是当服务提供的数量和调用方对服务进行 取舍的调节问题,在spring cloud中是通过Ribbon来解决 ... 
- SpringCloud系列五:Ribbon 负载均衡(Ribbon 基本使用、Ribbon 负载均衡、自定义 Ribbon 配置、禁用 Eureka 实现 Ribbon 调用)
		1.概念:Ribbon 负载均衡 2.具体内容 现在所有的服务已经通过了 Eureka 进行了注册,那么使用 Eureka 注册的目的是希望所有的服务都统一归属到 Eureka 之中进 行处理,但是现 ... 
- SpringCloud无废话入门02:Ribbon负载均衡
		1.白话负载均衡 在上一篇的介绍中,我们创建了两个一模一样的服务提供者:Provider1和Provider2,然后它们提供的服务也一模一样,都叫Hello-Service.为什么一样的服务我们要部署 ... 
- spring cloud: 关闭ribbon负载均衡
		spring cloud: 关闭ribbon负载均衡 1.eureka服务 2.2个user服务:7900/7901 3,movie服务 movie服务去请求 user的用户信息,而此时只想请求790 ... 
- spring-cloud: eureka之:ribbon负载均衡自定义配置(二)
		spring-cloud: eureka之:ribbon负载均衡自定义配置(二) 有默认配置的话基本上就是轮询接口,现在我们改用自定义配置,同时支持:轮询,随机接口读取 准备工作: 1.eureka服 ... 
随机推荐
- gird 布局控制元素都显示在一行
			gird 布局控制元素都显示在一行 <ul class="list"> <li v-for="(li, index) in list" :ke ... 
- Virtualbox网络设置
			记录一下https://ladybug.top/%E8%BD%AF%E4%BB%B6%E5%AE%89%E8%A3%85&%E9%85%8D%E7%BD%AE/complete-the-net ... 
- python3.10.0字符串基础
			字符串支持 索引 (下标访问),第一个字符的索引是 0.单字符没有专用的类型,就是长度为一的字符串: >>> word = 'Python' >>> word[0] ... 
- WPF materialDesign 锁屏后界面卡死问题解决
			materialDesign的一个缓存机制问题,在xaml文件中Window属性添加一行: materialDesign:ShadowAssist.CacheMode="{x:Null}&q ... 
- laravel request lifecycle
			1, index.php2, 生成service container3, service provider register/booted4, dispatch routing5, middlew ... 
- 线性斜压模式LBM学习&安装实录
			本文基本参照了LBM的用户手册进行. 环境:Ubuntu 18.04LTS (Windows Subsystem Linux) 编译器:gfortran 7.5.0 安装包: lapack-3.9.0 ... 
- 解题报告:Codeforces 279C Ladder
			Codeforces 279C Ladder 本题与tbw这篇博客上的题有相似思路.tbw的本来我还不会,抄了题解才过,这道题好歹自己磕半天磕出来了.到tbw做那道题我突然想明白了,再一想诶跟这里不是 ... 
- MySQL 5.7升级8.0过程(详解)
			记一次MySQL 5.7升级8.0的详细过程,聊聊我的思路,希望可以帮助大家. 以一个例子为切入点 一.升级背景 为什么要升级到MySQL8.0?大概多久进行一次? 大家可以参考下图记录的各个版本的发 ... 
- Go实现KMP和Sunday算法
			KMP 1 func KMP(str, substr string) int { 2 if substr == "" { 3 return 0 4 } 5 strLen := le ... 
- Retrofit简要分析
			Retrofit是对网络请求实现了一套请求架构封装,屏蔽底层网络实现,使网络请求像调用本地接口一样 基本使用例子 public interface GitHubService {//定义request ... 
