【微服务】SpringCloud中使用Ribbon实现负载均衡的原理

目录

一、前言

二、Ribbon实现负载均衡的原理

1、流程图

2、LoadBalancerInterceptor组件intercept()拦截逻辑

2.1、intercept()逻辑

2.2、拦截器机制怎么触发的,怎么来的?

3、RibbonLoadBalancerClient负载均衡器客户端

3.1、execute(String serviceId, LoadBalancerRequest request, Object hint)逻辑

3.2、getLoadBalancer(serviceId)获取ILoadBalancer

3.3、getInstance(String name, Class type)

3.4、NamedContextFactory组件

3.5、从Spring上下文获取ILoadBalancer类型实例

3.6、为什么是ZoneAwareLoadBalancer

4、获取服务

4.1、chooseServer()逻辑

4.2、BaseLoadBalancer组件

4.3、 PredicateBasedRule的choose()逻辑


一、前言

    2.x.x版本的SpringCloud项目使用的是Ribbon实现负载均衡,即使是OpenFeign也不例外,故本篇博客采用的是2.2.x版本的SpringCloud项目进行讲解。Ribbon实现负载均衡的案例在我的微服务专栏已经给出来了,感兴趣的读者可以去探究探究,然后本篇博客就是围绕它来进行展开。

二、Ribbon实现负载均衡的原理

1、流程图

 里面还涉及到SpringBoot的自动装配,下面有分析到。

2、LoadBalancerInterceptor组件intercept()拦截逻辑

2.1、intercept()逻辑

	@Override
	public ClientHttpResponse intercept(final HttpRequest request, final byte[] body,
			final ClientHttpRequestExecution execution) throws IOException {
		// 获取请求URI
		final URI originalUri = request.getURI();
		// 从URI获取服务名
		String serviceName = originalUri.getHost();
		Assert.state(serviceName != null,
				"Request URI does not contain a valid hostname: " + originalUri);
		// 调用负载均衡器客户端的execute()方法处理请求
		return this.loadBalancer.execute(serviceName,
				this.requestFactory.createRequest(request, body, execution));
	}

【微服务】SpringCloud中使用Ribbon实现负载均衡的原理_第1张图片

主要逻辑:

  1. 根据request入参,获取请求URI
  2. URI获取到要调用的服务名
  3. 调用LoadBalancerClient负载均衡客户端的execute()方法,完成请求处理。

2.2、拦截器机制怎么触发的,怎么来的?

先看下LoadBalancerInterceptor的类图。

【微服务】SpringCloud中使用Ribbon实现负载均衡的原理_第2张图片

 其次你要调用一个类的方法,首先对其进行实例化。那么这些都是Spring的项目,会不会在Spring容器初始化时就默认初始化了呢?回顾一下我们之前讲得的流程,有没有哪一点让你过目不忘。

1)LoadBalancerAutoConfiguration阻塞客户端负载均衡的自动配置。

如果你不知道它在哪个项目,可以点击它的包名找到对应的jar包项目

【微服务】SpringCloud中使用Ribbon实现负载均衡的原理_第3张图片

【微服务】SpringCloud中使用Ribbon实现负载均衡的原理_第4张图片

 一看到META-INF就情不自禁地点击进去,发现了SpringBoot的一个重要注解(EnableAutoConfiguration),具体的前面已经分析了。点击LoadBalancerAutoConfiguration进去看看。

1.1)LoadBalancerAutoConfiguration

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RestTemplate.class)
@ConditionalOnBean(LoadBalancerClient.class)
@EnableConfigurationProperties(LoadBalancerRetryProperties.class)
public class LoadBalancerAutoConfiguration {

	@LoadBalanced
	@Autowired(required = false)
	private List restTemplates = Collections.emptyList();

	@Autowired(required = false)
	private List transformers = Collections.emptyList();

	@Bean
	public SmartInitializingSingleton loadBalancedRestTemplateInitializerDeprecated(
			final ObjectProvider> restTemplateCustomizers) {
		return () -> restTemplateCustomizers.ifAvailable(customizers -> {
			for (RestTemplate restTemplate : LoadBalancerAutoConfiguration.this.restTemplates) {
				for (RestTemplateCustomizer customizer : customizers) {
					customizer.customize(restTemplate);
				}
			}
		});
	}

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

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

		/**
		 * 拦截器装配
		 *
		 * @param loadBalancerClient loadBalancerClient
		 * @param requestFactory requestFactory
		 * @return LoadBalancerInterceptor
		 */
		@Bean
		public LoadBalancerInterceptor loadBalancerInterceptor(
				LoadBalancerClient loadBalancerClient,
				LoadBalancerRequestFactory requestFactory) {
			return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);
		}

		@Bean
		@ConditionalOnMissingBean
		public RestTemplateCustomizer restTemplateCustomizer(
				final LoadBalancerInterceptor loadBalancerInterceptor) {
			return restTemplate -> {
				List list = new ArrayList<>(
						restTemplate.getInterceptors());
				list.add(loadBalancerInterceptor);
				// 为restTemplate设置拦截器
				restTemplate.setInterceptors(list);
			};
		}

	}

	/**
	 * Auto configuration for retry mechanism.
	 */
	@Configuration(proxyBeanMethods = false)
	@ConditionalOnClass(RetryTemplate.class)
	public static class RetryAutoConfiguration {

		@Bean
		@ConditionalOnMissingBean
		public LoadBalancedRetryFactory loadBalancedRetryFactory() {
			return new LoadBalancedRetryFactory() {
			};
		}

	}

	/**
	 * Auto configuration for retry intercepting mechanism.
	 */
	@Configuration(proxyBeanMethods = false)
	@ConditionalOnClass(RetryTemplate.class)
	public static class RetryInterceptorAutoConfiguration {

		@Bean
		@ConditionalOnMissingBean
		public RetryLoadBalancerInterceptor loadBalancerRetryInterceptor(
				LoadBalancerClient loadBalancerClient,
				LoadBalancerRetryProperties properties,
				LoadBalancerRequestFactory requestFactory,
				LoadBalancedRetryFactory loadBalancedRetryFactory) {
			return new RetryLoadBalancerInterceptor(loadBalancerClient, properties,
					requestFactory, loadBalancedRetryFactory);
		}

		@Bean
		@ConditionalOnMissingBean
		public RestTemplateCustomizer restTemplateCustomizer(
				final RetryLoadBalancerInterceptor loadBalancerInterceptor) {
			return restTemplate -> {
				List list = new ArrayList<>(
						restTemplate.getInterceptors());
				list.add(loadBalancerInterceptor);
				restTemplate.setInterceptors(list);
			};
		}

	}

}

    可见里面对拦截器LoadBalancerInterceptor进行了装配,并且给RestTemplete设置了拦截器。SpringBoot在在装配LoadBalancerInterceptor过程中,将已经装配的LoadBalancerClient(RibbonLoadBalancerClient)传递进来,在LoadBalancerInterceptor构造方法中初始化loadBalancer字段。当然自动装配还做了如重试等Bean的装配。这也就是为什么入口就是LoadBalancerInterceptor的原因。

2)LoadBalancerClient在哪里装配的,为什么是RibbonLoadBalancerClient

不知道在哪里情况下,可以先Debugger进来,再点击其包名看看在哪个项目里面

【微服务】SpringCloud中使用Ribbon实现负载均衡的原理_第5张图片

可见它在spring-cloud-netflix-ribbon项目里面,然后找找有没有META-INF

【微服务】SpringCloud中使用Ribbon实现负载均衡的原理_第6张图片

 可见是有的,我们在里面找找看

【微服务】SpringCloud中使用Ribbon实现负载均衡的原理_第7张图片

注意:RibbonAutoConfiguration是在装配LoadBalancerAutoConfiguration之前执行装配的

【微服务】SpringCloud中使用Ribbon实现负载均衡的原理_第8张图片

 可见这里装配的是RibbonLoadBalancerClient,所以为什么我们会进入到RibbonLoadBalancerClient的execute()逻辑。下面对其展开探究讲解:

3、RibbonLoadBalancerClient负载均衡器客户端

3.1、execute(String serviceId, LoadBalancerRequest request, Object hint)逻辑

	public  T execute(String serviceId, LoadBalancerRequest request, Object hint)
			throws IOException {
		// 根据服务名获取负载均衡器,Dy
		ILoadBalancer loadBalancer = getLoadBalancer(serviceId);
		// 根据负载均衡器筛选一个服务
		Server server = getServer(loadBalancer, hint);
		if (server == null) {
			throw new IllegalStateException("No instances available for " + serviceId);
		}
		RibbonServer ribbonServer = new RibbonServer(serviceId, server,
				isSecure(server, serviceId),
				serverIntrospector(serviceId).getMetadata(server));

		return execute(serviceId, ribbonServer, request);
	}

【微服务】SpringCloud中使用Ribbon实现负载均衡的原理_第9张图片

 主要逻辑:

  1. 根据服务名,获取Spring上下文,然后从上下文获取负载均衡器实例。
  2. 根据负载均衡器以及里面设置的规则,从服务列表里面选择一个可用服务。
  3. 封装数据,执行重载execute()方法完成请求。

3.2、getLoadBalancer(serviceId)获取ILoadBalancer

【微服务】SpringCloud中使用Ribbon实现负载均衡的原理_第10张图片

将请求委托SpringClientFactory的getLoadBalancer()方法处理,其内部调用 getInstance()处理。

3.3、getInstance(String name, Class type)

	@Override
	public  C getInstance(String name, Class type) {
		// 调用父类获取实例
		C instance = super.getInstance(name, type);
		if (instance != null) {
			return instance;
		}
		// 调用父类没有获取到实例,更改type重新调用
		IClientConfig config = getInstance(name, IClientConfig.class);
		return instantiateWithConfig(getContext(name), type, config);
	}

一看到super就看看类的父子关系图

【微服务】SpringCloud中使用Ribbon实现负载均衡的原理_第11张图片

 SpringClientFactory继承自NamedContextFactory,所以super.getInstance(name, type)方法调用的是NamedContextFactory的getInstance()方法:

注意:SpringClientFactoryspring-cloud-netflix项目里面的、NamedContextFactoryspring-cloud-commons里面的

3.4、NamedContextFactory组件

【微服务】SpringCloud中使用Ribbon实现负载均衡的原理_第12张图片

 因为this是SpringClientFactory的引用,故会调用SpringClientFactory的getContext()方法【微服务】SpringCloud中使用Ribbon实现负载均衡的原理_第13张图片

【微服务】SpringCloud中使用Ribbon实现负载均衡的原理_第14张图片 SpringClientFactory通过super调用父类的getContext()回到NamedContextFactory继续处理,里面会判断上下文是否已经存在指定key对应的context,如果没有则创建一个再存放到context(Map类型),然后从缓存map获取。 

【微服务】SpringCloud中使用Ribbon实现负载均衡的原理_第15张图片 createContext(name)返回创建的context,然后存放到context字段里面去。

【微服务】SpringCloud中使用Ribbon实现负载均衡的原理_第16张图片

获取Spring上下文后,接着执行BeanFactoryUtils.beansOfTypeIncludingAncestors(context, type);逻,从上下文获取ILoadBalancer类型实例。

3.5、从Spring上下文获取ILoadBalancer类型实例

【微服务】SpringCloud中使用Ribbon实现负载均衡的原理_第17张图片

 可见它上下文是ListableBeanFactory类型的,不难发现它的实现类一般用的是DefaultListableBeanFactory(Bean定义注册机)

【微服务】SpringCloud中使用Ribbon实现负载均衡的原理_第18张图片

 可以看到ribbonLoadBalancerZoneAwareLoadBalancer,那么为什么是它,下面的3.6步骤具体分析。

【微服务】SpringCloud中使用Ribbon实现负载均衡的原理_第19张图片

DefaultListableBeanFactory获取Bean的信息,回到NamedContextFactory的getInstances(),调用AbstractApplicationContext的getBean(),调用DefaultListableBeanFactory的getBean()、resolveBean()、resolveNamedBean()、getBean(beanName, null, args),然后

调用AbstractBeanFactorygetBean()方法,其内部又会调用doGetBean()方法从DefaultSingletonBeanRegistry默认单例Bean注册机缓存获取,没有则新建。这些逻辑在之前就讲解过了,具体的不再过多赘述。

3.6、为什么是ZoneAwareLoadBalancer

【微服务】SpringCloud中使用Ribbon实现负载均衡的原理_第20张图片

SpringBoot在自动装配的过程中,根据RibbonClientConfiguration中的配置默认装配ZoneAwareLoadBalancer的。ZoneAwareLoadBalancer是ribbon项目里面的,RibbonClientConfiguration是spring-cloud-netflix-ribbon里面的。

 看一下它的父子关系类图:

【微服务】SpringCloud中使用Ribbon实现负载均衡的原理_第21张图片

4、获取服务

【微服务】SpringCloud中使用Ribbon实现负载均衡的原理_第22张图片

获取负载均衡器后,根据负载均衡器去获取可用的服务。

4.1、chooseServer()逻辑

【微服务】SpringCloud中使用Ribbon实现负载均衡的原理_第23张图片

【微服务】SpringCloud中使用Ribbon实现负载均衡的原理_第24张图片

 根据上面获取到的负载均衡器是ZoneAwareLoadBalancer,故会进入到ZoneAwareLoadBalancer的chooseServer()处理,一般会委托它的父类BaseLoadBalancer去选择服务。

4.2、BaseLoadBalancer组件

【微服务】SpringCloud中使用Ribbon实现负载均衡的原理_第25张图片

 它选择服务的逻辑委托给了PredicateBasedRule处理

4.3、 PredicateBasedRule的choose()逻辑

【微服务】SpringCloud中使用Ribbon实现负载均衡的原理_第26张图片

在选择一个可调用服务前,先获取所有服务getAllServers()

【微服务】SpringCloud中使用Ribbon实现负载均衡的原理_第27张图片 BaseLoadBalancer组件获取所有服务

【微服务】SpringCloud中使用Ribbon实现负载均衡的原理_第28张图片

    public Optional chooseRoundRobinAfterFiltering(List servers, Object loadBalancerKey) {
        // 从服务器列表中获取根据此谓词筛选服务器。
        List eligible = getEligibleServers(servers, loadBalancerKey);
        if (eligible.size() == 0) {
            return Optional.absent();
        }
        // 计算索引,从eligible获取一个服务
        return Optional.of(eligible.get(incrementAndGetModulo(eligible.size())));
    }

选择一个服务

    private int incrementAndGetModulo(int modulo) {
        // 死循环直到获取到一个索引下标
        for (;;) {
            // 获取当前AtomicInteger类型变量的原子值
            int current = nextIndex.get();
            // 当前原子值 + 1 然后对 服务实例个数取余
            int next = (current + 1) % modulo;
            // CAS修改AtomicInteger类型变量,CAS成功并且current小于服务个数返回current,否则重试
            if (nextIndex.compareAndSet(current, next) && current < modulo)
                return current;
        }
    }
以循环方式选择服务器,这里受CAS思想以及RoundRobinRule规则启发而写的算法,其在是妙。

你可能感兴趣的:(Spring家族,spring,spring,boot,java,springcloud)