IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> 【微服务】SpringCloud中使用Ribbon实现负载均衡的原理 -> 正文阅读

[Java知识库]【微服务】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));
	}

主要逻辑:

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

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

先看下LoadBalancerInterceptor的类图。

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

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

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

?一看到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<RestTemplate> restTemplates = Collections.emptyList();

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

	@Bean
	public SmartInitializingSingleton loadBalancedRestTemplateInitializerDeprecated(
			final ObjectProvider<List<RestTemplateCustomizer>> 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<ClientHttpRequestInterceptor> 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<ClientHttpRequestInterceptor> 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进来,再点击其包名看看在哪个项目里面

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

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

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

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

3、RibbonLoadBalancerClient负载均衡器客户端

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

	public <T> T execute(String serviceId, LoadBalancerRequest<T> 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);
	}

?主要逻辑:

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

3.2、getLoadBalancer(serviceId)获取ILoadBalancer

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

3.3、getInstance(String name, Class<C> type)

	@Override
	public <C> C getInstance(String name, Class<C> 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就看看类的父子关系图

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

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

3.4、NamedContextFactory组件

?因为this是SpringClientFactory的引用,故会调用SpringClientFactory的getContext()方法

?SpringClientFactory通过super调用父类的getContext()回到NamedContextFactory继续处理,里面会判断上下文是否已经存在指定key对应的context,如果没有则创建一个再存放到context(Map<String, AnnotationConfigApplicationContext>类型),然后从缓存map获取。?

?createContext(name)返回创建的context,然后存放到context字段里面去。

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

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

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

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

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

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

3.6、为什么是ZoneAwareLoadBalancer

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

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

4、获取服务

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

4.1、chooseServer()逻辑

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

4.2、BaseLoadBalancer组件

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

4.3、?PredicateBasedRule的choose()逻辑

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

?BaseLoadBalancer组件获取所有服务

    public Optional<Server> chooseRoundRobinAfterFiltering(List<Server> servers, Object loadBalancerKey) {
        // 从服务器列表中获取根据此谓词筛选服务器。
        List<Server> 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规则启发而写的算法,其在是妙。
  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2022-11-05 00:12:25  更:2022-11-05 00:13:02 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2025年3日历 -2025/3/10 18:17:09-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码