SpringCloud Ribbon原始碼探索學習
Ribbon使用
在平時使用Ribbon時,更多的是將Ribbon與RestTemplate相結合:
@Bean
@LoadBalanced
RestTemplate restTemplate(){
return new RestTemplate();
}
複製程式碼
首先定義一個RestTemplate,通過註解注入,同時註解也完成了負載均衡。
同時去使用restTemplate進行Rest呼叫
@Override
public String hiService(String name){
return restTemplate.getForObject("http://SERVER-HI/hi?name=" +name,String.class);
}
複製程式碼
那麼在我們在輸入http://localhost:8770/hi?name=lixin後,到底做了什麼
一、封裝
首先,在進行getForObject方法後,會將帶入的url進行封裝,封裝成http請求request,然後被攔截器LoadBalancerInterceptor進行攔截,程式碼分別是:
@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);
}
複製程式碼
二、攔截
以及攔截部分:將request攔截下,擷取其中URL及服務名,用於之後呼叫負載均衡方法時,選擇合適的服務例項
@Override
public ClientHttpResponse intercept(final HttpRequest request, final byte[] body,
final ClientHttpRequestExecution execution) throws IOException {
final URI originalUri = request.getURI();
String serviceName = originalUri.getHost();
Assert.state(serviceName != null, "Request URI does not contain a valid hostname: " + originalUri);
return this.loadBalancer.execute(serviceName, requestFactory.createRequest(request, body, execution));
}
複製程式碼
三、根據負載均衡器呼叫服務例項
至此,呼叫RibbonLoadBalancerClient中的execute方法,先檢視一下RibbonLoadBalancerClient類:
其中LoadBalancerClient介面,有如下三個方法,其中excute()為執行請求,reconstructURI()用來重構url。
ServiceInstanceChooser介面,主要有一個方法,用來根據serviceId來獲取ServiceInstance。
它繼承了ServiceInstanceChooser及LoadBalancerClient類,最終的負載均衡的請求處理,由它來執行
首先,去獲取需要載入的負載均衡策略,通過getLoadBalancer方法執行,預設是輪詢RoundRobbinRule方式:
在獲取到負載均衡策略之後,通過getServer()方法去獲取例項,點選進入getServer方法,發現是ILoadBalancer類去選擇服務例項。
在ILoadBalancer介面中,addServers()方法是新增一個Server集合;chooseServer()方法是根據key去獲取Server;markServerDown()方法用來標記某個服務下線;getReachableServers()獲取可用的Server集合;getAllServers()獲取所有的Server集合。
chooseServer則是由BaseLoadBalancer類進行實現:
根據程式碼可以看出,具體choose方法是根據不同的負載均衡策略,會有不同的選擇方法,返回具體根據策略得到的服務例項。
最後根據服務例項,進行請求的呼叫。
負載均衡策略
IRule用於複雜均衡的策略,它有三個方法,其中choose()是根據key 來獲取server,setLoadBalancer()和getLoadBalancer()是用來設定和獲取ILoadBalancer的
IRule有很多預設的實現類,這些實現類根據不同的演算法和邏輯來處理負載均衡。Ribbon實現的IRule有以下幾個。在大多數情況下,這些預設的實現類是可以滿足需求的,如果有特性的需求,可以自己實現。
- BestAvailableRule 選擇最小請求數
- ClientConfigEnabledRoundRobinRule 輪詢
- RandomRule 隨機選擇一個server
- RoundRobinRule 輪詢選擇server
- RetryRule 根據輪詢的方式重試
- WeightedResponseTimeRule 根據響應時間去分配一個weight ,weight越低,被選擇的可能性就越低
- ZoneAvoidanceRule 根據server的zone區域和可用性來輪詢選擇
RoundRobbinRule
那我們就先看看RoundRobbinRule類中的輪詢策略:
①首先獲取所有存活的服務列表reachableServers及所有服務列表allServers,判斷兩個list是否為空。 ②incrementAndGetModulo中則是對一個原子性變數進行+1操作,並同時進行一個CAS操作,去修改nextServerIndex值,保證輪詢的可靠性。③最後判斷服務是否可用,如果不可用,則重新進入迴圈。
④如果在10次迴圈後,仍然沒有可用的服務,則退出迴圈並進行警告。最後返回服務例項
RetryRule
可重試的輪詢策略如下:
可見此處多了兩行程式碼:
long requestTime = System.currentTimeMillis();
long deadline = requestTime + maxRetryMillis;
複製程式碼
定義了500ms的總重試時間,如果服務例項獲取不到,則進入迴圈,在迴圈中每次需要判斷一下是否超過總時間
while迴圈中,每次都會去獲取一下服務例項,然後進行判斷,如果例項仍然沒有獲取到,則對當前執行緒進行Thread.yield()操作,此操作的意義是:讓出當前執行緒時間分片,重新爭奪時間片,讓定時任務去執行,看是否達到規定時間,如到時間,則執行interrupt()操作,退出迴圈
這即是可重試的策略
總體流程
Ribbon + RestTemplate 的負載平衡,流程是: 通過註解後,對url進行封裝request,攔截器對request進行攔截,然後根據負載均衡器去呼叫服務例項,完成負載平衡和服務呼叫