目录
一、前言
二、Ribbon实现负载均衡的原理
1、流程图
2、LoadBalancerInterceptor组件intercept()拦截逻辑
2.1、intercept()逻辑
2.2、拦截器机制怎么触发的,怎么来的?
3、RibbonLoadBalancerClient负载均衡器客户端
3.1、execute(String serviceId, LoadBalancerRequest request, Object hint)逻辑
3.2、getLoadBalancer(serviceId)获取ILoadBalancer
3.3、getInstance(String name, Class type)
3.4、NamedContextFactory组件
3.5、从Spring上下文获取ILoadBalancer类型实例
3.6、为什么是ZoneAwareLoadBalancer
4、获取服务
4.1、chooseServer()逻辑
4.2、BaseLoadBalancer组件
4.3、?PredicateBasedRule的choose()逻辑
一、前言
? ? 2.x.x版本的SpringCloud项目使用的是Ribbon实现负载均衡,即使是OpenFeign也不例外,故本篇博客采用的是2.2.x版本的SpringCloud项目进行讲解。Ribbon实现负载均衡的案例在我的微服务专栏已经给出来了,感兴趣的读者可以去探究探究,然后本篇博客就是围绕它来进行展开。
二、Ribbon实现负载均衡的原理
1、流程图

?里面还涉及到SpringBoot的自动装配,下面有分析到。
2、LoadBalancerInterceptor组件intercept()拦截逻辑
2.1、intercept()逻辑
@Override
public ClientHttpResponse intercept(final HttpRequest request, final byte[] body,
final ClientHttpRequestExecution execution) throws IOException {
// 获取请求URI
final URI originalUri = request.getURI();
// 从URI获取服务名
String serviceName = originalUri.getHost();
Assert.state(serviceName != null,
"Request URI does not contain a valid hostname: " + originalUri);
// 调用负载均衡器客户端的execute()方法处理请求
return this.loadBalancer.execute(serviceName,
this.requestFactory.createRequest(request, body, execution));
}

主要逻辑:
- 根据request入参,获取请求URI
- 从URI获取到要调用的服务名
- 调用LoadBalancerClient负载均衡客户端的execute()方法,完成请求处理。
2.2、拦截器机制怎么触发的,怎么来的?
先看下LoadBalancerInterceptor的类图。

?其次你要调用一个类的方法,首先对其进行实例化。那么这些都是Spring的项目,会不会在Spring容器初始化时就默认初始化了呢?回顾一下我们之前讲得的流程,有没有哪一点让你过目不忘。
1)LoadBalancerAutoConfiguration阻塞客户端负载均衡的自动配置。
如果你不知道它在哪个项目,可以点击它的包名找到对应的jar包或项目


?一看到META-INF就情不自禁地点击进去,发现了SpringBoot的一个重要注解(EnableAutoConfiguration),具体的前面已经分析了。点击LoadBalancerAutoConfiguration进去看看。
1.1)LoadBalancerAutoConfiguration
@Configuration(proxyBeanMethods = false)
@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();
@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);
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate")
static class LoadBalancerInterceptorConfig {
/**
* 拦截器装配
*
* @param loadBalancerClient loadBalancerClient
* @param requestFactory requestFactory
* @return LoadBalancerInterceptor
*/
@Bean
public LoadBalancerInterceptor loadBalancerInterceptor(
LoadBalancerClient loadBalancerClient,
LoadBalancerRequestFactory requestFactory) {
return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);
}
@Bean
@ConditionalOnMissingBean
public RestTemplateCustomizer restTemplateCustomizer(
final LoadBalancerInterceptor loadBalancerInterceptor) {
return restTemplate -> {
List<ClientHttpRequestInterceptor> list = new ArrayList<>(
restTemplate.getInterceptors());
list.add(loadBalancerInterceptor);
// 为restTemplate设置拦截器
restTemplate.setInterceptors(list);
};
}
}
/**
* Auto configuration for retry mechanism.
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RetryTemplate.class)
public static class RetryAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public LoadBalancedRetryFactory loadBalancedRetryFactory() {
return new LoadBalancedRetryFactory() {
};
}
}
/**
* Auto configuration for retry intercepting mechanism.
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RetryTemplate.class)
public static class RetryInterceptorAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public RetryLoadBalancerInterceptor loadBalancerRetryInterceptor(
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进行了装配,并且给RestTemplete设置了拦截器。SpringBoot在在装配LoadBalancerInterceptor过程中,将已经装配的LoadBalancerClient(RibbonLoadBalancerClient)传递进来,在LoadBalancerInterceptor构造方法中初始化loadBalancer字段。当然自动装配还做了如重试等Bean的装配。这也就是为什么入口就是LoadBalancerInterceptor的原因。
2)LoadBalancerClient在哪里装配的,为什么是RibbonLoadBalancerClient呢
不知道在哪里情况下,可以先Debugger进来,再点击其包名看看在哪个项目里面

可见它在spring-cloud-netflix-ribbon项目里面,然后找找有没有META-INF

?可见是有的,我们在里面找找看

注意:RibbonAutoConfiguration是在装配LoadBalancerAutoConfiguration之前执行装配的

?可见这里装配的是RibbonLoadBalancerClient,所以为什么我们会进入到RibbonLoadBalancerClient的execute()逻辑。下面对其展开探究讲解:
3、RibbonLoadBalancerClient负载均衡器客户端
3.1、execute(String serviceId, LoadBalancerRequest<T> request, Object hint)逻辑
public <T> T execute(String serviceId, LoadBalancerRequest<T> request, Object hint)
throws IOException {
// 根据服务名获取负载均衡器,Dy
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);
}

?主要逻辑:
- 根据服务名,获取Spring上下文,然后从上下文获取负载均衡器实例。
- 根据负载均衡器以及里面设置的规则,从服务列表里面选择一个可用服务。
- 封装数据,执行重载execute()方法完成请求。
3.2、getLoadBalancer(serviceId)获取ILoadBalancer

将请求委托SpringClientFactory的getLoadBalancer()方法处理,其内部调用?getInstance()处理。
3.3、getInstance(String name, Class<C> type)
@Override
public <C> C getInstance(String name, Class<C> type) {
// 调用父类获取实例
C instance = super.getInstance(name, type);
if (instance != null) {
return instance;
}
// 调用父类没有获取到实例,更改type重新调用
IClientConfig config = getInstance(name, IClientConfig.class);
return instantiateWithConfig(getContext(name), type, config);
}
一看到super就看看类的父子关系图

?SpringClientFactory继承自NamedContextFactory,所以super.getInstance(name, type) 方法调用的是NamedContextFactory的getInstance()方法:
注意:SpringClientFactory是spring-cloud-netflix项目里面的、NamedContextFactory是spring-cloud-commons里面的
3.4、NamedContextFactory组件

?因为this是SpringClientFactory的引用,故会调用SpringClientFactory的getContext()方法
?SpringClientFactory通过super调用父类的getContext()回到NamedContextFactory继续处理,里面会判断上下文是否已经存在指定key对应的context,如果没有则创建一个再存放到context(Map<String, AnnotationConfigApplicationContext>类型),然后从缓存map获取。?
?createContext(name)返回创建的context,然后存放到context字段里面去。

获取Spring上下文后,接着执行BeanFactoryUtils.beansOfTypeIncludingAncestors(context, type);逻,从上下文获取ILoadBalancer类型实例。
3.5、从Spring上下文获取ILoadBalancer类型实例

?可见它上下文是ListableBeanFactory类型的,不难发现它的实现类一般用的是DefaultListableBeanFactory(Bean定义注册机)

?可以看到ribbonLoadBalancer为ZoneAwareLoadBalancer,那么为什么是它,下面的3.6步骤具体分析。

从DefaultListableBeanFactory获取Bean的信息,回到NamedContextFactory的getInstances(),调用AbstractApplicationContext的getBean(),调用DefaultListableBeanFactory的getBean()、resolveBean()、resolveNamedBean()、getBean(beanName, null, args),然后
调用AbstractBeanFactory的getBean()方法,其内部又会调用doGetBean()方法从DefaultSingletonBeanRegistry默认单例Bean注册机缓存获取,没有则新建。这些逻辑在之前就讲解过了,具体的不再过多赘述。
3.6、为什么是ZoneAwareLoadBalancer

SpringBoot在自动装配的过程中,根据RibbonClientConfiguration中的配置默认装配ZoneAwareLoadBalancer的。ZoneAwareLoadBalancer是ribbon项目里面的,RibbonClientConfiguration是spring-cloud-netflix-ribbon里面的。
?看一下它的父子关系类图:

4、获取服务

获取负载均衡器后,根据负载均衡器去获取可用的服务。
4.1、chooseServer()逻辑


?根据上面获取到的负载均衡器是ZoneAwareLoadBalancer,故会进入到ZoneAwareLoadBalancer的chooseServer()处理,一般会委托它的父类BaseLoadBalancer去选择服务。
4.2、BaseLoadBalancer组件

?它选择服务的逻辑委托给了PredicateBasedRule处理
4.3、?PredicateBasedRule的choose()逻辑

在选择一个可调用服务前,先获取所有服务getAllServers()
?BaseLoadBalancer组件获取所有服务

public Optional<Server> chooseRoundRobinAfterFiltering(List<Server> servers, Object loadBalancerKey) {
// 从服务器列表中获取根据此谓词筛选服务器。
List<Server> eligible = getEligibleServers(servers, loadBalancerKey);
if (eligible.size() == 0) {
return Optional.absent();
}
// 计算索引,从eligible获取一个服务
return Optional.of(eligible.get(incrementAndGetModulo(eligible.size())));
}
选择一个服务
private int incrementAndGetModulo(int modulo) {
// 死循环直到获取到一个索引下标
for (;;) {
// 获取当前AtomicInteger类型变量的原子值
int current = nextIndex.get();
// 当前原子值 + 1 然后对 服务实例个数取余
int next = (current + 1) % modulo;
// CAS修改AtomicInteger类型变量,CAS成功并且current小于服务个数返回current,否则重试
if (nextIndex.compareAndSet(current, next) && current < modulo)
return current;
}
}
以循环方式选择服务器,这里受CAS思想以及RoundRobinRule规则启发而写的算法,其在是妙。
|