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


前提

在閱讀這篇博客之前,希望你對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: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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM