我们在使用RestTemplate类调用其他服务的时候,如果配置了
@Bean @LoadBalanced RestTemplate restTemplate() { return new RestTemplate(); }
那么如果被调用端有多个服务提供,那么自动就有了客户端负载均衡的效果。使得使用RestTemplate的时候就非常的方便,在这里不得不说spring的这些大师真的很厉害。在这里就会想,为什么配置上@LoadBalanced注解就有了这种神奇的效果。于是点看@LoadBalanced的源码,发现这个注解就是一个普通的注解而已,没有什么神奇的地方,但是要注意上面的一句注释
/**
//这里说了RestTemplate bean 被LoadBalancerClient配置
* Annotation to mark a RestTemplate bean to be configured to use a LoadBalancerClient
* @author Spencer Gibb
*/
@Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Qualifier
public @interface LoadBalanced {
}
然后看注解上说的LoadBalancerClient的源码,发现这只是1个接口,这个接口在spring-cloud-commons的jar包里面,由于是个接口,说明这只是为了定义一种能力,源码上的注解说是Represents a client side load balancer,也就是提供一种客户端负载均衡的能力,这个能力还需要依赖具体的实现类去实现。然后发现这个类有唯一的一个实现类是RibbonLoadBalancerClient,在spring-cloud-netflix-core这个jar里面,说明springcloud官方为了接入netflix的ribbon组件,专门写了RibbonLoadBalancerClient这个实现类,假设哪天netflix不维护ribbon或者有了更好的负载均衡组件出现,springcloud官方再写一个XXXLoadBalancerClient来实现LoadBalancerClient接口就能对接就新的组件。而RibbonLoadBalancerClient可以找到是在RibbonAutoConfiguration类注入到spring容器中的,代码如下
//LoadBalancerClient是个接口,@ConditionalOnMissingBean效果就是如果接口没有实现类,那么就是往spring容器中注入一个叫做RibbonLoadBalancerClient的接口实现类。
@Bean
@ConditionalOnMissingBean(LoadBalancerClient.class)
public LoadBalancerClient loadBalancerClient() {
return new RibbonLoadBalancerClient(springClientFactory());
}
到目前为止,LoadBalancerClient的实现类已经注入到spring容器中了,就看哪里会使用到了,接下来就不太好找线索了,只能用IDE工具搜索在哪里使用到了LoadBalancerClient这个类,一般思路就是在config类中,应该在初始化某些其他类的时候需要使用到,这个时候可以搜索到在LoadBalancerClient相同目录下的LoadBalancerAutoConfiguration类中使用到了,而这个类就是关键所在。直接看这个类的源码
@Configuration
//这里在没有RetryTemplate的时候才会使用这个配置类,估计是要使用重试功能就不会使用此配置
@ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate")
static class LoadBalancerInterceptorConfig {
//这个方法的参数有LoadBalancerClient,说明spring容器中的LoadBalancerClient实现类在这里就会真正用起来,最终由注入了一个LoadBalancerInterceptor的实例到容器中,看名字是一种拦截器,注入进去干什么呢,我们先不管,继续往下看
@Bean
public LoadBalancerInterceptor ribbonInterceptor(
LoadBalancerClient loadBalancerClient,
LoadBalancerRequestFactory requestFactory) {
return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);
}
//这个方法的参数就是上面的拦截器,说明上面的拦截器在这里使用,最终生成了一个RestTemplateCustomizer(中文含义就是RestTemplate的定制化器)的匿名内部类对象实例到spring容器中
@Bean
@ConditionalOnMissingBean
public RestTemplateCustomizer restTemplateCustomizer(
final LoadBalancerInterceptor loadBalancerInterceptor) {
return new RestTemplateCustomizer() {
//当调用这个对象实例的customize()方法的时候,就会把传入的restTemplate中的拦截器Interceptors集合中,再放入一个LoadBalancerInterceptor拦截器,这就是上面往spring容器中注入一个拦截器的作用
@Override
public void customize(RestTemplate restTemplate) {
List list = new ArrayList<>(
restTemplate.getInterceptors());
list.add(loadBalancerInterceptor);
restTemplate.setInterceptors(list);
}
};
}
}
restTemplate内部就提供了拦截器这种机制,来可以对restTemplate的一些行为做干预。说明很有可能就是拦截器起到了客户端负载均衡的效果。我们先不看拦截器到底做了什么事,我们先看怎么才能把项目中所有需要进行客户端负载均衡的restTemplate的拦截器中加入一个loadBalancerInterceptor。
继续看LoadBalancerAutoConfiguration上面部分的源码
//@Autowired放到集合上含义就是获取到所有配置了@LoadBalanced的RestTemplate对象实例。
@LoadBalanced
@Autowired(required = false)
private List restTemplates = Collections.emptyList();
然后接下来
//SmartInitializingSingleton就是当所有的singleton的bean都初始化完了之后才会回调这个接口。不过要注意是 4.1 之后才出现的接口。
//效果就是把获得了所有的RestTemplate的定制化器集合customizers,设置到上一步的restTemplates集合中的每个RestTemplate里面
@Bean
public SmartInitializingSingleton loadBalancedRestTemplateInitializer(
final List customizers) {
return new SmartInitializingSingleton() {
@Override
public void afterSingletonsInstantiated() {
for (RestTemplate restTemplate : LoadBalancerAutoConfiguration.this.restTemplates) {
for (RestTemplateCustomizer customizer : customizers) {
customizer.customize(restTemplate);
}
}
}
};
}
到这一步,工程中所有的加了@LoadBalanced的RestTemplate里面的拦截器都加上了LoadBalancerInterceptor。
最后,我们应该看看这个拦截器LoadBalancerInterceptor到底有什么用途了
@Override
public ClientHttpResponse intercept(final HttpRequest request, final byte[] body,
final ClientHttpRequestExecution execution) throws IOException {
//获取请求的uri
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));
}
接着看上面loadBalancer.execute()方法源码
@Override
public T execute(String serviceId, LoadBalancerRequest request) throws IOException {
ILoadBalancer loadBalancer = getLoadBalancer(serviceId);
//这里就是关键了
Server server = getServer(loadBalancer);
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);
}
getServer(loadBalancer)就是获取服务的地方,因为客户端负载均衡有轮询,根据权重等,那么这里肯定要依据其中某种规则获取到具体服务提供方的ip地址和端口。继续看这个方法的源码
protected Server getServer(ILoadBalancer loadBalancer) {
if (loadBalancer == null) {
return null;
}
return loadBalancer.chooseServer("default"); // TODO: better handling of key
}
//再看chooseServer,这里看的是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.choose(key)的rule就是netflix公司维护的ribbon这个开源组件包里面的类了,这样springcloud和netflix的ribbon就结合起来,接下来有兴趣可以继续研究ribbon的源码。
总结:看这些源码可以学习到springcloud是通过哪些巧妙的设计和其他公司的组件有机的结合到一起的,以及springboot一些注解的妙用。
参考资料:
《重新定义springcloud实战》