众所周知,spring-cloud可以使用@LoadBalancer注解实现默认的负载均衡(轮询),而作为一位求知者,我挺好奇spring-cloud底层究竟是如何实现该功能的.在查阅一些源码并研究后,我大致了解了其实现流程:
流程的最开始自然是用户向指定服务发出流程了~~在用户发出请求后,
该请求会被org.springframework.cloud.client.loadbalancer下LoadBalancerAutoConfiguration中定义的LoadBalancerInterceptor拦截器拦截~
LoadBalancerAutoConfiguration类如下图,
其有注解
//RestTemplate在classpath中存在才会托管
@ConditionalOnClass(RestTemplate.class)
//应用程序中存在LoadBalancerClient的bean实例后才会托管
@ConditionalOnBean(LoadBalancerClient.class)
//启用LoadBalancerClientsProperties中属性配置功能
@EnableConfigurationProperties(LoadBalancerClientsProperties.class)
确保只有在负载均衡启用后才会托管此类,
同时开启LoadBalancerClientsProperties.class类的属性配置功能
至于LoadBalancerClientsProperties.class是啥,emmm:
这是一个继承至LoadBalancerProperties的类,会读取spring.cloud.loadbalancer配置的属性~
里面clients存储了为每个客户端配置的负载均衡属性,String类型存储客户端的名称,而LoadBalancerProperties存储该客户端的负载均衡属性~
这段作用为做到能够根据配置文件中的设置对不同的客户端进行个性化LoadBalancer 配置,不过影响并不大,我们继续回到LoadBalancerAutoConfiguration类中~
从上往下查看,易见这段代码
根据返回类型SmartInitializingSingleton可知此方法用于Spring Bean初始化完成后立即执行一些逻辑,这个方法的大意为获取RestTemplateCustomizer对象的List集合,判断其中是否存在RestTemplateCustomizer对象,若存在则调用customizer.customize(restTemplate),将定制化的行为应用于restTemplate对象.而LoadBalancerInterceptor拦截器就可在此处添加至restTemplate上.
而继续往下看,就能发现此段代码:
这是一个静态内部类,会在LoadBalancerAutoConfiguration创建的同时创建,确保了其优先运行~
其中,有这段代码:
@Bean
@ConditionalOnMissingBean
public RestTemplateCustomizer restTemplateCustomizer(final LoadBalancerInterceptor loadBalancerInterceptor) {
return restTemplate -> {
List list = new ArrayList<(restTemplate.getInterceptors());
list.add(loadBalancerInterceptor);
restTemplate.setInterceptors(list);
};
}
这段代码作用是通过restTemplate.getInterceptors()方法获取restTemplate的ClientHttpRequestInterceptor客户端http请求拦截器列表,并把loadBalancerInterceptor拦截器添加至其末尾.
说人话就是将loadBalancerInterceptor拦截器添加至restTemplate上.
而其与前段loadBalancedRestTemplateInitializerDeprecated方法添加loadBalancerInterceptor拦截器的区别在于
loadBalancedRestTemplateInitializerDeprecated是Spring Cloud 中默认实现RestTemplate添加loadBalancerInterceptor的方法,
而restTemplateCustomizer是在应用自定义的restTemplate配置中添加负载均衡拦截器的方式.
前者作用所有RestTemplate,而后者仅作用指定的RestTemplate.
至于这一段:
@Bean
public LoadBalancerInterceptor loadBalancerInterceptor(LoadBalancerClient loadBalancerClient,LoadBalancerRequestFactory requestFactory) {
return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);
}
自然是用于返回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,this.requestFactory.createRequest(request, body, execution));
}
我们所发的请求就是被这里的intercept方法所拦截!并在取出请求中的服务名将其存为serviceName后继续开始下一步!
我们追踪明显是最重点的execute方法,可以来到此处:
这是一个接口,不过该接口只有一个实现方法,就不用用断点跳来跳去了~
找到该接口的实现~:
其中,重点代码为这两段:
其一:
Set supportedLifecycleProcessors = getSupportedLifecycleProcessors(serviceId);
supportedLifecycleProcessors.forEach(lifecycle -> lifecycle.onStart(lbRequest));
第一句代码意思为根据服务名与配置寻找对应的服务实例列表提供者.
我并未指定服务实例列表提供者,故这个方法最终指向默认的服务实例列表提供者,即org.springframework.cloud.loadbalancer.annotation下LoadBalancerClientConfiguration类中ReactiveSupportConfiguration内部类下的discoveryClientServiceInstanceListSupplier方法(有点绕).这个流程可以简单理解为根据请求中的服务名,从ApplicationContext中获取对应的ServiceInstanceListSupplier实例.(PS:ServiceInstanceListSupplier的作用是根据服务名提供对应的服务实例列表)
而ServiceInstanceListSupplier实例也分为很多类型,有提供默认的服务实例列表提供者discoveryClientServiceInstanceListSupplier方法,也有提供基于区域偏好的服务实例列表提供者zonePreferenceDiscoveryClientServiceInstanceListSupplier,亦存在检查服务列表健康的服务实例列表提供者healthCheckDiscoveryClientServiceInstanceListSupplier...这并非本文重点,还请感兴趣的大佬自行查阅相关资料~~
一般来说这种我会追一下实现过程...但这个方法调用实在过于复杂,在spring底层一堆方法间相互调用了十来次才指向目标方法...有兴趣的大佬可以自己追踪一下~
第二句则为:先是对supportedLifecycleProcessors集合中的每个生命周期处理器进行了迭代,再通过onStart方法处理lbRequest,其实现一部分初始化操作.而服务实例列表也存在lbRequest中.
之后让我们把目光回到execute方法,第二段重要代码是在这:
ServiceInstance serviceInstance = choose(serviceId, lbRequest);
其作用为调用负载均衡算法取得服务实例,这个方法的追踪可变上面简单多了,下面我们来追踪choose方法:
这段代码中,重点自然是:
Response loadBalancerResponse = Mono.from(loadBalancer.choose(request)).block();
其余多为做一些检测等等,我们直接继续追踪loadBalancer.choose(request)方法
这是一个接口 ,我们查看里面默认实现的轮询负载均衡算法(随机等负载均衡算法比较类似)
即可来到负载均衡最最核心的一段!!!
其中
ServiceInstanceListSupplier supplier = serviceInstanceListSupplierProvider.getIfAvailable(NoopServiceInstanceListSupplier::new);
代表从serviceInstanceListSupplierProvider获取服务实例列表提供者,如若为空,则返回new NoopServiceInstanceListSupplier();
之后:
return supplier.get(request).next().map(serviceInstances -> processInstanceResponse(supplier, serviceInstances));
循环请求,并将该请求下的服务实例列表提供者与服务实例列表传给processInstanceResponse方法:
processInstanceResponse方法会对服务实例列表提供者做出一些判断,如果为SelectedInstanceCallback且通过getInstanceResponse获取到了服务实例,会将选定的服务实例传递给回调函数,不过这并不是我们重点,我们关注的重点是
Response serviceInstanceResponse = getInstanceResponse(serviceInstances);
中的getInstanceResponse方法~
这里就是负载均衡算法最最最最核心的地方,其功能为从指定服务名的服务实例列表中根据负载均衡算法选择一个指定的服务实例,也是@LoadBalancer实现负载均衡的根本所在!
这段核心代码我自是逐段逐段的讲解!
if (instances.isEmpty()) {
if (log.isWarnEnabled()) {
log.warn("No servers available for service: " + serviceId);
}
return new EmptyResponse();
}
上图此段代码作用为判断服务实例列表是否为空,若为空,则返回new EmptyResponse()~
// Do not move position when there is only 1 instance, especially some suppliers
// have already filtered instances
if (instances.size() == 1) {
return new DefaultResponse(instances.get(0));
}
而上图代码则判定服务实例列表中服务实例数量是否为1,若为1直接返回该服务实例,无需通过负载均衡算法返回(毕竟就一个,随你怎么返回也是这个来着(~ ̄▽ ̄)~)
int pos = this.position.incrementAndGet() & Integer.MAX_VALUE;
ServiceInstance instance = instances.get(pos % instances.size());
return new DefaultResponse(instance);
上图这两句代码则是负载均衡中轮询算法实现的绝对核心代码!!!
参数解释:this.position:随构造器生成的随机数,为AtomicInteger对象
代码解释:incrementAndGet()将this.position加一,接着与Integer.MAX_VALUE按位与,既确保了线程安全,又确保了pos的值限制在一个非负整数范围内。
(说人话就是pos=this.position++的高级版本ヾ(≧▽≦*)o)
之后,pos对instances服务实例列表取余,并根据其值取出对应服务实例
最后返回该服务实例.
而返回的服务实例将转为url代替原来url中的地址~~
至此,整个@LoadBalancer实现负载均衡的流程全部走完!通过拦截请求开始,直到根据某个算法返回对应服务实例结束~~
第一次写源码解析方面博客,没想到一写出来如此多的内容...
我的水平其实不算非常高,源码解析可能存在许多问题,还请大佬多多包容指正.
最后,新人落星,请多关照?ヾ(≧▽≦*)o