- 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得區別
,我們就從此註解下手: 從註釋上可以看出用來標記RestTemplate得bean( 使用LoadBalancerClient來配置),然後全局搜了一下並沒有發現有哪個地方會處理此@LoadBalanced註解
,此時心中有點產生疑惑(難道這個註解也不按套路來嗎?爲什麼沒有地方來解析或者攔截呢?
)。然後全局查看此工程中使用到此註解得地方僅有三處:
第①處:LoadBalancerAutoConfiguration類
(上一篇已經分析過)
第②處:AsyncLoadBalancerAutoConfiguration類
(上一篇也分析過)
第③處:MyRestTemplateConfiguration類
(當前項目中用來配置RestTemplate得配置類)我們也可以看出來@LoadBalanced註解可以修飾在方法上、屬性上、參數上
而且還被@Qualifier
修飾。從目前得突破口來看只有此註解比較可疑
。那我們就順着@Qualifier註解得解析看起:,對Spring註解解析相關類比較熟悉得就可以快速找到解析@Qualifier得解析器,而此類創建時機是當調用org.springframework.context.annotation.AnnotationConfigUtils#registerAnnotationConfigProcessors(org.springframework.beans.factory.support.BeanDefinitionRegistry, java.lang.Object)
方法( 此方法得調用在啓動類解析時,不清楚得可以看一下作者得SpringBoot啓動過程源碼分析系列文章
)創建ContextAnnotationAutowireCandidateResolver( 此類繼承了QualifierAnnotationAutowireCandidateResolver )
解析器的時候:,並且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
中創建): 然後調用請求工廠InterceptingClientHttpRequestFactory 的createRequest方法:通過類關係圖我們可以得知此工廠繼承自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方法,方法如下:方法中首先獲取RibbonServer實例,然後根據服務Id從SpringClientFactory中獲取服務均衡上下文,創建
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,然後對負載均衡得調用鏈做了一個全面得分析,但是對負載均衡得實例選擇那塊得策略與邏輯處理並沒有大幅度展開,感興趣得讀者可以根據文章作者提到得一些關鍵點步驟與總結再進行一次梳理,這樣會加深印象(
並沒有要求代碼得每一處都記住,也是不可能得,主要記住整個邏輯,然後記住這些調用鏈中用到得類與一些設計巧妙得地方,然後轉爲己用
)。
- ☛ 文章要是勘誤或者知識點說的不正確,歡迎評論,畢竟這也是作者通過閱讀源碼獲得的知識,難免會有疏忽!
- ☛ 要是感覺文章對你有所幫助,不妨點個關注,或者移駕看一下作者的其他文集,也都是幹活多多哦,文章也在全力更新中。
- ☛ 著作權歸作者所有。商業轉載請聯繫作者獲得授權,非商業轉載請註明出處!