一.介绍下测试用到的服务
从Eureka注册中心中可以可以看出有EUREKA-CLIENT和RIBBON-CLIENT的服务,其中EUREKA-CLIENT有两个节点作为服务提供者,而RIBBON-CLIENT则是服务消费者,通过RestTemplate来消费EUREKA-CLIENT的服务。
下面代码就是简单实现Ribbon负载均衡的配置类:
@Configuration
public class RibbonConfig {
@Bean
@LoadBalanced
RestTemplate getRestTemlate() {
return new RestTemplate();
}
}
这样简单的通过一个@LoadBalanced注解在RestTemplate上 ,在RestTemplate 远程调用的时候,就会出现负载均衡的效果。
二.一步一步理清Ribbon负载均衡的逻辑
- 首先全局搜索@LoadBalanced这个注解,发现在LoadBalancerAutoConfiguration类有用到该注解:
@Configuration
@ConditionalOnClass(RestTemplate.class)
@ConditionalOnBean(LoadBalancerClient.class)
@EnableConfigurationProperties(LoadBalancerRetryProperties.class)
public class LoadBalancerAutoConfiguration {
/**
* 这段代码的作用是将有用@LoadBalanced注解的RestTemplate注入
*/
@LoadBalanced
@Autowired(required = false)
private List<RestTemplate> restTemplates = Collections.emptyList();
}
分析以上代码:
- 通过@Configuration表明这是一个配置类
- 通过@ConditionalOnClass(RestTemplate.class)可以知道RestTemplate类要在类路径上存在才会实例化LoadBalancerAutoConfiguration
- 通过@ConditionalOnBean(LoadBalancerClient.class)可以知道LoadBalancerClient类要存在才会实例化LoadBalancerAutoConfiguration
- @EnableConfigurationProperties(LoadBalancerRetryProperties.class)是用来使用@ConfigurationProperties注解的类LoadBalancerRetryProperties生效,贴上部分LoadBalancerRetryProperties类的代码,会更清晰:
@ConfigurationProperties("spring.cloud.loadbalancer.retry")
public class LoadBalancerRetryProperties {
private boolean enabled = true;
/**
* Returns true if the load balancer should retry failed requests.
* @return True if the load balancer should retry failed requests; false otherwise.
*/
public boolean isEnabled() {
return this.enabled;
}
- 所以重启下RIBBON-CLIENT服务,Debug继续看LoadBalancerAutoConfiguration 类的代码,发现在启动时会先进入LoadBalancerAutoConfiguration 的loadBalancerRequestFactory方法,实例化出LoadBalancerRequestFactory
@Bean
@ConditionalOnMissingBean
public LoadBalancerRequestFactory loadBalancerRequestFactory(
LoadBalancerClient loadBalancerClient) {
return new LoadBalancerRequestFactory(loadBalancerClient, this.transformers);
}
接下去断点进入LoadBalancerAutoConfiguration 类中的静态内部类LoadBalancerInterceptorConfig的ribbonInterceptor方法,可以看出这是为了实例化出LoadBalancerInterceptor 拦截器
@Configuration
@ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate")
static class LoadBalancerInterceptorConfig {
@Bean
public LoadBalancerInterceptor ribbonInterceptor(
LoadBalancerClient loadBalancerClient,
LoadBalancerRequestFactory requestFactory) {
return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);
}
继续跟断点,进入了loadBalancedRestTemplateInitializerDeprecated方法,可以看出这个方法里主要的逻辑代码是customizer.customize(restTemplate)
@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);
}
}
});
}
继续Debug,断点进入LoadBalancerAutoConfiguration类中的静态内部类LoadBalancerInterceptorConfig:
@Configuration
@ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate")
static class LoadBalancerInterceptorConfig {
@Bean
@ConditionalOnMissingBean
public RestTemplateCustomizer restTemplateCustomizer(
final LoadBalancerInterceptor loadBalancerInterceptor) {
return restTemplate -> {
List<ClientHttpRequestInterceptor> list = new ArrayList<>(
restTemplate.getInterceptors());
list.add(loadBalancerInterceptor);
restTemplate.setInterceptors(list);
};
}
}
通过 list.add(loadBalancerInterceptor)和restTemplate.setInterceptors(list)两段代码可以看出,这是要给restTemplate加上loadBalancerInterceptor拦截器。
那么接下来看看loadBalancerInterceptor拦截器里做了什么,通过页面发起一个http请求,断点进入到LoadBalancerInterceptor类的intercept方法,
@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));
}
截图看下信息:
可以看到该方法取得了request里的url和servicName,然后将这些参数交给loadBalancer.execute去执行方法。而loadBalancer是LoadBalancerClient类的实例。
看下LoadBalancerClient的类图,可以看到LoadBalancerClient继承了ServiceInstanceChooser,LoadBalancerClient的实现类是RibbonLoadBalancerClient
逻辑继续,断点进入了RibbonLoadBalancerClient的execute方法
public <T> T execute(String serviceId, LoadBalancerRequest<T> request, Object hint)
throws IOException {
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);
}
跟着断点一步一步看方法:
-
ILoadBalancer loadBalancer = getLoadBalancer(serviceId);
经过这个方法,得到loadBalancer,从截图里可以看到,loadBalancer里有个allServerList集合,里面有两个对象,端口号分别是8763和8762,这就是我们提供的服务节点。 -
Server server = getServer(loadBalancer, hint)
从图里可以看出,通过这个getServer方法,会返回给我们一个当前可调用的服务节点,而至于怎么返回服务节点,会再写一篇分析,写完后会更新链接到该篇。 -
生成RibbonServer 作为参数传入execute方法
-
运行execute方法
接着跟进execute方法
可以看该方法里的关键执行方法是:
T returnVal = request.apply(serviceInstance);
接着看apply方法,发现它是LoadBalancerRequest接口的方法,该接口却没有具体的实现类:
public interface LoadBalancerRequest<T> {
T apply(ServiceInstance instance) throws Exception;
}
思路回溯,是request对象调用的apply方法,而request其实是execute方法传进来的参数,追溯到源头,发现是LoadBalancerInterceptor类的intercept方法里this.requestFactory.createRequest(request, body, execution)生成了LoadBalancerRequest,然后作为参数传入,之后再调用了apply方法
@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));
}
跟进createRequest方法里:
可以从图中看到,经过一些操作后,生成的serviceRequest对象里的serviceId是eureka-client,也就是我们的服务节点名,而server是localhost:8763,这是具体的服务节点ip,之后作为参数调用org.springframework.http.client包下的InterceptingClientHttpRequest类中的execute方法
断点进入该方法:
可以看出通过requestFactory.createRequest(request.getURI(), method)方法生成了ClientHttpRequest类的实例delegate,它的url就是我们最后真正要请求的,最后正常调用delegate.execute()方法取得返回ClientHttpResponse就好了。
而这里产生了一个疑问,url是怎么产生的?重新发起请求断点试下
发现关键在LoadBalancerRequestFactory类中的createRequest方法中的这句:
HttpRequest serviceRequest = new ServiceRequestWrapper(request, instance,his.loadBalancer);
跟进ServiceRequestWrapper类中,发现它继承了HttpRequestWrapper 类,同时重写了getURI方法
public class ServiceRequestWrapper extends HttpRequestWrapper {
private final ServiceInstance instance;
private final LoadBalancerClient loadBalancer;
public ServiceRequestWrapper(HttpRequest request, ServiceInstance instance,
LoadBalancerClient loadBalancer) {
super(request);
this.instance = instance;
this.loadBalancer = loadBalancer;
}
@Override
public URI getURI() {
URI uri = this.loadBalancer.reconstructURI(this.instance, getRequest().getURI());
return uri;
}
}
断点打在getURI方法里:
可以看到该方法返回了我们最后需要的url。
最后,关于Ribbon是如何通过一个@LoadBalanced注解就实现负载均衡的分析就到这了,还是有很多疏漏的地方,但是大致的逻辑就是这样的了,还有一些更深层的比如如何根据策略选出当前提供服务的节点等,留待后续补充,来日方长~