阅读源码理解springcloud的@LoadBalanced的原理

我们在使用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实战》

你可能感兴趣的:(java,spring,cloud)