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