1. 程式人生 > >springCloud的[email

springCloud的[email

學習springcluod的時候,有一個困惑,為什麼RestTemplate上面@LoadBalanced註解,就能實現負載均衡,今天我們一起學習下原始碼,探索下springCloud底層的祕密:

第一步:在看原始碼之前我們先自己搭建一個消費者微服務(因為我們這裡主要講解的是springCloud的Ribbon負載均衡,所以註冊中心和提供者這裡就不再講解了)

1、引入必要的maven依賴:

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</
artifactId> <version>1.5.9.RELEASE</version> <relativePath /> </parent> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</
artifactId> <version>Edgware.RELEASE</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <dependencies> <dependency> <groupId
>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-ribbon</artifactId> </dependency> </dependencies>

2、建立springboot的啟動類:

@SpringBootApplication
@EnableDiscoveryClient
public class HelloApplicaton {

    //這裡就是建立一個負載均衡的RestTemplate Bean
    @LoadBalanced
    @Bean
public RestTemplate restTemplate(){
        return new RestTemplate();
    }


    public static void main(String[] args) {
        SpringApplication.run(HelloApplicaton.class, args);
    }
}

3、resource下配置啟動檔案:application.yml或者application.properties(具體的配置檔案的作用我覺得應該不用我做解釋)

server:
  port: 9091
spring:
  application:
    name: post-service
eureka:
  instance:
    hostname: localhost
  client:
    serviceUrl:
        defaultZone: http://localhost:1111/eureka/

4、建立消費者請求介面:

@RestController
public class HelloController {

   //注入前面建立的負載均衡的RestTemplate
    @Autowired
public RestTemplate restTemplate;

    @RequestMapping(value = "/hello", method = RequestMethod.GET)
    public String postHello(){
     //使用有負載均衡能力的RestTemplate請求微服務
     return   restTemplate.getForEntity("http://HELLO-SERVICE/hello"
,String.class,"").getBody();
    }
}

注:這裡的HELLO-SERVICE就是註冊中心存在的微服務的名稱,這個大家應該都很清楚;

這樣一個具有負載均衡能力的消費者介面就建立成功了!

但是為什麼被@LoadBalanced註解修飾的RestTemplate就有了負載均衡的能力呢,先看看@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
@Qualifier
public @interface LoadBalanced {
}

這就是一個普通的標記註解,作用就是修飾RestTemplate讓其擁有負載均衡的能力,檢視org.springframework.cloud.client.loadbalancer包下面的class,我們很容易發現LoadBalancerAutoConfiguration這個類;

@Configuration//這是一個配置類@ConditionalOnClass(RestTemplate.class)//這個配置檔案載入必要條件是存在RestTemplate類和LoadBalancerClient
//Bean
@ConditionalOnBean(LoadBalancerClient.class)
@EnableConfigurationProperties(LoadBalancerRetryProperties.class)
public class LoadBalancerAutoConfiguration {

  //這個很重要,這裡的restTemplates是所有的被@LoadBalanced註解的集合,這就是標記註解的作用(Autowired是可以集合注入的)
   @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);
               }
            }
         }
      };
   }

   @Autowired(required = false)
   private List<LoadBalancerRequestTransformer> transformers = Collections.emptyList();

   @Bean
   @ConditionalOnMissingBean
public LoadBalancerRequestFactory loadBalancerRequestFactory(
         LoadBalancerClient loadBalancerClient) {
      return new LoadBalancerRequestFactory(loadBalancerClient, transformers);
   }

//生成一個LoadBalancerInterceptor的Bean
   @Configuration
   @ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate")
   static class LoadBalancerInterceptorConfig {
      @Bean
public LoadBalancerInterceptor ribbonInterceptor(
            LoadBalancerClient loadBalancerClient,
            LoadBalancerRequestFactory requestFactory) {
         return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);
      }

     //給註解了@LoadBalanced的RestTemplate加上攔截器
      @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);
            }
         };
      }
   }

}

總結:現在我們應該大致知道@loadBalanced的作用了,就是起到一個標記RestTemplate的作用,當服務啟動時,標記了的RestTemplate物件裡面就會被自動加入LoadBalancerInterceptor攔截器,這樣當RestTemplate像外面發起http請求時,會被LoadBalancerInterceptor的intercept函式攔截,而intercept裡面又呼叫了LoadBalancerClient介面實現類execute方法,我們接著往下看

LoadBalancerInterceptor的intercept方法:
@Override
public ClientHttpResponse intercept(final HttpRequest request, final byte[] body,
      final ClientHttpRequestExecution execution) throws IOException {
 //這是以服務名為地址的原始請求:例:http://HELLO-SERVICE/hello
   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));
}

這裡的LoadBalancerClient的實現是RibbonLoadBalancerClient,

public <T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException {
    //通過serviceId找到ILoadBalancer 的實現者,預設是ZoneAwareLoadBalancer
  //(RibbonClientConfiguration裡面對ILoadBalancer的實現做的預設配置)
ILoadBalancer loadBalancer = this.getLoadBalancer(serviceId); Server server = this.getServer(loadBalancer); if (server == null) { throw new IllegalStateException("No instances available for " + serviceId); } else {
        //將Server組裝成RibbonServer,附加了一些額外的資訊
        RibbonLoadBalancerClient.RibbonServer ribbonServer = new RibbonLoadBalancerClient.RibbonServer(serviceId, server, this.isSecure(server, serviceId), this.serverIntrospector(serviceId).getMetadata(server));
        return this.execute(serviceId, ribbonServer, request);
    }
}
//ZoneAwareLoadBalancer的Rule規則去選擇Server(這裡的規則是統一個zone的優先選擇,這裡返回的Server
//就是通過serviceId和Rule解析返回的帶有IP:port形式的資訊的Server)
protected Server getServer(ILoadBalancer loadBalancer) {
    return loadBalancer == null ? null : loadBalancer.chooseServer("default");
}

接著往下看:

public <T> T execute(String serviceId, ServiceInstance serviceInstance, LoadBalancerRequest<T> request) throws IOException {
    Server server = null;
    if (serviceInstance instanceof RibbonLoadBalancerClient.RibbonServer) {
        server = ((RibbonLoadBalancerClient.RibbonServer)serviceInstance).getServer();
    }

    if (server == null) {
        throw new IllegalStateException("No instances available for " + serviceId);
    } else {
        RibbonLoadBalancerContext context = this.clientFactory.getLoadBalancerContext(serviceId);
        RibbonStatsRecorder statsRecorder = new RibbonStatsRecorder(context, server);

        try {
           //這一行很關鍵
            T returnVal = request.apply(serviceInstance);
            statsRecorder.recordStats(returnVal);
            return returnVal;
        } catch (IOException var8) {
            statsRecorder.recordStats(var8);
            throw var8;
        } catch (Exception var9) {
            statsRecorder.recordStats(var9);
            ReflectionUtils.rethrowRuntimeException(var9);
            return null;
        }
    }
}
public LoadBalancerRequest<ClientHttpResponse> createRequest(final HttpRequest request,
      final byte[] body, final ClientHttpRequestExecution execution) {
   return new LoadBalancerRequest<ClientHttpResponse>() {

      @Override
 public ClientHttpResponse apply(final ServiceInstance instance)
            throws Exception {
         HttpRequest serviceRequest = new ServiceRequestWrapper(request, instance, loadBalancer);
         if (transformers != null) {
            for (LoadBalancerRequestTransformer transformer : transformers) {
               serviceRequest = transformer.transformRequest(serviceRequest, instance);
            }
         }
         return execution.execute(serviceRequest, body);
      }

   };
}

這裡需要注意ServiceRequestWrapper物件改寫了getURL方法:

@Override
public URI getURI() {
   URI uri = this.loadBalancer.reconstructURI(
         this.instance, getRequest().getURI());
   return uri;
}

所以這裡的reconstructURI實際上呼叫的是實現類RibbonLoadBalancerClient的reconstructURI方法:

public URI reconstructURI(ServiceInstance instance, URI original) {
    Assert.notNull(instance, "instance can not be null");
    String serviceId = instance.getServiceId();
    RibbonLoadBalancerContext context = this.clientFactory.getLoadBalancerContext(serviceId);
    //實際請求的server還是以IP:port的形式
    Server server = new Server(instance.getHost(), instance.getPort());
    IClientConfig clientConfig = this.clientFactory.getClientConfig(serviceId);
    ServerIntrospector serverIntrospector = this.serverIntrospector(serviceId);
    //是否需要轉換為Https
    URI uri = RibbonUtils.updateToHttpsIfNeeded(original, clientConfig, serverIntrospector, server);
 
//  根據Server組裝真正的URI  
    return context.reconstructURIWithServer(server, uri);
   }

execution.execute(serviceRequest, body)方法呼叫的是InterceptingRequestExecution的execute方法:
@Override
public ClientHttpResponse execute(HttpRequest request, byte[] body) throws IOException {
      if (this.iterator.hasNext()) {
         ClientHttpRequestInterceptor nextInterceptor = this.iterator.next();
         return nextInterceptor.intercept(request, body, this);
      }
      else {
         ClientHttpRequest delegate = requestFactory.createRequest(request.getURI(), request.getMethod());
         for (Map.Entry<String, List<String>> entry : request.getHeaders().entrySet()) {
            List<String> values = entry.getValue();
            for (String value : values) {
               delegate.getHeaders().add(entry.getKey(), value);
            }
         }
         if (body.length > 0) {
            StreamUtils.copy(body, delegate.getBody());
         }
      //這就是真正請求外部的地方
         return delegate.execute();
      }
   }
}
這裡的request.getURI()呼叫的就是ServiceRequestWrapper的getURL,也就是RibbonLoadBalancerClient的
reconstructURI方法;
總結:好了,至此springCloud的分析告一段落,這裡也只是給給出了負載均衡的大綱,沒有列出詳細的細節,讀者可根據大綱對照著檢視細節,感謝您的觀看,歡迎指錯;