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