【一起學源碼-微服務】Ribbon 源碼一:Ribbon概念理解及Demo調試

  • 2020 年 1 月 14 日
  • 筆記

前言

前情回顧

前面文章已經梳理清楚了Eureka相關的概念及源碼,接下來開始研究下Ribbon的實現原理。

我們都知道Ribbon在spring cloud中擔當負載均衡的角色, 當兩個Eureka Client互相調用的時候,Ribbon能夠做到調用時的負載,保證多節點的客戶端均勻接收請求。(這個有點類似於前端調用後端時Nginx做的負載均衡)

本講目錄

本講主通過一個簡單的demo來了解ribbon內部實現,這裡主要是對ribbon有個宏觀的認識,後續篇章會一步步通過debug的方式對ribbon的細節做一個全面的講解。

目錄如下:

  1. 一個demo來看看ribbon是做什麼的
  2. @LoadBalanced初探
  3. LoadBalancerAutoConfiguration初探
  4. RibbonLoadBalancerClient初探

說明

原創不易,如若轉載 請標明來源!

博客地址:一枝花算不算浪漫 微信公眾號:壹枝花算不算浪漫 (文章底部有公眾號二維碼)

Ribbon正文

一個demo來看看ribbon是做什麼的

首先看下我們這裡的demo,目錄結構如下:

這裡有3個模塊,eurekaServer作為註冊中心,serviceA和serviceB分別作為EurekaClient。

代碼地址上傳到了自己的git: https://github.com/barrywangmeng/spring-cloud-learn

ribbon相關的類結構信息

  1. 啟動了eureka client如下: 服務A 2個: 一個端口號為8087,另一個為8088 服務B 1個
  1. 查看註冊中心Dashboard
  1. 服務B調用服務A中的接口
  1. 查看負載均衡情況 第一次調用服務B的greeting方法:
  1. 第二次調用服務A的greeting方法:

這裡可以看到服務A調用的時候加了一個註解: @LoadBalanced

服務B第一次調用到了服務A的8088那個節點 服務B第二次調用到了服務A的8087那個節點

這裡就可以證明使用@LoadBalanced 自動對我們的http請求加了負載均衡,接下來我們就用@LoadBalanced來一步步往下看。

@LoadBalanced初探

接下來看下@LoadBalanced的源碼:

/** * Annotation to mark a RestTemplate bean to be configured to use  a LoadBalancerClient * @author Spencer Gibb */@Target({ ElementType.FIELD, ElementType.PARAMETER,  ElementType.METHOD })@Retention(RetentionPolicy.RUNTIME)@Documented@Inherited@Qualifierpublic @interface LoadBalanced {}

這裡主要看注釋,這裡意思是使用這個註解後可以將普通的RestTemplate 使用 LoadBalanceClient 這個類去處理。

接着我們看下LoadBalanced相關的配置。

LoadBalancerAutoConfiguration初探

我們知道,springboot + springcloud 對應的組件都會有相應的XXXAutoConfigure配置類,同理,我們在LoadBalanced同級包下可以找到對應的AutoConfigure類:LoadBalancerAutoConfiguration, 先看下類的定義:

@Configuration  @ConditionalOnClass(RestTemplate.class)  @ConditionalOnBean(LoadBalancerClient.class)  @EnableConfigurationProperties(LoadBalancerRetryProperties.class)  public class LoadBalancerAutoConfiguration {    }

看到這裡有個 @ConditionalOnClass(RestTemplate.class),這個含義是 只有存在RestTemplate 這個類的時該配置才會生效。

接着看LoadBalancerAutoConfiguration中的一些方法:

public class LoadBalancerAutoConfiguration {      @LoadBalanced      @Autowired(required = false)      private List<RestTemplate> restTemplates = Collections.emptyList();        @Bean      public SmartInitializingSingleton loadBalancedRestTemplateInitializer(              final List<RestTemplateCustomizer> customizers) {          return new SmartInitializingSingleton() {              @Override              public void afterSingletonsInstantiated() {                  for (RestTemplate restTemplate : LoadBalancerAutoConfiguration.this.restTemplates) {                      for (RestTemplateCustomizer customizer : customizers) {                          customizer.customize(restTemplate);                      }                  }              }          };      }  }

我們可以看下loadBalancedRestTemplateInitializer 方法,這個裏面會遍歷restTemplates然後調用customize() 方法進行特殊處理。

public class LoadBalancerAutoConfiguration {      @LoadBalanced      @Autowired(required = false)      private List<RestTemplate> restTemplates = Collections.emptyList();        @Configuration      @ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate")      static class LoadBalancerInterceptorConfig {          @Bean          public LoadBalancerInterceptor ribbonInterceptor(                  LoadBalancerClient loadBalancerClient,                  LoadBalancerRequestFactory requestFactory) {              return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);          }            @Bean          @ConditionalOnMissingBean          public RestTemplateCustomizer restTemplateCustomizer(                  final LoadBalancerInterceptor loadBalancerInterceptor) {              return new RestTemplateCustomizer() {                  @Override                  public void customize(RestTemplate restTemplate) {                      List<ClientHttpRequestInterceptor> list = new ArrayList<>(                              restTemplate.getInterceptors());                      list.add(loadBalancerInterceptor);                      restTemplate.setInterceptors(list);                  }              };          }      }  }

這裏面是為每一個restTemplate 添加一個loadBalancerInterceptor 攔截器,緊接着看一下LoadBalancerInterceptor.java

public class LoadBalancerInterceptor implements ClientHttpRequestInterceptor {        private LoadBalancerClient loadBalancer;      private LoadBalancerRequestFactory requestFactory;        @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));      }  }

這裏面就很簡單了,將serviceName(這裡就是對應我們demo中的:ServiceA)和request、body、excution等組成的新的request傳遞給LoadBalancerClient,然後調用其中的execute,這個方法的實現繼續往下看RibbonLoadBalancerClient

RibbonLoadBalancerClient初探

接下來再看一下 LoadBalanceClient

public interface LoadBalancerClient extends ServiceInstanceChooser {        <T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException;        <T> T execute(String serviceId, ServiceInstance serviceInstance, LoadBalancerRequest<T> request) throws IOException;        URI reconstructURI(ServiceInstance instance, URI original);  }

這個接口只有一個實現類:RibbonLoadBalancerClient, 那麼我們繼續看實現類中的execute方法:

@Override  public <T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException {      ILoadBalancer loadBalancer = getLoadBalancer(serviceId);      Server server = getServer(loadBalancer);      if (server == null) {          throw new IllegalStateException("No instances available for " + serviceId);      }      RibbonServer ribbonServer = new RibbonServer(serviceId, server, isSecure(server,              serviceId), serverIntrospector(serviceId).getMetadata(server));        return execute(serviceId, ribbonServer, request);  }

接着就可以在這個方法上愉快的debug了,我們先看看ILoadBalancer 是幹嘛的:

我們通過debug可以看到 獲取的ILoadBalancer 已經獲取到服務A所有的節點信息了,這一章就先不延伸下去了,後面會詳細來說ILoadBalancer處理的細節。

總結

這一篇主要講解了一個RestTemplate 加上@LoadBalanced 註解後是如何獲取到請求服務的多個節點信息的,通過debug 我們可以很清晰的看到請求流程,最後畫一個圖來總結一下:

申明

本文章首發自本人博客:https://www.cnblogs.com/wang-meng 和公眾號:壹枝花算不算浪漫,如若轉載請標明來源!