Ribbon源碼分析系列(一)

簡介

Spring cloud ribbon在spring cloud微服務體系中充當著負載均衡的角色。這個負載均衡指的是客戶端的負載均衡。本文是ribbon源碼分析系列的第一篇,主要內容如下:

  • 怎樣使用spring cloud ribbon
  • ribbon原理概覽

怎樣使用Spring cloud ribbon

我們知道ribbon是客戶端負載均衡,也就是說在相同的服務集群中選擇一個,然後進行訪問,並從該服務獲取到結果。這裡面會引申出一個問題,就是相同服務集群的來源。ribbon有兩種方式獲取,第一種是通過Eureka(註冊中心),這種方式需要使用ribbon的工程是一個Eureka Client也就是說需要在工程的主函數上使用(@EnableDiscoveryClient),第二種方式是通過properties進行配置。

本文主要介紹的是第二種。

下面結合一個例子來說明:

添加對應依賴

<dependency>n <groupId>org.springframework.cloud</groupId>n <artifactId>spring-cloud-starter-ribbon</artifactId>n</dependency>n

定義配置類

@Configurationnpublic class RibbonConfig {nn @LoadBalancedn @Beann public RestTemplate restTemplate() {n return new RestTemplate();n }n}n

如上圖所示在該配置類中創建RestTemplate,並且使用@LoadBalanced註解。該註解使得RestTemplate具有了客戶端負載均衡的能力。

properties文件

spring.application.name=ribbon-clientnusers.ribbon.listOfServers=http://localhost:8081,http://localhost:8082n

users.ribbon.listOfServers這個參數很關鍵,它的含義是指定服務(集群)的地址,其中users是自定義的Key。本文中有兩個相同的服務,它們的地址分別為http://localhost:8081以及http://localhost:8082

定義一個Controller(Ribbon-Client端)

@RestControllernpublic class DemoController {nn private static final String URL = "http://users/hello";nn @Autowiredn private RestTemplate restTemplate;nn @RequestMapping(value = "/ribbon")n public String ribbon() {n return this.restTemplate.getForObject(DemoController.URL, String.class);n }nn}n

後端Server代碼(8081、8082)

@RequestMapping(value = "demo")npublic String demo() {n return "this is 8081 server...";n}n

@RequestMapping(value = "demo")npublic String demo() {n return "this is 8082 server...";n}n

此時當我們訪問http://localhost:8080/ribbon並且不斷刷新瀏覽器(多次訪問該介面),我們可以看到`http://localhost:8081/hello`、`http://localhost:8082/hello`這兩個介面反覆被調用。(交替返回`this is 8081 server…this is 8082 server…`)

至此通過這個例子我們完成了使用ribbon來完成客戶端負載均衡的功能,接下來通過源碼了解下其中的原理。

Ribbon原理概覽

通過源碼分析,個人認為可以拆解為如下部分:

  • 1.獲取@LoadBalanced註解標記的RestTemplate
  • 2.RestTemplate添加一個攔截器(filter),當使用RestTemplate發起http調用時進行攔截。
  • 3.在filter攔截到該請求時,獲取該次請求服務集群的全部列表信息。
  • 4.根據規則從集群中選取一個服務作為此次請求訪問的目標。
  • 5.訪問該目標,並獲取返回結果。

獲取@LoadBalanced註解標記的RestTemplate。

Ribbon將所有標記@LoadBalanced註解的RestTemplate保存到一個List集合當中,具體源碼如下:

@LoadBalancedn@Autowired(required = false)nprivate List<RestTemplate> restTemplates = Collections.emptyList();n

具體源碼位置是在LoadBalancerAutoConfiguration中。

RestTemplate添加一個攔截器(filter)

RestTemplate添加攔截器需要有兩個步驟,首先是定義一個攔截器,其次是將定義的攔截器添加到RestTemplate中。

定義一個攔截器

實現ClientHttpRequestInterceptor介面就具備了攔截請求的功能,該介面源碼如下:

public interface ClientHttpRequestInterceptor {n /**n *實現該方法,在該方法內完成攔截請求後的邏輯內容。n *對於ribbon而言,在該方法內完成了根據具體規則從n *服務集群中選取一個服務,並向該服務發起請求的操作。n */n ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException;nn}n

ribbon中對應的實現類是LoadBalancerInterceptor(不使用spring-retry的情況下)具體源碼如下:

public class LoadBalancerInterceptor implements ClientHttpRequestInterceptor {nn private LoadBalancerClient loadBalancer;n private LoadBalancerRequestFactory requestFactory;nn //省略構造器代碼...nn @Overriden public ClientHttpResponse intercept(final HttpRequest request, final byte[] body,n final ClientHttpRequestExecution execution) throws IOException {n final URI originalUri = request.getURI();n String serviceName = originalUri.getHost();n /**n *攔截請求,並調用loadBalancer.execute()方法n *在該方法內部完成server的選取。向選取的servern *發起請求,並獲得返回結果。n */n return this.loadBalancer.execute(serviceName, requestFactory.createRequest(request, body, execution));n }n}n

將攔截器添加到RestTemplate中

RestTemplate繼承了InterceptingHttpAccessor,在InterceptingHttpAccessor中提供了獲取以及添加攔截器的方法,具體源碼如下:

public abstract class InterceptingHttpAccessor extends HttpAccessor {nn /**n * 所有的攔截器是以一個List集合形式進行保存。n */n private List<ClientHttpRequestInterceptor> interceptors = new ArrayList<ClientHttpRequestInterceptor>();nn /**n * 設置攔截器。n */n public void setInterceptors(List<ClientHttpRequestInterceptor> interceptors) {n this.interceptors = interceptors;n }nn /**n * 獲取當前的攔截器。n */n public List<ClientHttpRequestInterceptor> getInterceptors() {n return interceptors;n }nn //省略部分代碼...n}n

通過這兩個方法我們就可以將剛才定義的LoadBalancerInterceptor添加到有@LoadBalanced註解標識的RestTemplate中。具體的源碼如下(LoadBalancerAutoConfiguration)省略部分代碼:

public class LoadBalancerAutoConfiguration {nn /**n * 獲取所有帶有@LoadBalanced註解的restTemplaten */n @LoadBalancedn @Autowired(required = false)n private List<RestTemplate> restTemplates = Collections.emptyList();nn /**n * 創建SmartInitializingSingleton介面的實現類。Spring會在所有n * 單例Bean初始化完成後回調該實現類的afterSingletonsInstantiated()n * 方法。在這個方法中會為所有被@LoadBalanced註解標識的n * RestTemplate添加ribbon的自定義攔截器LoadBalancerInterceptor。n */n @Beann public SmartInitializingSingleton loadBalancedRestTemplateInitializer(n final List<RestTemplateCustomizer> customizers) {n return new SmartInitializingSingleton() {n @Overriden public void afterSingletonsInstantiated() {n for (RestTemplate restTemplate : LoadBalancerAutoConfiguration.this.restTemplates) {n for (RestTemplateCustomizer customizer : customizers) {n customizer.customize(restTemplate);n }n }n }n };n }n /**n * 創建Ribbon自定義攔截器LoadBalancerInterceptorn * 創建前提是當前classpath下不存在spring-retry。n * 所以LoadBalancerInterceptor是默認的Ribbon攔截n * 請求的攔截器。n */n @Configurationn @ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate")n static class LoadBalancerInterceptorConfig {n @Beann public LoadBalancerInterceptor ribbonInterceptor(n LoadBalancerClient loadBalancerClient,n LoadBalancerRequestFactory requestFactory) {n return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);n }nn /**n * 添加攔截器具體方法。首先獲取當前攔截器集合(List)n * 然後將loadBalancerInterceptor添加到當前集合中n * 最後將新的集合放回到restTemplate中。n */n @Beann @ConditionalOnMissingBeann public RestTemplateCustomizer restTemplateCustomizer(n final LoadBalancerInterceptor loadBalancerInterceptor) {n return new RestTemplateCustomizer() {n @Overriden public void customize(RestTemplate restTemplate) {n List<ClientHttpRequestInterceptor> list = new ArrayList<>(n restTemplate.getInterceptors());n list.add(loadBalancerInterceptor);n restTemplate.setInterceptors(list);n }n };n }n }n}n

至此知道了ribbon攔截請求的基本原理,接下來我們看看Ribbon是怎樣選取server的。

Ribbon選取server原理概覽

通過上面的介紹我們知道了當發起請求時ribbon會用LoadBalancerInterceptor這個攔截器進行攔截。在該攔截器中會調用LoadBalancerClient.execute()方法,該方法具體代碼如下:

@Overridenpublic <T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException {n /**n *創建loadBalancer的過程可以理解為組裝選取服務的規則(IRule)、n *服務集群的列表(ServerList)、檢驗服務是否存活(IPing)等特性n *的過程(載入RibbonClientConfiguration這個配置類),需要注意n *的是這個過程並不是在啟動時進行的,而是當有請求到來時才會處理。n */n ILoadBalancer loadBalancer = getLoadBalancer(serviceId);nn /**n * 根據ILoadBalancer來選取具體的一個Server。n * 選取的過程是根據IRule、IPing、ServerListn * 作為參照。n */n Server server = getServer(loadBalancer);n if (server == null) {n throw new IllegalStateException("No instances available for " + serviceId);n }n RibbonServer ribbonServer = new RibbonServer(serviceId, server, isSecure(server,n serviceId), serverIntrospector(serviceId).getMetadata(server));nn return execute(serviceId, ribbonServer, request);n}n

通過代碼我們可知,首先創建一個ILoadBalancer,這個ILoadBalancer是Ribbon的核心類。可以理解成它包含了選取服務的規則(IRule)、服務集群的列表(ServerList)、檢驗服務是否存活(IPing)等特性,同時它也具有了根據這些特性從服務集群中選取具體一個服務的能力。

Server server = getServer(loadBalancer);這行代碼就是選取舉一個具體server。

最終調用了內部的execute方法,該方法代碼如下(只保留了核心代碼):

@Overridenpublic <T> T execute(String serviceId, ServiceInstance serviceInstance, LoadBalancerRequest<T> request) throws IOException {n try {n //發起調用n T returnVal = request.apply(serviceInstance);n statsRecorder.recordStats(returnVal);n return returnVal;n }n catch (IOException ex) {n statsRecorder.recordStats(ex);n throw ex;n }n catch (Exception ex) {n statsRecorder.recordStats(ex);n ReflectionUtils.rethrowRuntimeException(ex);n }n return null;n}n

接下來看下request.apply(serviceInstance)方法的具體做了那些事情(LoadBalancerRequestFactory中):

@Overridenpublic ClientHttpResponse apply(final ServiceInstance instance)n throws Exception {n HttpRequest serviceRequest = new ServiceRequestWrapper(request, instance, loadBalancer);n //省略部分代碼...n /**n * 發起真正請求。n */n return execution.execute(serviceRequest, body);n}n

看到這裡整體流程的原理就說完了,接下來我們結合一張圖來回顧下整個過程:

首先獲取所有標識@LoadBalanced註解的RestTemplate(可以理解成獲取那些開啟了Ribbon負載均衡功能的RestTemplate),然後將Ribbon默認的攔截器LoadBalancerInterceptor添加到RestTemplate中,這樣當使用RestTemplate發起http請求時就會起到攔截的作用。當有請求發起時,ribbon默認的攔截器首先會創建ILoadBalancer(裡面包含了選取服務的規則(IRule)、服務集群的列表(ServerList)、檢驗服務是否存活(IPing)等特性)。在代碼層面的含義是載入RibbonClientConfiguration配置類)。然後使用ILoadBalancer從服務集群中選擇一個服務,最後向這個服務發送請求。

總結

本文首先以一個簡單例子介紹了怎樣使用ribbon完成客戶端負載均衡的功能,然後結合源碼簡明的說明了ribbon負載均衡的內部原理。但是至於怎樣創建ILoadBalancer以及IRuleIPing等具體實現細節還有選取服務的具體過程本文沒有詳細介紹,後續文章會陸續介紹。

由於水平有限可能有些問題沒有闡述清楚,還請大家多多留言討論。

最後感謝Spring4all社區提供這個平台,能讓大家交流學習Spring相關知識。

本文作者:holy12345

轉載地址:Ribbon原理分析(一) | Spring For All

版權說明:本文來源於極樂科技合作網站,版權歸作者所有,轉載請註明作者及出處,謝謝!

小程序解決方案:99抵999元,開搶啦>>

推薦閱讀:

Spring Boot 1.5.x新特性:動態修改日誌級別
Netflix Zuul與Nginx的性能對比
【spring指南系列】使用Redis進行消息傳遞
springcloud: 配置中心svn示例和refresh

TAG:SpringCloud | Spring | WindowsRibbon |