- SpringCloud 版本 :Hoxton.SR1
- SpringBoot 版本:2.2.1.RELEASE
- 本文适用于对SpringBoot有一定基础的人,主要讲解RestTemplate的工作过程。讲解方式:
场景驱动
- 关键词 :RestTemplate 配置使用演示及代码示例、负载均衡过程源码分析
- 上一篇 SpringCloud 服务注册与发现 源码分析(二)分析了 Eureka Client端和负载均衡客户端的相关组件及装配过程(
本篇的铺垫,因为实现复负载均衡的一些底层组件都是这里实现创建的,建议两篇结合着看
),本篇将会介绍RestTemplate的工作过程以及负载均衡的实现。
1. RestTemplate
- 此组件是Spring中用来做远程调用而封装的一个调用模板:包括但不限于 GET、POST、PUT等简单易用的操作方法
1.1 使用RestTemplate实现负载均衡的先决条件
准备工作:
一个Eureka消费者,两个Eureka提供者,两个Eureka服务器做服务端集群
1.1.1 Maven中引入如下依赖,版本自适应( 此依赖包中默认包含RestTemplate核心模块调用类 )
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
1.1.2 创建RestTemplate模板调用类,创建的方式多种多样,常用的就是基于@Configuration + @Bean
的方式
Eureka 服务端与客户端如何启动与配置此处不再详述,不了解的读者可观看作者相关的文章
SpringCloud 服务注册与发现
SpringCloud 注解之@EnableEurekaServer与@EnableEurekaClient原理-
服务启动效果如下图:
到此前期准备工作已经完成,我们可以发现两个提供者provider1和provider2均已注册到服务端了,由于作者本机演示,所以Port设置的不一样而IP地址是一样的,正常线上应用都是IP不一样而Port一样,道理都是一样的。下面我们看一下浏览器访问消费者提供的Web接口,会如何负载到服务端provider1和provider2
1.2 如何使用RestTemplate ?
1.2.1 前面我们也说了,RestTemplate中提供了若干个调用方法,使用者可以根据自己的需求选择性调用,此处作者写了个简单的Web接口,接口中调用RestTemplate的getForObject方法,需要注意的是:要想实现客户端的负载均衡,调用的URL中要使用应用名spring.application.name对应的值。
如图:
-
下面我们访问
消费端的uri为/loadBalance的接口地址
:
第一次请求
,控制台输出
第二次请求
,控制台输出
......
我们可以发现,一次provider1然后一次provider2,轮询的请求这两个服务提供者
-
然后再访问
消费端的uri为/noLoadBalance的接口地址
:
1.2.2 而图1(作者暂时未找到在简书中如何实现页内跳转,此处还请读者向上翻到图1,若是有读者知道烦请评论区留言哦~)
中的配置,我们观察两个RestTemplate模板类的唯一不同之处就是第一个创建过程多了一个@LoadBalanced注解,此时我们可以得出结论就是带有@LoadBalanced注解的RestTemplate具有负载均衡的能力
,得出这个结论之后实际开发上已经够用了,但是为了做到 知根知底
,我们将剖析负载均衡背后的原理!
代码下载地址:https://github.com/LiujunjieALiling/spring-cloud-netflix.git
1.2.3 在此,我们先回答上一篇中的问题1: 为什么只会注入带有@LoadBalanced注解的RestTemplate实例,而普通的RestTemplate不会注入进去?
-
由于两个RestTemplate得不同之处仅仅是一个@LoadBalanced得区别
,我们就从此注解下手:然后全局搜了一下并没有发现有哪个地方会处理此@LoadBalanced注解
,此时心中有点产生疑惑(难道这个注解也不按套路来吗?为什么没有地方来解析或者拦截呢?
)。然后全局查看此工程中使用到此注解得地方仅有三处:
第①处:LoadBalancerAutoConfiguration类
(上一篇已经分析过)
第②处:AsyncLoadBalancerAutoConfiguration类
(上一篇也分析过)
第③处:MyRestTemplateConfiguration类
(当前项目中用来配置RestTemplate得配置类)我们也可以看出来@LoadBalanced注解可以修饰在方法上、属性上、参数上
而且还被@Qualifier
修饰。从目前得突破口来看只有此注解比较可疑
。那我们就顺着@Qualifier注解得解析看起:org.springframework.context.annotation.AnnotationConfigUtils#registerAnnotationConfigProcessors(org.springframework.beans.factory.support.BeanDefinitionRegistry, java.lang.Object)
方法( 此方法得调用在启动类解析时,不清楚得可以看一下作者得SpringBoot启动过程源码分析系列文章
)创建ContextAnnotationAutowireCandidateResolver( 此类继承了QualifierAnnotationAutowireCandidateResolver )
解析器的时候:javax.inject.Qualifier
与org.springframework.beans.factory.annotation.Qualifier
注解都缓存起来以供属性注入时使用: - 当属性注入得时候(
生命周期其中一个关键点
)调用org.springframework.beans.factory.support.DefaultListableBeanFactory#isAutowireCandidate(java.lang.String, org.springframework.beans.factory.support.RootBeanDefinition, org.springframework.beans.factory.config.DependencyDescriptor, org.springframework.beans.factory.support.AutowireCandidateResolver)
方法时,由于上下文中得解析器是上文说得ContextAnnotationAutowireCandidateResolver
,而isAutowireCandidate继承自父类,所以我们直接从父类QualifierAnnotationAutowireCandidateResolver
得isAutowireCandidate
方法看起(对SpringBoot启动过程与Spring容器解析过程不了解得可移步作者得SpringBoot启动解析系列文章,此处不再深入
):
@Override
public boolean isAutowireCandidate(BeanDefinitionHolder bdHolder, DependencyDescriptor descriptor) {
boolean match = super.isAutowireCandidate(bdHolder, descriptor);
if (match) {
// 校验依赖得属性中得注解与当前bean定义中是否匹配(很关键得步骤)
match = checkQualifiers(bdHolder, descriptor.getAnnotations());
if (match) {
MethodParameter methodParam = descriptor.getMethodParameter();
if (methodParam != null) {
Method method = methodParam.getMethod();
if (method == null || void.class == method.getReturnType()) {
match = checkQualifiers(bdHolder, methodParam.getMethodAnnotations());
}
}
}
}
return match;
}
/**
* Match the given qualifier annotations against the candidate bean definition.
*/
protected boolean checkQualifiers(BeanDefinitionHolder bdHolder, Annotation[] annotationsToSearch) {
if (ObjectUtils.isEmpty(annotationsToSearch)) {
return true;
}
SimpleTypeConverter typeConverter = new SimpleTypeConverter();
for (Annotation annotation : annotationsToSearch) {
Class<? extends Annotation> type = annotation.annotationType();
boolean checkMeta = true;
boolean fallbackToMeta = false;
if (isQualifier(type)) {
if (!checkQualifier(bdHolder, annotation, typeConverter)) {
fallbackToMeta = true;
}
else {
checkMeta = false;
}
}
if (checkMeta) {
boolean foundMeta = false;
for (Annotation metaAnn : type.getAnnotations()) {
Class<? extends Annotation> metaType = metaAnn.annotationType();
if (isQualifier(metaType)) {
foundMeta = true;
// Only accept fallback match if @Qualifier annotation has a value...
// Otherwise it is just a marker for a custom qualifier annotation.
if ((fallbackToMeta && StringUtils.isEmpty(AnnotationUtils.getValue(metaAnn))) ||
!checkQualifier(bdHolder, metaAnn, typeConverter)) {
return false;
}
}
}
if (fallbackToMeta && !foundMeta) {
return false;
}
}
}
return true;
}
/**
* Match the given qualifier annotation against the candidate bean definition.
*/
protected boolean checkQualifier(
BeanDefinitionHolder bdHolder, Annotation annotation, TypeConverter typeConverter) {
Class<? extends Annotation> type = annotation.annotationType();
RootBeanDefinition bd = (RootBeanDefinition) bdHolder.getBeanDefinition();
// 此处我们没有对bean定义指定解析器,所以获取得为空
AutowireCandidateQualifier qualifier = bd.getQualifier(type.getName());
if (qualifier == null) {
// 同理,也为空
qualifier = bd.getQualifier(ClassUtils.getShortName(type));
}
if (qualifier == null) {
// 正常情况下创建得bean定义也没有设置此属性,所以返回也为null
Annotation targetAnnotation = getQualifiedElementAnnotation(bd, type);
// Then, check annotation on factory method, if applicable
if (targetAnnotation == null) {
// 此处针对于@Bean 方式创建得bean会设置一个FactoryMethod属性,此属性保存得就是配置类中得创建方法,
// 此处根据type(@Qualifier)获取到得目标注解就是@LoadBalanced
targetAnnotation = getFactoryMethodAnnotation(bd, type);
}
if (targetAnnotation == null) {
RootBeanDefinition dbd = getResolvedDecoratedDefinition(bd);
if (dbd != null) {
targetAnnotation = getFactoryMethodAnnotation(dbd, type);
}
}
if (targetAnnotation == null) {
// Look for matching annotation on the target class
if (getBeanFactory() != null) {
try {
Class<?> beanType = getBeanFactory().getType(bdHolder.getBeanName());
if (beanType != null) {
targetAnnotation = AnnotationUtils.getAnnotation(ClassUtils.getUserClass(beanType), type);
}
}
catch (NoSuchBeanDefinitionException ex) {
// Not the usual case - simply forget about the type check...
}
}
if (targetAnnotation == null && bd.hasBeanClass()) {
targetAnnotation = AnnotationUtils.getAnnotation(ClassUtils.getUserClass(bd.getBeanClass()), type);
}
}
// 此处获取得targetAnnotation注解为@LoadBalanced,并且annotation也是,则会直接返回匹配成功,注入属性
if (targetAnnotation != null && targetAnnotation.equals(annotation)) {
return true;
}
}
Map<String, Object> attributes = AnnotationUtils.getAnnotationAttributes(annotation);
if (attributes.isEmpty() && qualifier == null) {
// If no attributes, the qualifier must be present
return false;
}
for (Map.Entry<String, Object> entry : attributes.entrySet()) {
String attributeName = entry.getKey();
Object expectedValue = entry.getValue();
Object actualValue = null;
// Check qualifier first
if (qualifier != null) {
actualValue = qualifier.getAttribute(attributeName);
}
if (actualValue == null) {
// Fall back on bean definition attribute
actualValue = bd.getAttribute(attributeName);
}
if (actualValue == null && attributeName.equals(AutowireCandidateQualifier.VALUE_KEY) &&
expectedValue instanceof String && bdHolder.matchesName((String) expectedValue)) {
// Fall back on bean name (or alias) match
continue;
}
if (actualValue == null && qualifier != null) {
// Fall back on default, but only if the qualifier is present
actualValue = AnnotationUtils.getDefaultValue(annotation, attributeName);
}
if (actualValue != null) {
actualValue = typeConverter.convertIfNecessary(actualValue, expectedValue.getClass());
}
if (!expectedValue.equals(actualValue)) {
return false;
}
}
return true;
}
上述代码中判断逻辑是 isAutowireCandidate() -> checkQualifiers() -> checkQualifier()
,首先获取候选bean得所有注解,然后循环调用checkQualifier方法判断这些注解是否符合要求,而判断是否符合要求得核心代码是在checkQualifier方法中
。通过上述代码分析得知:通过@Qualifier注解只会过滤出带有@LoadBalanced修饰得RestTemplate,不带有此注解得RestTemplate不会注入到属性集合中
。换句话说就是:只要你注入得属性(容器bean)被带有@Qualifier得自定义注解修饰(即使不叫@LoadBalanced
),那么就可以将容器bean注入到属性中。此处就不演示了,代码都在GitHub上可以下载
1.2.4 解决完第一个问题之后,接下来我们就开始分析RestTemplate怎么就可以实现均衡的调用服务提供者呢? 开始我们今天另一个主题
( 手撕RestTemplate负载均衡过程源码,上一篇中的问题2
)
2.1 RestTemplate实现负载均衡的底层实现
-
还是老套路,我们使用
场景驱动
的方式从前到后梳理调用链。首先,我们顺着入口方法getForObject
:execute
方法,基本上所有的直接使用方法调用流程都是getXXX/postXXX/putXXX 等方法 -> execute(可省略) -> doExecute(真正处理调用过程的实现)
,所以我们看一下doExecute方法的实现,大致分为以下几部分:
① 处createRequest(url, method)方法
:通过注释可以得知是通过ClientHttpRequestFactory工厂来创建ClientHttpRequest
实例的getRequestFactory方法
获取请求工厂实现,而此方法被子类InterceptingHttpAccessor
覆写
我们通过注释可以得知会覆写父类HtppAccessor的getRequestFactory方法
来创建InterceptingClientHttpRequestFactory
类型的请求工厂。首先获取当前对象的interceptors
列表(当前只有一个RetryLoadBalancerInterceptor,拦截器的设置时机已经上篇文章说明了
),所以我们此处的拦截器不为空,将会创建一个InterceptingClientHttpRequestFactory 类型的请求工厂
,并且创建InterceptingClientHttpRequestFactory的时候同时会包装一个SimpleClientHttpRequestFactory
类型的工厂(调用super.getRequestFactory()
在父类HttpAccessor
中创建):AbstractClientHttpRequestFactoryWrapper
,所以会调用父类AbstractClientHttpRequestFactoryWrapper的创建请求方法:InterceptingClientHttpRequest
类型的请求:
② 处requestCallback.doWithRequest(request)方法
:判断若是requestCallback
不为空,则调用其doWithRequest
方法对请求进行处理,一般情况下我们调用get/post..方法时不会指定这个requestCallback参数,所以会使用默认的AcceptHeaderRequestCallback
实例,而此实例的作用就是像请求头中添加支持的MediaType
类型。
③ 处request.execute()方法
:调用InterceptingClientHttpRequest 的execute方法,此execute方法继承自AbstractClientHttpRequest抽象类
:
模板方法 executeInternal(this.headers)
,此模板方法得实现是在抽象类AbstractBufferingClientHttpRequest
中:内部模板方法 executeInternal(headers, bytes)
,方法实现就是在创建的请求实例InterceptingClientHttpRequest 中:InterceptingRequestExecution
请求拦截执行器,紧接着调用执行器得execute
方法。很显然,由于我们有拦截器(RetryLoadBalancerInterceptor
),所以会执行拦截器得interceptor
方法,如图:①首先获取原始请求URL,然后获取URL中得主机名(此处是
provider
)②根据负载均衡重试工厂获取重试策略:此处得重试工厂是
RibbonLoadBalancedRetryFactory(上一篇文章说过)
,创建得策略是RibbonLoadBalancedRetryPolicy
。③然后再创建重试调用模板类
RetryTemplate
,此模板类中会设置一些重试监听器、重试策略等,最重要得一点是此模板类似于RestTemplate(由于是重试得模板调用类,所以会多一些重试策略(
此处设置得是InterceptorRetryPolicy)、重试机制、回调监听等等,看到这里我们发现刚开始执行RestTemplate得逻辑已经切换到RetryTemplate了
)④然后调用RetryTemplate得
execute
方法,再调用org.springframework.retry.support.RetryTemplate#doExecute
,此方法中主要步骤是首先通过 重试策略InterceptorRetryPolicy
获取重试上下文(LoadBalancedRetryContext,获取得同时会通过InterceptorRetryPolicy调用org.springframework.cloud.client.loadbalancer.InterceptorRetryPolicy#canRetry方法选择服务实例,此处创建得服务实例是 RibbonServer 类型,具体是通过org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerClient#getServer(com.netflix.loadbalancer.ILoadBalancer, java.lang.Object)方法,调用com.netflix.loadbalancer.BaseLoadBalancer#chooseServer方法,此选择方法是负载均衡得核心实现,调用默认规则RoundRobinRule得com.netflix.loadbalancer.RoundRobinRule#choose(com.netflix.loadbalancer.ILoadBalancer, java.lang.Object)方法选择实例,默认得轮询规则可以通过set方法修改。然后将实例设置到上下文中
),接着执行回调逻辑(函数式编程,看起来不是很直观,就是context ->{} 里面得逻辑
)注:总的来说逻辑之前得桥梁就是LoadBalancedRetryContext,会向此重试上下文中设置一系列得参数供负载均衡得时候使用
⑤由于上面已经获取到服务实例(
RibbonServer
),此处不会执行⑥调用RibbonLoadBalancerClient得execute方法,方法如下:
RibbonStatsRecorder
用于对调用返回结果做记录(记录服务状态ServerStats
),此处得返回值是通过LoadBalancerRequest函数接口
调用得方式(apply方法)根据服务实例RibbonServer获取得。函数是this.requestFactory.createRequest(request, body, execution)
ServiceRequestWrapper
请求,通过LoadBalancerRequestTransformer
允许用户对HttpRequest做修改,最后请求执行器InterceptingRequestExecution得execute
方法(我们发现又回到图22中得execute方法,类似于递归处理拦截器得拦截方法。此处责任链模式得设计通过一个迭代器来实现很值得借鉴与学习。但是由于我们只有一个拦截器,所以此处得this.iterator.hasNext()为false,会进入else逻辑
)图22中已经截图,此处大致说一下里面得逻辑:首先获取ServiceRequestWrapper得请求方法,然后通过成员变量ClientHttpRequestFactory得实现
SimpleClientHttpRequestFactory
调用createRequest方法创建HTTP请求(此处得createRequest方法要玩真的了😂,前面很多类都有这个方法,我们可以把它想象成在做负载均衡得一些关键性步骤
),创建得实例是SimpleBufferingClientHttpRequest
,然后完善请求头参数,如果请求body不为空,将body写入到SimpleBufferingClientHttpRequest委派请求得输出流中,最后调用此请求实例得execute方法
,此执行方法得execute调用链 :org.springframework.http.client.AbstractClientHttpRequest#execute -> org.springframework.http.client.AbstractBufferingClientHttpRequest#executeInternal(org.springframework.http.HttpHeaders) -> org.springframework.http.client.SimpleBufferingClientHttpRequest#executeInternal(HttpHeaders headers, byte[] bytes)
,此处创建得是原生得java.net.HttpURLConnection
,到此负载均衡得调用过程基本分析完毕。由于使用了大量得函数式编程,请求拦截得过程与代码得编写理解起来还是有一定难度得,不过其中有很多地方得逻辑稍显繁琐,让会让阅读起来有一定得理解成本。下面我们来做个简单得总结:
1. 调用RestTemplate得execute方法,由于有拦截器,所以会创建InterceptingClientHttpRequestFactory,请求工厂中会保存设置得拦截器集合。然后调用工厂得createRequest方法创建InterceptingClientHttpRequest(
可以理解为负载均衡就是通过拦截器来实现得,所以后面创建得类基本上都会带有Interceptor字样
),会保存一份原始得请求工厂(SimpleClientHttpRequestFactory
)、拦截器列表、请求方法、请求URI。
2. 调用InterceptingClientHttpRequest得execute方法,此处涉及到大量得模板设计,最终会调用InterceptingRequestExecution得execute方法。此execute方法中会通过迭代器得方式实现拦截器得责任链调用,直到执行最后一个拦截器(此处得拦截器可以作为扩展钩子
)。执行得过程中会创建重试策略、将RestTemplate得执行逻辑切换到执行RetryTemplate得逻辑,获取服务实例:获取实例得默认规则是轮询,通过com.netflix.loadbalancer.RoundRobinRule#choose(com.netflix.loadbalancer.ILoadBalancer, java.lang.Object)方法选取实例(此处就是负载均衡得默认核心实现,当然开发者可以自己实现ILoadBalancer接口实现自己得负载均衡算法,作者没有对负载均衡得选择做大量篇幅介绍,感兴趣得读者可以自己看看
)
3. 选择完要调用得服务之后,通过JDK原生得java.net.HttpURLConnection
处理HTTP请求,得到响应。
- 介于篇幅原因,本文着重解决了 上篇提到问题1与问题2,然后对负载均衡得调用链做了一个全面得分析,但是对负载均衡得实例选择那块得策略与逻辑处理并没有大幅度展开,感兴趣得读者可以根据文章作者提到得一些关键点步骤与总结再进行一次梳理,这样会加深印象(
并没有要求代码得每一处都记住,也是不可能得,主要记住整个逻辑,然后记住这些调用链中用到得类与一些设计巧妙得地方,然后转为己用
)。
- ☛ 文章要是勘误或者知识点说的不正确,欢迎评论,毕竟这也是作者通过阅读源码获得的知识,难免会有疏忽!
- ☛ 要是感觉文章对你有所帮助,不妨点个关注,或者移驾看一下作者的其他文集,也都是干活多多哦,文章也在全力更新中。
- ☛ 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处!