Spring cloud负载均衡 @LoadBalanced注解原理

接上一篇文章,案例代码也在上一篇文章的基础上。

在上一篇文章的案例中,我们创建了作为Eureka server的Eureka注册中心服务、作为Eureka client的userservice、orderservice。

orderservice引入RestTemplate,加入了@LoadBalanced注解,代码如下:

package com;

@SpringBootApplication
@EnableEurekaClient
public class OrderServiceApplication {
    public static void main(String[] args) {
        SpringApplication.run(OrderServiceApplication.class);
    }

    @Bean
    @LoadBalanced
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }

}

从而,我们实现了基于Eureka注册中心的微服务治理框架,在orderservice调用userservice的时候通过加了@LoadBalanced注解的RestTemplate实现了负载均衡。

今天的目标是:深入研究@LoadBalanced生效的底层原理。

@LoadBalanced是怎么实现负载均衡的?

我们要回答的第一个问题是,为什么@LoadBalanced能实现负载均衡?

我们从代码的源头一路追查下去…

orderservice通过RestTemplate实现对userservice的调用代码:

@Service
public class OrderService {
    @Autowired
    private RestTemplate restTemplate;
    public String getOrder(){
        //通过userService获取user信息
        String url="http://userservice/user/getUser";
        System.out.println("url"+url);
        User user=restTemplate.getForObject(url,User.class);
        System.out.println(user);
        return user.getName();
    }
}

很容易的,我们需要有一个认知:这里访问的地址http://userservice/user/getUser只可能在Spring cloud服务治理环境下有意义,最终能访问到我们发布到本机上的userservice的如下服务:

1. http://localhost:8080
2. http://localhost:8081
3. http://localhost:8082

必定需要借助Spring Cloud的某一机制将http://userservice转换为上述地址之一。这个转换过程,也就是Spring Cloud的负载均衡机制。

跟踪getForObject:

	@Override
	@Nullable
	public <T> T getForObject(String url, Class<T> responseType, Object... uriVariables) throws RestClientException {
		RequestCallback requestCallback = acceptHeaderRequestCallback(responseType);
		HttpMessageConverterExtractor<T> responseExtractor =
				new HttpMessageConverterExtractor<>(responseType, getMessageConverters(), logger);
		return execute(url, HttpMethod.GET, requestCallback, responseExtractor, uriVariables);
	}

调用到execute方法:

	@Override
	@Nullable
	public <T> T execute(String url, HttpMethod method, @Nullable RequestCallback requestCallback,
			@Nullable ResponseExtractor<T> responseExtractor, Object... uriVariables) throws RestClientException {

		URI expanded = getUriTemplateHandler().expand(url, uriVariables);
		return doExecute(expanded, method, requestCallback, responseExtractor);
	}

doExecute方法:

@Nullable
	protected <T> T doExecute(URI url, @Nullable HttpMethod method, @Nullable RequestCallback requestCallback,
			@Nullable ResponseExtractor<T> responseExtractor) throws RestClientException {

		Assert.notNull(url, "URI is required");
		Assert.notNull(method, "HttpMethod is required");
		ClientHttpResponse response = null;
		try {
			ClientHttpRequest request = createRequest(url, method);
			if (requestCallback != null) {
				requestCallback.doWithRequest(request);
			}
			response = request.execute();
			handleResponse(url, method, response);
			return (responseExtractor != null ? responseExtractor.extractData(response) : null);
		}
		catch (IOException ex) {
			String resource = url.toString();
			String query = url.getRawQuery();
			resource = (query != null ? resource.substring(0, resource.indexOf('?')) : resource);
			throw new ResourceAccessException("I/O error on " + method.name() +
					" request for \"" + resource + "\": " + ex.getMessage(), ex);
		}
		finally {
			if (response != null) {
				response.close();
			}
		}
	}

createRequest方法:

	protected ClientHttpRequest createRequest(URI url, HttpMethod method) throws IOException {
		ClientHttpRequest request = getRequestFactory().createRequest(url, method);
		initialize(request);
		if (logger.isDebugEnabled()) {
			logger.debug("HTTP " + method.name() + " " + url);
		}
		return request;
	}

最关键的部分来了,就是这个 getRequestFactory()方法,在RestTemplate的父类InterceptingHttpAccessor中定义:

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


    public ClientHttpRequestFactory getRequestFactory() {
		List<ClientHttpRequestInterceptor> interceptors = getInterceptors();
		if (!CollectionUtils.isEmpty(interceptors)) {
			ClientHttpRequestFactory factory = this.interceptingRequestFactory;
			if (factory == null) {
				factory = new InterceptingClientHttpRequestFactory(super.getRequestFactory(), interceptors);
				this.interceptingRequestFactory = factory;
			}
			return factory;
		}
		else {
			return super.getRequestFactory();
		}
	}

	public List<ClientHttpRequestInterceptor> getInterceptors() {
		return this.interceptors;
	}

首先通过getInterceptor()方法检查是否有有拦截器,拦截器interceptors是由ClientHttpRequestInterceptor组成的一个list。如果有的话,就会创建InterceptingClientHttpRequestFactory、并且将拦截器interceptors送给InterceptingClientHttpRequestFactory工厂之后,返回工厂InterceptingClientHttpRequestFactory。

然后,方法调用返回到createRequest方法中,调用InterceptingClientHttpRequestFactory的createRequest方法,最终会调用到:

	@Override
	protected ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod, ClientHttpRequestFactory requestFactory) {
		return new InterceptingClientHttpRequest(requestFactory, this.interceptors, uri, httpMethod);
	}

方法最终返回的是InterceptingClientHttpRequest,并且,会将工厂InterceptingClientHttpRequestFactory持有的interceptors传递给InterceptingClientHttpRequest对象:

protected InterceptingClientHttpRequest(ClientHttpRequestFactory requestFactory,
			List<ClientHttpRequestInterceptor> interceptors, URI uri, HttpMethod method) {

		this.requestFactory = requestFactory;
		this.interceptors = interceptors;
		this.method = method;
		this.uri = uri;
	}

之后返回到doExecute方法中,会调用InterceptingClientHttpRequest的父类AbstractClientHttpRequest类的execute方法、之后又转回到InterceptingClientHttpRequest类的executeInternal方法:

	@Override
	protected final ClientHttpResponse executeInternal(HttpHeaders headers, byte[] bufferedOutput) throws IOException {
		InterceptingRequestExecution requestExecution = new InterceptingRequestExecution();
		return requestExecution.execute(this, bufferedOutput);
	}

executeInternal方法创建了InterceptingClientHttpRequest内部类InterceptingRequestExecution对象之后,调用内部对象的execute方法。

查看内部类InterceptingRequestExecution,不难发现,他拿到了宿主类InterceptingClientHttpRequest的拦截器interceptors的迭代器:

private class InterceptingRequestExecution implements ClientHttpRequestExecution {

		private final Iterator<ClientHttpRequestInterceptor> iterator;

		public InterceptingRequestExecution() {
			this.iterator = interceptors.iterator();
		}

		@Override
		public ClientHttpResponse execute(HttpRequest request, byte[] body) throws IOException {
			if (this.iterator.hasNext()) {
				ClientHttpRequestInterceptor nextInterceptor = this.iterator.next();
				return nextInterceptor.intercept(request, body, this);
			}
			else {
				HttpMethod method = request.getMethod();
				Assert.state(method != null, "No standard HTTP method");
				ClientHttpRequest delegate = requestFactory.createRequest(request.getURI(), method);
				request.getHeaders().forEach((key, value) -> delegate.getHeaders().addAll(key, value));
				if (body.length > 0) {
					if (delegate instanceof StreamingHttpOutputMessage) {
						StreamingHttpOutputMessage streamingOutputMessage = (StreamingHttpOutputMessage) delegate;
						streamingOutputMessage.setBody(outputStream -> StreamUtils.copy(body, outputStream));
					}
					else {
						StreamUtils.copy(body, delegate.getBody());
					}
				}
				return delegate.execute();
			}
		}
	}

然后在execute方法中首先遍历该迭代器iterator并调用迭代器对象ClientHttpRequestInterceptor的intercept方法。

拦截器ClientHttpRequestInterceptor就是实现负载均衡的关键所在,Spring正是在拦截器ClientHttpRequestInterceptor的intercept方法中,完成了负载均衡的实现:将请求中的服务名称比如本案例中的userservice、替换成了由Eureka注册中心下发下来的具体的userservice服务器(比如http://127.0.0.1:8081)

我们发现最关键的部分就是RestTemplate对象中的拦截器interceptors。

接下来的问题就是:interceptors是什么时候、怎么创建出来的?

拦截器的初始化

从RestTemplate的父类InterceptingHttpAccessor简单追踪一下,不难发现他的interceptors是通过setInterceptors方法赋值的,然后借助开发工具的帮助:
在这里插入图片描述
发现LoadBalanceAutoConfiguration调用了setInterceptors方法。

这个LoadBalanceAutoConfiguration的命名方式是不是很熟悉啊?我们前面分析过SpringBoot的自动配置,就是各种xxxxAutoConfiguration命名的类负责具体的自动配置任务的。

简单了解了下,LoadBalanceAutoConfiguration就是SpringBoot的自动配置类。我们找到LoadBalanceAutoConfiguration类在spring-cloud-commons包下,按图索骥,我们找到了包下的/META-INF/spring.factories文件:
在这里插入图片描述
因此我们知道,SpringBoot的自动配置机制会通过调用LoadBalanceAutoConfiguration类来完成LoadBalance的相关初始化工作。
所以我们接下来的工作就是要研究LoadBalanceAutoConfiguration。

LoadBalanceAutoConfiguration

第一步,从LoadBalanceAutoConfiguration类的源码知道他要通过restTemplateCustomizer方法加载一个RestTemplateCustomizer对象,方法需要一个参数LoadBalancerInterceptor:

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

先看一眼RestTemplateCustomizer类,只有一个customize方法,该方法有一个参数RestTemplate:

public interface RestTemplateCustomizer {

	void customize(RestTemplate restTemplate);

}

这个customize方法已经在restTemplateCustomizer方法中通过lamda表达式定义出来了,代码逻辑很简单:

就是要将LoadBalancerInterceptor拦截器对象通过调用RestTemplate的setInterceptors方法加入到RestTemplate的interceptors中!

似乎快要摸到开关了!!!

那么我们现在又冒出了以下几个问题:

  1. 这个LoadBalancerInterceptor从哪里来?
  2. RestTemplate从哪里来?
  3. 什么时候调用这个已经注入到Spring Ioc容器中的RestTemplateCustomizer的customize方法?

第1、第2两个问题其实很简单。看代码:

    @Configuration(proxyBeanMethods = false)
	@Conditional(RetryMissingOrDisabledCondition.class)
	static class LoadBalancerInterceptorConfig {

		@Bean
		public LoadBalancerInterceptor loadBalancerInterceptor(LoadBalancerClient loadBalancerClient,
				LoadBalancerRequestFactory requestFactory) {
			return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);
		}
		...
}

LoadBalancerAutoConfiguration类中有一个静态配置类LoadBalancerInterceptorConfig,通过loadBalancerInterceptor方法注入了LoadBalancerInterceptor 对象,创建对象的时候通过参数注入了loadBalancerClient和LoadBalancerRequestFactory。

第2个问题,RestTemplate的注入,其实是我们从应用层通过@Bean注入的,同时加了@LoadBalanced注解。

现在我们来回答第3个问题:

什么时候调用这个已经注入到Spring Ioc容器中的RestTemplateCustomizer的customize方法?

比前两个问题稍稍复杂了一点:

	@LoadBalanced
	@Autowired(required = false)
	private List<RestTemplate> restTemplates = 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);
				}
			}
		});
	}

首先看到标注了@Autowire和@LoadBalanced注解的RestTemplates列表的注入,意思是: 如果有加了@LoadBalanced注解的RestTemplates bean的话,就自动装配到restTemplates 变量中。

之后,通过loadBalancedRestTemplateInitializerDeprecated方法注入了一个SmartInitializingSingleton,我们知道SmartInitializingSingleton在装配到Spring Ioc之后会调用他的afterSingletonsInstantiated()方法。这里注入的SmartInitializingSingleton通过lamda实现了afterSingletonsInstantiated()方法,代码逻辑:通过方法参数打包注入到Ioc容器中的RestTemplateCustomizer(前面讲过了,他已经注入到IoC容器中了)到restTemplateCustomizers中,然后遍历restTemplates(这个list在前面说过了,已经把我们通过@Bean和@LoadBalanced注解的RestTemplate对象注入进来了)、针对每一个RestTemplate再遍历restTemplateCustomizers中的RestTemplateCustomizer对象,逐个调用他的customize方法。

OK!对于@LoadBalanced注解从应用、到初始化、生效机制,我们就分析清楚了。

最后还遗留两个小问题,初始化和应用两端各一个:初始化过程中的装配到loadBalancerInterceptor对象中的LoadBalancerClient具体是什么对象、什么时候注入的?应用端最终的负载均衡策略、负载均衡实现逻辑,我们还没具体分析。

下一篇文章解决上述两个问题。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/117886.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

线性【SVM】数学原理和算法实现

一. 数学原理 SVM是一类有监督的分类算法&#xff0c;它的大致思想是&#xff1a;假设样本空间上有两类点&#xff0c;如下图所示&#xff0c;我们希望找到一个划分超平面&#xff0c;将这两类样本分开&#xff0c;我们希望这个间隔能够最大化来使得模型泛化能力最强。 如上图所…

谷歌浏览器默认https 怎么关闭

#然后把网址从 https 改成http 回车即可

用于3D Visual Grounding的多模态场景图

文章目录 引言方法1. Language Scene Graph Module Paper&#xff1a;《Free-form Description Guided 3D Visual Graph Network for Object Grounding in Point Cloud》【ICCV’2021】 Code&#xff1a;https://github.com/PNXD/FFL-3DOG 引言 3DVG任务有以下三个挑战&#x…

c语言 简单认识 指针和结构体

指针 代码 #include <stdio.h>int main(){int a 10;//指针类型需要与变量的类型相同&#xff0c;且后面需要添加一个*符号&#xff08;注意这里不是乘法运算&#xff09;表示是对于类型的指针int * p &a; //这里的&并不是进行按位与运算&#xff0c;而是取…

迅为iTOP-i.MX8M开发板使用 make 工具

make 工具是编译辅助工具&#xff0c;用来解决使用命令编译工程非常繁琐的问题。 调用这个命令工具&#xff1a;我们在 windows 上编程使用 ide &#xff0c;我们有图形界面&#xff0c;有相应的按钮&#xff0c;比如说 build 或者 run 来编译。其实 make 这个编译辅助工具使…

【Python基础】IF、Else判断以及Whlie、for循环介绍符实例

运算符 1. if 语句体验2.逻辑运算3. if 语句进阶4.While循环4.1基本语法 5.break 和 continue6. for循环 1. if 语句体验 if 判断语句基本语法 在 Python 中&#xff0c;if 语句 就是用来进行判断的&#xff0c;格式如下&#xff1a; if 要判断的条件: 条件成立时&#xff0c;…

如何使用腾讯云+Picgo搭建图床

目录 一、进入腾讯云进行实名认证 二、领取免费存储额度 2.1新用户界面概览就可以领取 三、开始创建远端图床并生成秘钥等信息 3.1创建存储桶 3.2配置基本信息 3.3配置高级选项 3.4确认配置页面点击创建即可 3.5创建访问秘钥 3.6查看秘钥等信息 3.7查看桶名称 四、图…

lv9 嵌入式开发 数据库sqlite

1 数据库基本概念 数据&#xff08;Data&#xff09; 能够输入计算机并能被计算机程序识别和处理的信息集合 数据库 &#xff08;Database&#xff09; 数据库是在数据库管理系统管理和控制之下&#xff0c;存放在存储介质上的数据集合 2 常用的数据库 大型数据库…

【实践篇】一次Paas化热部署实践分享 | 京东云技术团队

前言 本文是早些年&#xff0c;Paas化刚刚提出不久时&#xff0c;基于部门内第一次Paas化热部署落地经验所写&#xff0c;主要内容是如何构建一些热部署代码以及一些避雷经验。 一、设计-领域模型设计 1.首先&#xff0c;确定领域服务所属的领域 2.其次&#xff0c;确定垂直…

前端基础之BOM和DOM

1、BOM和DOM简述 BOM&#xff1a;指浏览器对象模型&#xff0c;它使JavaScript有能力与浏览器进行对话 DOM&#xff1a;指文档对象模型&#xff0c;通过它&#xff0c;可以访问HTML文档的所有元素 2、Window对象 所有浏览器都支持window对象&#xff0c;他表示浏览器窗口。 如果…

5.3 连接和分离线程

方法 pthread_join(thread, status) pthread_detach(thread) pthread_attr_setdetachstate(attr, detachstate) pthread_attr_getdetachstate(attr) 连接 连接&#xff08;joining&#xff09;是一种线程之间完成同步的方法&#xff0c;举例如下。 pthread_join()方法会阻…

无代码平台哪家好,盘点最新国内十大无代码零代码平台排名

无代码&#xff08;No Code&#xff09;是一种通过使用可视化界面和预构建的模块来创建应用程序、网站或其他数字化解决方案的方法&#xff0c;不需要编写大量的手动代码。 无代码平台通常包括一些基本的构建块&#xff0c;如表单、按钮、文本框等&#xff0c;用户可以通过拖拽…

Android Studio(对话框AlertDialog)

前言 前面介绍了常用控件的相关属性&#xff0c;那些控件的使用起来也很容易。在本节及后面的章节介绍的控件将是相比于前面使用起来较为复杂的&#xff08;不过使用多了&#xff0c;也很容易上手&#xff09;。 这些控件常常需要配合java代码来使用&#xff0c;比如说对话框、…

zookeeper本地部署和集群搭建

zookeeper&#xff08;动物园管理员&#xff09;是一个广泛应用于分布式服务提供协调服务Apache的开源框架 Zookeeper从设计模式角度来理解&#xff1a;是一个基于观察者模式设计的分布式服务管理框架&#xff0c;它 负责存储和管理大家都关心的数据 &#xff0c;然 后 接受观察…

最短路径—Dijkstra算法及 变式题(一个人的旅行)

Dijkstra(迪杰斯特拉)算法是 典型的单源最短路径算法&#xff0c;用于计算一个节点到其他所有节点的最短路径 无向图为以下&#xff08;对称&#xff09; &#xff1a; 算法本质&#xff1a; 第一个最短点 &#xff08;直接与0.源点连接&#xff09; 第二个次短点 &#…

修改docker 版本的mysql 8.0 本机Navicat 连不上的问题

1.进入容器 docker exec -it xxxx bash 2.使用root账号登录mysql mysql -u root -p 3.查看当前加密方式 use mysql; SELECT Host, User, plugin from user; 我这是改过了&#xff0c;应该都是caching_sha2_password 4. 修改加密方式 ALTER USER root% IDENTIFIED WITH m…

性能测试 —— Jmeter 常用三种定时器

1、同步定时器 位置&#xff1a;HTTP请求->定时器->Synchronizing Timer 当需要进行大量用户的并发测试时&#xff0c;为了让用户能真正的同时执行&#xff0c;添加同步定时器&#xff0c;用户阻塞线程&#xff0c;知道线程数达到预先配置的数值&#xff0c;才开始执行…

3、Python基础语法:解释器、标识符、关键字、缩进

文章目录 Python解释器标识符关键字缩进代码示例与运行结果Python是一种高级编程语言,以其简洁明了的语法和强大的功能而受到广泛欢迎。本文将介绍Python的一些基础语法元素,包括解释器、标识符、关键字和缩进,并提供相应的代码示例和运行结果。 Python解释器 Python是一种…

半导体工厂将应用哪些制造创新技术?

半导体工厂是高科技产业的结晶&#xff0c;汇聚了世界上最新的技术。 在半导体的原料硅晶片上绘制设计图纸&#xff0c;不产生误差&#xff0c;准确切割并包装&#xff0c;然后用芯片生产出我们使用的电脑、智能手机、手表等各种电子产品。绝大多数半导体厂都采用一贯的工艺&a…

制造业出海如何乘风破浪?制胜绝招在这里!

目录 问题1: 企业为什么要出海&#xff1f; 问题2: 中国制造业出海企业应具备那些能力&#xff1f; 问题3: 出海应注意哪些事项以保证数据安全&#xff1f; 问题4: 出海企业应怎样做好人才管理&#xff1f; 问题5: 企业如何高质量出海&#xff1f; 国内制造领域各行各业纷…