Ribbon 源码解读
Ribbon 是spring cloud 生态中的客户端负载均衡工具,今天让我们一起走进Ribbon的源码世界。
对于ribbon我们平时的使用时这样的
@Bean
@LoadBalanced //添加了@LoadBalanced注解就可以使用Ribbon的负载均衡功能了
public RestTemplate restTemplate() {
return new RestTemplate();
}
看看@LoadBalanced注解内部
跟普通的注解也没有什么两样,只是多了一个@Qualifier;那我们就继续找到该注解的配置类中去。(Spring一般在同一包下面,带有autoConfiguration后缀的是配置类)
看下LoadBalancerAutoConfiguration
内部实现
public class LoadBalancerAutoConfiguration {
//查询容器中所有的带有@LoadBalanced注解的RestTemplate的,注入到restTemplates中
@LoadBalanced
@Autowired(required = false)
private List<RestTemplate> restTemplates = Collections.emptyList();
@Autowired(required = false)
private List<LoadBalancerRequestTransformer> transformers = Collections.emptyList();
//声明一个SmartInitializingSingleton ,
//这个bean的作用就是将我们容器中所有的restTemplateCustomizers拿出来执行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);
}
}
});
}
//当容器中没有时,加载bean
//@Bean注解需要的参数,参数会根据容器中bean的类型进行自动注入
@Bean
@ConditionalOnMissingBean
public LoadBalancerRequestFactory loadBalancerRequestFactory(
LoadBalancerClient loadBalancerClient) {
return new LoadBalancerRequestFactory(loadBalancerClient, this.transformers);
}
//负载均衡拦截器配置类
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate")
static class LoadBalancerInterceptorConfig {
//配置LoadBalancedInterceptor,ribbon的功能正式因为注入了这个拦截器才会实现
@Bean
public LoadBalancerInterceptor ribbonInterceptor(
LoadBalancerClient loadBalancerClient,
LoadBalancerRequestFactory requestFactory) {
return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);
}
//restTemplate定制化器。将拦截器设置到restTemplate中
@Bean
@ConditionalOnMissingBean
public RestTemplateCustomizer restTemplateCustomizer(
final LoadBalancerInterceptor loadBalancerInterceptor) {
return restTemplate -> {
List<ClientHttpRequestInterceptor> list = new ArrayList<>(
restTemplate.getInterceptors());
list.add(loadBalancerInterceptor);
restTemplate.setInterceptors(list);
};
}
}
}
那我们就一起来看拦截器的实现。当我们new一个LoadBalancedInteceptor时需要传入一个参数LoadBalancerInterceptor(loadBalancerClient, requestFactory);
先记住这个参数,我们后面会用到。
着重看拦截器的intercept()方法
//调用loadBalancer(LoadBalancerClient)的execute方法
this.loadBalancer.execute(serviceName,
this.requestFactory.createRequest(request, body, execution));
loadBalancedClient接口定义的方法如下
public interface LoadBalancerClient extends ServiceInstanceChooser {
//通过serviceId 去执行任务(调用具体的提供服务的接口)
<T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException;
//通过serviceId和servieInstance去执行任务,
<T> T execute(String serviceId, ServiceInstance serviceInstance,
LoadBalancerRequest<T> request) throws IOException;
//改造url方法,通过serviceName获取到真实的服务器的ip:port,然后替换之
URI reconstructURI(ServiceInstance instance, URI original);
}
当然,LoadBalancedClient
也是由Spring自动注入的,那么这个LoadBalancedClient
具体实现是什么呢?是在哪里加载的呢?
org.springframework.cloud.netflix.ribbon.RibbonAutoConfiguration#loadBalancerClient()
方法为我们的容器注入了一个LoadBalancerClient
,其具体实现为org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerClient#RibbonLoadBalancerClient
//层层调用上级,最终调用到这个方法内部
public <T> T execute(String serviceId, LoadBalancerRequest<T> request, Object hint)
throws IOException {
/**根据serviceId获取到负载均衡器,一直跟踪到上层,发现这个方法是从容器中
//取了一个ILoadBalancer的Bean,但是ILoadBalancer有很多个实现类,到底用的是哪个呢?
*RibbonClientConfiguration这个类注入了我们ribbon客户端需要的所有配置
*其中就包含了 ILoadBalancer=>ZoneAwareLoadBalancer
*IPing=>DummyPing
*IRule=>ZoneAvoidanceRule
*所以此处我们获取到的实例是ZoneAwareLoadBalancer
**/
ILoadBalancer loadBalancer = getLoadBalancer(serviceId);
//通过负载均衡器选取一个server实例
Server server = getServer(loadBalancer, hint);
if (server == null) {
throw new IllegalStateException("No instances available for " + serviceId);
}
//封装成RibbionServer
RibbonServer ribbonServer = new RibbonServer(serviceId, server,
isSecure(server, serviceId),
serverIntrospector(serviceId).getMetadata(server));
return execute(serviceId, ribbonServer, request);
}
跟踪getServer()
方法
protected Server getServer(ILoadBalancer loadBalancer, Object hint) {
if (loadBalancer == null) {
return null;
}
// Use 'default' on a null hint, or just pass it on?
return loadBalancer.chooseServer(hint != null ? hint : "default");
}
//loadBalancer.chooseServer=>ZoneAwareLoadBalancer.chooseServer()
@Override
public Server chooseServer(Object key) {
//对于中国地区来说,是没有zone的概念的, 所以此处getAvailableZones= 0进入super.chooseServer(key)
if (!ENABLED.get() || getLoadBalancerStats().getAvailableZones().size() <= 1) {
logger.debug("Zone aware logic disabled or there is only one zone");
return super.chooseServer(key);
}
...
}
//super.chooseServer()
public Server chooseServer(Object key) {
if (counter == null) {
counter = createCounter();
}
counter.increment();
if (rule == null) {
return null;
} else {
try {
//通过rule来选择具体的server实例,此时的IRule具体实现是哪个呢 ?
//我们在new ZoneAwareLoadBalancer()的时候注入了ZoneAvoidanceRule,并且保存到rule属性中去,所以我们这个里的这个rule是ZoneAvoidanceRule
//ZoneAvoidanceRule中没有choose方法,找到父类PredicateBasedRule
return rule.choose(key);
} catch (Exception e) {
logger.warn("LoadBalancer [{}]: Error choosing server for key {}", name, key, e);
return null;
}
}
}
//com.netflix.loadbalancer.PredicateBasedRule#choose
public Server choose(Object key) {
ILoadBalancer lb = getLoadBalancer();
/**
*lb.getAllServers()=>BaseLoadBalancer.getAllServers()
*发现只是返回了一个属性值,看看是在哪里赋值的,有个方法setServersList
**/
Optional<Server> server = getPredicate().chooseRoundRobinAfterFiltering(lb.getAllServers(), key);
if (server.isPresent()) {
return server.get();
} else {
return null;
}
}
至此setServersList是在哪里调用的。猜测一下,所有的服务配置都是在nacos上配置的,nacos客户端会去读取所有的server信息并缓存在本地。此时用到的serverlist应该就是从nacos客户端拿的信息。那么ribbon是在什么时候去nacos客户端拿去的信息呢?我们再次回到ZoneAwareLoadBalancer的构造方法中去,在DynamicServerListLoadBalancer#DynamicServerListLoadBalancer(IClientConfig, IRule, IPing, ServerList<T>, ServerListFilter<T>, ServerListUpdater)
中存在这么一行代码restOfInit(clientConfig);
字面意思就是rest初始化,rest肯定会有接口访问信息,我们点进去继续跟进
void restOfInit(IClientConfig clientConfig) {
boolean primeConnection = this.isEnablePrimingConnections();
// turn this off to avoid duplicated asynchronous priming done in BaseLoadBalancer.setServerList()
this.setEnablePrimingConnections(false);
//开始一个延时任务去调用NacosServerList更新servers,该方法允许我们将一个新的实例添加到列表中
enableAndInitLearnNewServersFeature();
//接受延时任务的返回结果,更新serversList,也就是setServersList()
//至此我们就拿到了从nacos client获取到的servert集合
updateListOfServers();
if (primeConnection && this.getPrimeConnections() != null) {
this.getPrimeConnections()
.primeConnections(getReachableServers());
}
this.setEnablePrimingConnections(primeConnection);
LOGGER.info("DynamicServerListLoadBalancer for client {} initialized: {}", clientConfig.getClientName(), this.toString());
}