深入理解@LoadBalanced註解的實現原理與客戶端負載均衡

  • 2019 年 11 月 14 日
  • 筆記

前提

在閱讀這篇部落格之前,希望你對SpringCloud套件熟悉和理解,更希望關注下微服務開發平台

概述

在使用springcloud ribbon客戶端負載均衡的時候,可以給RestTemplate bean 加一個@LoadBalanced註解,就能讓這個RestTemplate在請求時擁有客戶端負載均衡的能力,先前有細嚼過但是沒有做過筆記,剛好處理此類問題記錄下

@LoadBalanced

/**   * 注釋將RestTemplate bean標記為配置為使用LoadBalancerClient。   */  @Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD })  @Retention(RetentionPolicy.RUNTIME)  @Documented  @Inherited  @Qualifier  public @interface LoadBalanced {  }

通過源碼可以發現這是一個LoadBalanced標記註解並且標記了@Qualifier(基於Spring Boot的自動配置機制),我們可以溯源到LoadBalancerAutoConfiguration

LoadBalancerAutoConfiguration

/**   * 功能區的自動配置(客戶端負載平衡)   */  @Configuration  @ConditionalOnClass(RestTemplate.class)  @ConditionalOnBean(LoadBalancerClient.class)  @EnableConfigurationProperties(LoadBalancerRetryProperties.class)  public class LoadBalancerAutoConfiguration {        @LoadBalanced      @Autowired(required = false)      private List<RestTemplate> restTemplates = Collections.emptyList();   //這裡持有@LoadBalanced標記的RestTemplate實例        @Autowired(required = false)      private List<LoadBalancerRequestTransformer> transformers = Collections.emptyList();        @Bean      public SmartInitializingSingleton loadBalancedRestTemplateInitializerDeprecated(              final ObjectProvider<List<RestTemplateCustomizer>> restTemplateCustomizers) {          return () -> restTemplateCustomizers.ifAvailable(customizers -> {              for (RestTemplate restTemplate : LoadBalancerAutoConfiguration.this.restTemplates) {                  for (RestTemplateCustomizer customizer : customizers) {            //為restTemplate添加訂製                      customizer.customize(restTemplate);                  }              }          });      }       // ...        /**       * 以下針對classpath存在RetryTemplate.class的情況配置,先忽略       */      @Configuration      @ConditionalOnClass(RetryTemplate.class)      public static class RetryAutoConfiguration {            @Bean          @ConditionalOnMissingBean          public LoadBalancedRetryFactory loadBalancedRetryFactory() {              return new LoadBalancedRetryFactory() {              };          }      }      // ...  }

@LoadBalanced@Autowried結合使用,意思就是這裡注入的RestTempate Bean是所有加有@LoadBalanced註解標記的(持有@LoadBalanced標記的RestTemplate實例)

這段自動裝配的程式碼的含義不難理解,就是利用了RestTempllate的攔截器,使用RestTemplateCustomizer對所有標註了@LoadBalanced的RestTemplate Bean添加了一個LoadBalancerInterceptor攔截器,而這個攔截器的作用就是對請求的URI進行轉換獲取到具體應該請求哪個服務實例ServiceInstance。

關鍵問下自己:為什麼?

  • RestTemplate實例是怎麼被收集的?
  • 怎樣通過負載均衡規則獲取具體的具體的server?

繼續扒看源碼>
上面可以看出,會LoadBalancerAutoConfiguration類對我們加上@LoadBalanced註解的bean 添加loadBalancerInterceptor攔截器

LoadBalancerInterceptor

/**  * 功能區的自動配置(客戶端負載平衡)。  */  public class LoadBalancerInterceptor implements ClientHttpRequestInterceptor {        private LoadBalancerClient loadBalancer;        private LoadBalancerRequestFactory requestFactory;        public LoadBalancerInterceptor(LoadBalancerClient loadBalancer,              LoadBalancerRequestFactory requestFactory) {          this.loadBalancer = loadBalancer;          this.requestFactory = requestFactory;      }        public LoadBalancerInterceptor(LoadBalancerClient loadBalancer) {          // for backwards compatibility          this(loadBalancer, new LoadBalancerRequestFactory(loadBalancer));      }        @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,                  this.requestFactory.createRequest(request, body, execution));      }    }

重點看intercept方法 當我們restTemplate執行請求操作時,就會被攔截器攔截進入intercept方法,而loadBalancer是LoadBalancerClient的具體實現

RibbonLoadBalancerClient

    public <T> T execute(String serviceId, LoadBalancerRequest<T> request, Object hint)              throws IOException {          ILoadBalancer loadBalancer = getLoadBalancer(serviceId);          Server server = getServer(loadBalancer, hint);          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);      }

看到這裡相信都遇到過類似的錯誤,恍然大悟

No instances available for  xxxxx

總結

  • 1.根據serviceId 獲取對應的loadBalancer
  • 2.根據loadBalancer獲取具體的server(這裡根據負載均衡規則,獲取到具體的服務實例)
  • 3.創建RibbonServer
  • 4.執行具體請求

這裡

注意: @LoadBalanced 標記註解獲取到最後通過負載均衡規則獲取具體的具體的server來發起請求

案例

/**   * 服務註冊中心配置   *   * @author <a href="mailto:[email protected]">iByte</a>   * @since 1.0.1   */  @Configuration  @EnableConfigurationProperties(ModuleMappingHelper.class)  public class DiscoveryConfig {      @Autowired      Environment environment;        /**       * DiscoveryHeaderHelper默認bean       * @return       */      @Bean      public DiscoveryHeaderHelper discoveryHeaderHelper() {          DiscoveryHeaderHelper discoveryHeaderHelper = new DiscoveryHeaderHelper(environment);          DiscoveryHeaderHelper.INSTANCE = discoveryHeaderHelper;          return discoveryHeaderHelper;      }        /**       * resttemplate構建       */      @Resource      private RestTemplateBuilder restTemplateBuilder;        /**       * resttemplate請求bean,更改系統本身的builder       * @return       */      @Bean      @LoadBalanced      public RestTemplate restTemplate() {          RestTemplate restTemplate = restTemplateBuilder.configure(new RestTemplate());          //RestTemplate interceptors 遠程調用請求增加頭部資訊處理          restTemplate.getInterceptors().add(new RestApiHeaderInterceptor());          //RestTemplate Set the error handler 錯誤處理          restTemplate.setErrorHandler(new RestResponseErrorHandler());          return  restTemplate;      }        @Bean      public DiscoveryClient.DiscoveryClientOptionalArgs discoveryClientOptionalArgs() {          DiscoveryClient.DiscoveryClientOptionalArgs discoveryClientOptionalArgs = new DiscoveryClient.DiscoveryClientOptionalArgs();          discoveryClientOptionalArgs.setAdditionalFilters(Collections.singletonList(new DiscoveryHeaderClientFilter()));          discoveryClientOptionalArgs.setEventListeners(Collections.singleton(new EurekaClientEventListener()));          return discoveryClientOptionalArgs;      }  }

源碼地址 > DiscoveryConfig