1、@LoadBalanced
|
2、接下来看下 负载均衡的客户端 LoadBalancerClient
org.springframework.cloud.client.loadbalancer.LoadBalancerClient(接口)
RibbonLoadBalancerClient是其一个实现类
LoadBalancerClient的三个接口:
|
3、接着看RibbonLoadBalancerClient
分析:看源码的目的是为了看ribbon如何实现负载均衡的,就是ribbon如何选择服务端
RibbonLoadBalancerClient重要方法:
|
分析:最终getServer()方法去获取实例,经过源码跟踪,最终交给了ILoadBalancer类去选择服务实例。
总结:虽然Spring Cloud中定义了LoadBalancerClient作为负载均衡的通用接口,并且针对Ribbon实现了RibbonLoadBalancerClient,但是在具体的实现客户端负载均衡时,是通过Ribbon实现的 ILoadBalancer接口实现的。
下面根据ILoadBalancer接口的实现类看看到底是如何实现负载均衡的!
二、IloadBalancer接口
com.netflix.loadbalancer.ILoadBalancer(接口)
|
其中,addServers()方法是添加一个Server集合;chooseServer()方法是根据key去获取Server;markServerDown()方法用来标记某个服务下线;getReachableServers()获取可用的Server集合;getAllServers()获取所有的Server集合。
其实现类:
先看下这些类、接口之间的关系:
分析:上面说到,实现客户端负载均衡最终是通过Ribbon实现的 ILoadBalancer接口实现的。那么实现负载均衡都需要实现哪些工作呢,或者说应该实现哪些工作?
负载均衡无外乎也就是选择一个服务器进行调用(进行通讯),只不过有个服务实例选择的算法。那么通过算法选择服务实例一定可以使用吗?肯定不是!当然最好进行下服务检活的测试。
根据这些想法,实现客户端负载均衡的类应该包括哪些东西呢?应该包含:服务实例的列表,选择算法,服务连通性的检测 等。当然看这个类主要还是关注它是如何实现IloadBalancer定义的方法的!
下面去具体的类中验证。
2.1 AbstractLoadBalancer
com.netflix.loadbalancer.AbstractLoadBalancer, 是IloadBalancer的直接实现类,是个抽象类
2.2 BaseLoadBalancer
com.netflix.loadbalancer.BaseLoadBalancer是IloadBalancer(负载均衡器)的基础实现类,该类中定义了很多关于负载均衡相关的内容。当然看这个类主要还是关注它是如何实现IloadBalancer定义的方法的!
|
BaseLoadBalancer解析:
1、静态内部类SerialPingStrategy判断server是否可以ping通的策略是,顺序遍历server列表,存在问题就是如果IPing速度不理想,或者Server列表比较大,可能会影响性能。这个时候需要重写
IPingStrategy的pingServers(IPing ping, Server[] servers)函数去扩展ping的执行策略
2、BaseLoadBalancer选择服务:chooseServer(Object key)实现
|
说明:发现BaseLoadBalancer选择server是通过IRule来实现的, BaseLoadBalancer使用的默认IRule的策略是RoundRobinRule, 其底层实现了最基本且常用的线性负载均衡规则,即从服务列表中线性的选择服务进行Ping, 返回第一个可用的服务。关于IRule的实现下面介绍。
3、BaseLoadBalancer添加服务:public void addServers(List<Server> newServers);
将向负载均衡器中 新添加的服务实例 添加到原有维护着的 所有服务实例清单allServerList 中。并通过调用setServerList方法新创建的服务列表进行处理,用新的列表覆盖旧的列表。
4、BaseLoadBalancer标记某个服务实例暂停服务:public void markServerDown(Server server);
5、BaseLoadBalancer定时的服务实例的检测
在BaseLoadBalancer的默认构造函数中,会直接启动一个用于定时检查Server是否健康的任务,改任务的默认执行时间间隔为10s
|
疑问:默认的ping是null, 会导致这个pingTask不会执行其中的定时任务。
可以通过指定的方式创建的构造器:
|
总结:至此,我们看到了负载均衡器BaseLoadBalancer,对服务列表的维护,包括列表结构、定时检测服务存活效果等,如何实现接口IloadBalancer中的一些方法(没写出来的方法实现比较简单),至于如何选择服务,或者说是如何根据选择算法选择服务,这部分还要继续追踪IRule里面的实现。
2.3 DynamicServerListLoadBalancer
继承了BaseLoadBalancer类,对基础负载均衡器的扩展,实现了服务实例清单在运行期的动态更新能力;同时,还具备了对服务实例清单的过滤功能,也就是可以通过过滤器来选择性的获取一批服务实例清单。
|
而ServerList接口中的方法:
|
接口ServerList也有多个实现类:
ServerList<T>:
既然要实现服务实例的动态更新,肯定是从Eureka服务中心获取服务实例,就要有Ribbon和Eureka整合的部分,于是正好有个包 ribbon-eureka, 通过探索其中的类,可以发现创建ServerList实例的内容。(往后的内容太复杂了。。。。)
最后会看到,这两个方法是通过DiscoveryEnabledNIWSServerList类一个私有函数obtainServersViaDiscovery通过服务发现机制实习服务实例获取的。
ServerListFilter<T>:
知道了获取服务实例清单,那么它又是如何触发向Eureka Server去获取服务实例清单?如何在获取服务实例清单后更新本地服务实例清单呢?
|
总结:
RestTemplate是如何和Ribbon结合的
最后,回答问题的本质,为什么在RestTemplate加一个@LoadBalance注解就可可以开启负载均衡呢?
@LoadBalanced
RestTemplate restTemplate() {
return new RestTemplate();
}
全局搜索ctr+shift+f @LoadBalanced有哪些类用到了LoadBalanced有哪些类用到了, 发现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();
}
@Bean
public SmartInitializingSingleton loadBalancedRestTemplateInitializer(
final List<RestTemplateCustomizer> customizers) {
return new SmartInitializingSingleton() {
@Override
public void afterSingletonsInstantiated() {
for (RestTemplate restTemplate : LoadBalancerAutoConfiguration.this.restTemplates) {
for (RestTemplateCustomizer customizer : customizers) {
customizer.customize(restTemplate);
}
}
}
};
}
@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);
}
};
}
}
}
在该类中,首先维护了一个被@LoadBalanced修饰的RestTemplate对象的List,在初始化的过程中,通过调用customizer.customize(restTemplate)方法来给RestTemplate增加拦截器LoadBalancerInterceptor。
而LoadBalancerInterceptor,用于实时拦截,在LoadBalancerInterceptor这里实现来负载均衡。LoadBalancerInterceptor的拦截方法如下:
@Override
public ClientHttpResponse intercept(final HttpRequest request, final byte[] body,
final ClientHttpRequestExecution execution) throws IOException {
final URI originalUri = request.getURI();
String serviceName = originalUri.getHost();
Assert.state(serviceName != null, "Request URI does not contain a valid hostname: " + originalUri);
return this.loadBalancer.execute(serviceName, requestFactory.createRequest(request, body, execution));
}
总结
综上所述,Ribbon的负载均衡,主要通过LoadBalancerClient来实现的,而LoadBalancerClient具体交给了ILoadBalancer来处理,ILoadBalancer通过配置IRule、IPing等信息,并向EurekaClient获取注册列表的信息,并默认10秒一次向EurekaClient发送“ping”,进而检查是否更新服务列表,最后,得到注册列表后,ILoadBalancer根据IRule的策略进行负载均衡。
而RestTemplate 被@LoadBalance注解后,能过用负载均衡,主要是维护了一个被@LoadBalance注解的RestTemplate列表,并给列表中的RestTemplate添加拦截器,进而交给负载均衡器去处理。