以下源码分析只针对:
<spring.cloud.version>Finchley.SR4</spring.cloud.version>
<spring.boot.version>2.0.9.RELEASE</spring.boot.version>
Ribbon是一个基于HTTP和TCP的客户端负载均衡工具,它基于Netflix Ribbon实现。通过Spring Cloud的封装,可以让我们轻松地将面向服务的REST模版请求自动转换成客户端负载均衡的服务调用。Spring Cloud Ribbon虽然只是一个工具类框架,它不像服务注册中心、配置中心、API网关那样需要独立部署,但是它几乎存在于每一个Spring Cloud构建的微服务和基础设施中。因为微服务间的调用,API网关的请求转发等内容,实际上都是通过Ribbon来实现的,包括后续我们将要介绍的Feign,它也是基于Ribbon实现的工具。
Ribbon的负载均衡的引入是从以下代码开始的:
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
那我们从这个@LoadBalanced注解开始,打开它的源码:
/**
* Annotation to mark a RestTemplate bean to be configured to use a LoadBalancerClient.
标记RestTemplate的注解以使用LoadBalancerClient对象.
* @author Spencer Gibb
*/
@Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Qualifier
public @interface LoadBalanced {
}
从注释中可以知道,这个注解是用来给RestTemplate做标记,以使用负载均衡客户端(LoadBalancerClient)来配置它。所以,我们在生成的RestTemplate的bean上添加这么一个注解,这个bean就会配置LoadBalancerClient。
接下来再查看 LoadBalancerClient 源码,它位于 spring-cloud-commons-xxx.jar 中的org.springframework.cloud.client.loadbalancer包下.
public interface LoadBalancerClient extends ServiceInstanceChooser {
<T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException;
<T> T execute(String serviceId, ServiceInstance serviceInstance,
LoadBalancerRequest<T> request) throws IOException;
URI reconstructURI(ServiceInstance instance, URI original);
}
LoadBalancerClient是一个接口,里面有三个方法(包括从 ServiceInstanceChooser中继承的choose() 方法) :
- ServiceInstance choose(String serviceId);从方法名上就可以看出,是根据传入的serviceId(服务名),从负载均衡器中选择一个服务实例ServiceInstance类 , 这个方法是继承自它的父接口 ServiceInstanceChooser接口的.
- execute方法,使用从负载均衡器中选择的服务实例来执行请求内容。
- URI reconstructURI(ServiceInstance instance, URI original);方法,是重新构建一个URI的,还记得我们在代码中,通过RestTemplate请求服务时,写的是服务名(restTemplate.getForObject("http://MICROSERVICE-PROVIDER-PRODUCT/product/get/"+id, Product.class); )吧,这个方法就会把这个请求的URI进行转换,将服务名替换成这个服务下的一个实例名,返回 实例host+port,通过 实例host+port的形式去请求服务实例 .
这个接口的层次关系为:
LoadBalancerClient的在Ribbon中的实现类为RibbonLoadBalancerClient,这个类是非常重要的一个类,最终的负载均衡的请求处理,由它来执行。它位于: 请求的负载均衡在execute中实现:
public <T> T execute(String serviceId, LoadBalancerRequest<T> request, Object hint)
throws IOException {
//每次发送请求都会获取一个ILoadBalancer ,会涉及负载均衡(IRULS),服务器列表集群(ServerList) 和检验服务是否存活(IPing)等细节实现
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);
}
追踪 getLoadBalancer() 可知它会得到一个 ILoadBalancer对象.
而 getServer() 则直接调用了ILoadBalancer 的chooseServer方法来使用负载均衡策略,从已知的服务列表中选出一个服务器实例 .
protected Server getServer(ILoadBalancer loadBalancer) {
if (loadBalancer == null) {
return null;
}
return loadBalancer.chooseServer("default");
}
那么 ILoadBalancer 是如何实现的呢? 我们可以看一个它的实现类 ZoneAwareLoadBalancer 类(它也是默认采用的负载均衡策略,它是一个基于区域感知的轮询策略),注意它的构造方法:
public ZoneAwareLoadBalancer(IClientConfig clientConfig, IRule rule,
IPing ping, ServerList<T> serverList, ServerListFilter<T> filter,
ServerListUpdater serverListUpdater) {
super(clientConfig, rule, ping, serverList, filter, serverListUpdater);
}
它的参数分析:
IClientConfig client的配置类,具体指的DefaultClientConfigImpl
IRule 负载均衡的策略类,默认轮询 RoundRobinRule
IPing 服务可用性检查,默认DummyPing
ServerList 服务列表获取,ConfigurationBasedServerList
ServerListFilter 服务列表过滤 ZonePreferenceServerListFilter
ZoneAwareLoadBalancer其中一个重要的方法就是com.netflix.loadbalancer.ZoneAwareLoadBalancer#chooseServer.
@Override
public Server chooseServer(Object key) {
//只有当负载均衡器中维护的实例所属的Zone区域的个数大于1的时候才会执行选择策略, 否则还是使用父类的实现.
if (!ENABLED.get() || getLoadBalancerStats().getAvailableZones().size() <= 1) {
logger.debug("Zone aware logic disabled or there is only one zone");
return super.chooseServer(key);
}
Server server = null;
try {
//获取当前有关负载均衡的服务器状态集合
LoadBalancerStats lbStats = getLoadBalancerStats();
//为当前负载均衡器中的所有Zone区域分别创建快照,保存在zoneSnapshot中,这些快照中的数据用于后续的算法
Map<String, ZoneSnapshot> zoneSnapshot = ZoneAvoidanceRule.createSnapshot(lbStats);
logger.debug("Zone snapshots: {}", zoneSnapshot);
//获取平均负载阈值
if (triggeringLoad == null) {
triggeringLoad = DynamicPropertyFactory.getInstance().getDoubleProperty(
"ZoneAwareNIWSDiscoveryLoadBalancer." + this.getName() + ".triggeringLoadPerServerThreshold", 0.2d);
}
//获取平均实例故障率阈值
if (triggeringBlackoutPercentage == null) {
triggeringBlackoutPercentage = DynamicPropertyFactory.getInstance().getDoubleProperty(
"ZoneAwareNIWSDiscoveryLoadBalancer." + this.getName() + ".avoidZoneWithBlackoutPercetage", 0.99999d);
}
//根据两个阈值来获得可用Zone区域的集合,getAvailableZones会通过zoneSnapshot实现可用区域挑选
Set<String> availableZones = ZoneAvoidanceRule.getAvailableZones(zoneSnapshot, triggeringLoad.get(), triggeringBlackoutPercentage.get());
logger.debug("Available zones: {}", availableZones);
if (availableZones != null && availableZones.size() < zoneSnapshot.keySet().size()) {
//随机从可用服务区列表中选择一个服务器
String zone = ZoneAvoidanceRule.randomChooseZone(zoneSnapshot, availableZones);
logger.debug("Zone chosen: {}", zone);
if (zone != null) {
//获得对应区域的负载均衡器
BaseLoadBalancer zoneLoadBalancer = getLoadBalancer(zone);
//选择具体的服务实例
//在chooseServer中将会使用IRule接口的choose函数来选择具体服务实例。在这里,IRule接口的实现会实现ZoneAvoidanceRule来挑选具体的服务实例。
server = zoneLoadBalancer.chooseServer(key);
}
}
} catch (Exception e) {
logger.error("Error choosing server using zone aware logic for load balancer={}", name, e);
}
if (server != null) {
return server;
} else {
logger.debug("Zone avoidance logic is not invoked.");
return super.chooseServer(key); //这里调用的是父类com.netflix.loadbalancer.BaseLoadBalancer的chooseServer方法
}
}
com.netflix.loadbalancer.BaseLoadBalancer的chooseServer方法分析:
public Server chooseServer(Object key) {
if (counter == null) {
counter = createCounter();
}
counter.increment();
if (rule == null) {
return null;
} else {
try {
//根据具体的路由算法获取服务
return rule.choose(key);
} catch (Exception e) {
logger.warn("LoadBalancer [{}]: Error choosing server for key {}", name, key, e);
return null;
}
}
}
那么这个 rule 又是什么呢,追踪一下源码可知:
protected IRule rule = DEFAULT_RULE;
//而 DEFAULT_RULE 又是
private final static IRule DEFAULT_RULE = new RoundRobinRule();
这就可知系统默认的负载均衡策略是 RoundRobin . 具体的负载均衡算法的实现,请看: Ribbon(五) Ribbon源码分析 - IRULE负载均衡的实现
===================================================================================
让我们再回到前面LoadBalancerClient类. 在LoadBalancerClient接口的同一个包路径下,还会看到另一个LoadBalancerAutoConfiguration类,看名字就感觉这是一个自动配置LoadBalancer的. 它的源码如下:
@Configuration
@ConditionalOnClass(RestTemplate.class)
@ConditionalOnBean(LoadBalancerClient.class)
@EnableConfigurationProperties(LoadBalancerRetryProperties.class)
public class LoadBalancerAutoConfiguration {
@LoadBalanced
@Autowired(required = false)
private List<RestTemplate> restTemplates = Collections.emptyList();
@Autowired(required = false)
private List<LoadBalancerRequestTransformer> transformers = Collections.emptyList();
....
}
整理这个LoadBalancerAutoConfiguration可以得到如下关系:
详细分析一下LoadBalancerAutoConfiguration可知,Ribbon实现负载均衡自动化配置需要满足下面两个条件:
- @ConditionalOnClass(RestTemplate.class):RestTemplate必须存在于当前工程的环境中。
- @ConditionalOnBean(LoadBalancerClient.class):在Spring的Bean工程中必须有LoadBalancerClient的实现bean。
LoadBalancerAutoConfiguration类的其它代码(主要看@Bean修饰的代码,可以看出它还主要做了下面三件事:
/**
* 创建SmartInitializingSingleton接口的实现类。Spring会在所有
* 单例Bean初始化完成后回调该实现类的afterSingletonsInstantiated()
* 方法。在这个方法中会为所有被@LoadBalanced注解标识的
* RestTemplate添加ribbon的自定义拦截器LoadBalancerInterceptor。
*/
@Bean
public SmartInitializingSingleton loadBalancedRestTemplateInitializerDeprecated(
final ObjectProvider<List<RestTemplateCustomizer>> restTemplateCustomizers) {
return () -> restTemplateCustomizers.ifAvailable(customizers -> {
for (RestTemplate restTemplate : LoadBalancerAutoConfiguration.this.restTemplates) {
for (RestTemplateCustomizer customizer : customizers) {
customizer.customize(restTemplate);
}
}
});
}
@Bean
@ConditionalOnMissingBean
public LoadBalancerRequestFactory loadBalancerRequestFactory(
LoadBalancerClient loadBalancerClient) {
return new LoadBalancerRequestFactory(loadBalancerClient, this.transformers);
}
/**
* 创建Ribbon自定义拦截器LoadBalancerInterceptor
* 创建前提是当前classpath下不存在spring-retry。
* 所以LoadBalancerInterceptor是默认的Ribbon拦截
* 请求的拦截器。
*/
@Configuration
@ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate")
static class LoadBalancerInterceptorConfig {
@Bean
public LoadBalancerInterceptor ribbonInterceptor(
LoadBalancerClient loadBalancerClient,
LoadBalancerRequestFactory requestFactory) {
return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);
}
/**
* 添加拦截器具体方法。首先获取当前拦截器集合(List)
* 然后将loadBalancerInterceptor添加到当前集合中
* 最后将新的集合放回到restTemplate中。
*/
@Bean
@ConditionalOnMissingBean
public RestTemplateCustomizer restTemplateCustomizer(
final LoadBalancerInterceptor loadBalancerInterceptor) {
return restTemplate -> {
List<ClientHttpRequestInterceptor> list = new ArrayList<>(
restTemplate.getInterceptors());
list.add(loadBalancerInterceptor);
restTemplate.setInterceptors(list);
};
}
}
/**
* Auto configuration for retry mechanism.
*/
@Configuration
@ConditionalOnClass(RetryTemplate.class)
public static class RetryAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public LoadBalancedRetryFactory loadBalancedRetryFactory() {
return new LoadBalancedRetryFactory() {
};
}
}
/**
* Auto configuration for retry intercepting mechanism.
*/
@Configuration
@ConditionalOnClass(RetryTemplate.class)
public static class RetryInterceptorAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public RetryLoadBalancerInterceptor ribbonInterceptor(
LoadBalancerClient loadBalancerClient,
LoadBalancerRetryProperties properties,
LoadBalancerRequestFactory requestFactory,
LoadBalancedRetryFactory loadBalancedRetryFactory) {
return new RetryLoadBalancerInterceptor(loadBalancerClient, properties,
requestFactory, loadBalancedRetryFactory);
}
@Bean
@ConditionalOnMissingBean
public RestTemplateCustomizer restTemplateCustomizer(
final RetryLoadBalancerInterceptor loadBalancerInterceptor) {
return restTemplate -> {
List<ClientHttpRequestInterceptor> list = new ArrayList<>(
restTemplate.getInterceptors());
list.add(loadBalancerInterceptor);
restTemplate.setInterceptors(list);
};
}
}
- 创建了一个LoadBalancerInterceptor的Bean,用于实现对客户端发起请求时进行拦截,以实现客户端负载均衡。
- 创建了一个RestTemplateCustomizer的Bean,用于给RestTemplate增加LoadbalancerInterceptor
- 维护了一个被@LoadBalanced注解修饰的RestTemplate对象列表,并在这里进行初始化,通过调用RestTemplateCustomizer的实例来给需要客户端负载均衡的RestTemplate增加LoadBalancerInterceptor拦截器。
小结: 回头看一下我们自己实现的项目,就定义了RestTemplate的一个对象,并且引入了spring-cloud相关的包,存在RibbonLoadBalancerClient作为LoadBalancerClient的实现类,所以,满足自动化配置的条件。
=================================================================================================