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

前提

在閱讀這篇博客以前,但願你對SpringCloud套件熟悉和理解,更但願關注下微服務開發平臺java

概述

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

@LoadBalanced

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

經過源碼能夠發現這是一個LoadBalanced標記註解而且標記了@Qualifier(基於Spring Boot的自動配置機制),咱們能夠溯源到LoadBalancerAutoConfigurationspring

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實例)api

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

關鍵問下本身:爲何?負載均衡

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

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

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);
    }

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

No instances available for  xxxxx

總結

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

這裏this

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

案例

/**
 * 服務註冊中心配置
 *
 * @author <a href="mailto:shangzhi.ibyte@gmail.com">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

相關文章
相關標籤/搜索