1. 程式人生 > >Spring Cloud 客服端負載均衡 Ribbon

Spring Cloud 客服端負載均衡 Ribbon

present cer 內容 uri erl 元數據 creat arraylist dave

一、簡介

Spring Cloud Ribbon 是一個基於Http和TCP的客服端負載均衡工具,它是基於Netflix Ribbon實現的。它不像服務註冊中心、配置中心、API網關那樣獨立部署,但是它幾乎存在於每個微服務的基礎設施中。包括前面的提供的聲明式服務調用也是基於該Ribbon實現的。理解Ribbon對於我們使用Spring Cloud來講非常的重要,因為負載均衡是對系統的高可用、網絡壓力的緩解和處理能力擴容的重要手段之一。在上節的例子中,我們采用了聲明式的方式來實現負載均衡。實際上,內部調用維護了一個RestTemplate對象,該對象會使用Ribbon的自動化配置,[email protected]

/* */Template是Spring自己提供的對象,不是新的內容。讀者不知道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做標記,以使用負載均衡的客戶端(LoadBalancerClient)來配置RestTemplate。接著我們來查看一下LoadBalancerClient這個接口定義:

/**
 * Represents a client side load balancer
 * @author Spencer Gibb
 */
public interface LoadBalancerClient extends ServiceInstanceChooser {

	/**
	 * execute request using a ServiceInstance from the LoadBalancer for the specified
	 * service
	 * @param serviceId the service id to look up the LoadBalancer
	 * @param request allows implementations to execute pre and post actions such as
	 * incrementing metrics
	 * @return the result of the LoadBalancerRequest callback on the selected
	 * ServiceInstance
	 */
	<T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException;

	/**
	 * execute request using a ServiceInstance from the LoadBalancer for the specified
	 * service
	 * @param serviceId the service id to look up the LoadBalancer
	 * @param serviceInstance the service to execute the request to
	 * @param request allows implementations to execute pre and post actions such as
	 * incrementing metrics
	 * @return the result of the LoadBalancerRequest callback on the selected
	 * ServiceInstance
	 */
	<T> T execute(String serviceId, ServiceInstance serviceInstance, LoadBalancerRequest<T> request) throws IOException;

	/**
	 * Create a proper URI with a real host and port for systems to utilize.
	 * Some systems use a URI with the logical serivce name as the host,
	 * such as http://myservice/path/to/service.  This will replace the
	 * service name with the host:port from the ServiceInstance.
	 * @param instance
	 * @param original a URI with the host as a logical service name
	 * @return a reconstructed URI
	 */
	URI reconstructURI(ServiceInstance instance, URI original);
}

  

LoadBalancerClient 是集成 ServiceInstanceChooser,接著我們查看一下該接口定義:
public interface ServiceInstanceChooser {

    /**
     * Choose a ServiceInstance from the LoadBalancer for the specified service
     * @param serviceId the service id to look up the LoadBalancer
     * @return a ServiceInstance that matches the serviceId
     */
    ServiceInstance choose(String serviceId);
}

  從上面的註解中,我們可以知道 choose()方法根據傳入的serviceId服務Id,從負載均衡器選擇一個一個對應的服務實例。execute()方法根據serviceId服務ID和請求request來執行請求內容。reconstructURI()方法構建出一個合適的Host:Port的URI。而 RibbonLoadBalancerClient就是LoadBalancerClient的具體實現。

接著我們查看LoadBalancerClient所在的包,結構如下:

技術分享

  我們發現有一個類我們需要去關註一下:LoadBalancerAutoConfiguration ,從其源碼註解中我們知道是一個為Ribbon 自動化配置類。註釋如下:

/**
 * Auto configuration for Ribbon (client side load balancing).
 *
 * @author Spencer Gibb
 * @author Dave Syer
 * @author Will Tran
 */
@Configuration
@ConditionalOnClass(RestTemplate.class) //條件 : RestTemplate必須在工程的類路徑下
@ConditionalOnBean(LoadBalancerClient.class)  //條件: Spring 容器中必須包含LoadBalancerClient的實現,即RibbonLoadBalancerClient
@EnableConfigurationProperties(LoadBalancerRetryProperties.class) //啟動重試功能,可以spring.cloud.loadbalancer.retry=false,取消重試,默認參數為true
public class LoadBalancerAutoConfiguration {

	@LoadBalanced
	@Autowired(required = false)
	private List<RestTemplate> restTemplates = Collections.emptyList(); //維護一個RestTemplate列表,通過LoadBalanced來註解。

	@Bean
	public SmartInitializingSingleton loadBalancedRestTemplateInitializer( //加載初始話自定義的restTeplate,實質是初始化InterceptingHttpAccessor具體調用
			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);
	}

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

	@Configuration
	@ConditionalOnClass(RetryTemplate.class)
	static class RetryAutoConfiguration {
		@Bean
		public RetryTemplate retryTemplate() {
			RetryTemplate template =  new RetryTemplate();
			template.setThrowLastExceptionOnExhausted(true);
			return template;
		}

		@Bean
		@ConditionalOnMissingBean
		public LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory() {
			return new LoadBalancedRetryPolicyFactory.NeverRetryFactory();
		}

		@Bean
		public RetryLoadBalancerInterceptor ribbonInterceptor(
				LoadBalancerClient loadBalancerClient, LoadBalancerRetryProperties properties,
				LoadBalancedRetryPolicyFactory lbRetryPolicyFactory,
				LoadBalancerRequestFactory requestFactory) {
			return new RetryLoadBalancerInterceptor(loadBalancerClient, retryTemplate(), properties,
					lbRetryPolicyFactory, requestFactory);
		}

		@Bean
		@ConditionalOnMissingBean
		public RestTemplateCustomizer restTemplateCustomizer( //自定義RestTemplate ,實質是初始化InterceptingHttpAccessor
				final RetryLoadBalancerInterceptor loadBalancerInterceptor) {
			return new RestTemplateCustomizer() {
				@Override
				public void customize(RestTemplate restTemplate) {
					List<ClientHttpRequestInterceptor> list = new ArrayList<>(
							restTemplate.getInterceptors());
					list.add(loadBalancerInterceptor);
					restTemplate.setInterceptors(list);
				}
			};
		}
	}
}

  從上面代碼得知,我們需要了解一下LoadBalancerInterceptor,該類用於實現對客戶端發起請求時進行攔截,以實現客戶端的負載均衡。在需要更多的章節才能理清其關系,我還要細細的閱讀,等閱讀差不多在給大家講講。

三、配置

當於Spring Cloud應用引入Ribbon和Eureka的時候,會觸發Eureka中實現的Ribbon的自動化配置。

serverList 的維護機制是由 DiscoveryEnabledNIWSServerList的實例維護,該類會將服務清單列表交給Eureka的服務治理機制來維護。

IPing的實現由 NIWSDiscoveryPing 的實例維護,該類也將服務檢查交給Eureka的服務治理機制來維護。

默認情況下,用於獲取實例請求的ServiceList接口實現是采用Spring Cloud Eureka中封裝的DomainExtractingServerList.由於Spring Cloud Ribbon默認實現了區域親和策略,所以我們可以通過Eureka實例的元數據配置來實現區域化的實例配置方案。

比如 eureka.instance.metadataMap.zone=hangzhou. 通過zone參數來指定自己所在的區域。

在Spring Cloud Ribbon 與 Spring Cloud Eureka結合工程中,我們可以通過參數配置方式來禁用Eureka對Ribbon服務實例的維護實現。

ribbon.eureka.enabled=false

Spring Cloud 客服端負載均衡 Ribbon