两年前的笔记了,Ribbon早就停止维护了,挂着纪念一下吧
注册中心如何与Ribbon整合
我们知道,我们在使用RestTemplate发送HTTP请求调用远端服务的时候,Ribbon需要获取请求URL中的服务名去注册中心拿到对应服务的所有实例信息,再负载均衡选择一个实例,用该实例的ip:port替换掉服务名 因此,Ribbon需要具备服务发现的功能,换句话说服务注册中心需要给Ribbon提供API让Ribbon具备针对该注册中心的服务发现的功能 很明显,Ribbon肯定会提供一个公共接口来让注册中心去实现 (面向接口编程),这个接口就是ServerList
package com.netflix.loadbalancer;
public interface ServerList<T extends Server> {
List<T> getInitialListOfServers();
List<T> getUpdatedListOfServers();
}
这里主要看Nacos是如何整合Ribbon的,进入NacosDiscovery包下的spring.factories可以看到,NacosDiscovery利用SpringBoot的自动配置SPI帮我们添加了一个配置类: com.alibaba.cloud.nacos.ribbon.RibbonNacosAutoConfiguration
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties
@ConditionalOnBean(SpringClientFactory.class)
@ConditionalOnRibbonNacos
@ConditionalOnNacosDiscoveryEnabled
@AutoConfigureAfter(RibbonAutoConfiguration.class)
@RibbonClients(defaultConfiguration = NacosRibbonClientConfiguration.class)
public class RibbonNacosAutoConfiguration {
}
这个配置类中并没有任何实现,为啥呢,我们不是说Nacos要提供一个ServerList的实现类嘛? 因为Ribbon为了支持不同服务的负载均衡策略单独定制,就意味着会有多个不同类型的IRule同时存在,如果只用一个容器来放IRule的话,就会很乱,为每一个需要调用服务单独都创建了一个Spring容器,容器实现为AnnotationConfigApplicationContext,并指定SpringBoot容器为其父容器,如果直接在上面的配置类中配置ServerList的话,那ServerList就会在父容器中,
重点在这个自动配置类上的@RibbonClients注解,这个注解指定了一个默认配置类NacosRibbonClientConfiguration 注意:RibbonAutoConfiguration上也有@RibbonClients
@Import(RibbonClientConfigurationRegistrar.class)
public @interface RibbonClients {}
可以看到,这个注解利用@Import注解向容器中导入了一个RibbonClientConfigurationRegistrar类,而这个类实现了ImportBeanDefinitionRegistrar接口重写了registerBeanDefinitions()方法,该方法就会在容器解析配置类的时候被调用
public class RibbonClientConfigurationRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
// 拿到注解元信息
Map<String, Object> attrs = metadata
.getAnnotationAttributes(RibbonClients.class.getName(), true);
if (attrs != null && attrs.containsKey("defaultConfiguration")) {
// 获取到注解所在类的全类名,即org.springframework.cloud.netflix.ribbon.RibbonNacosAutoConfiguration / RibbonAutoConfiguration
// 再给全类名前面加一个default.得到新的name = default.org.springframework.cloud.netflix.ribbon.RibbonNacosAutoConfiguration / RibbonAutoConfiguration
String name = "default." + metadata.getClassName();
// registry = DefaultListableBeanFactory
// attrs.get("defaultConfiguration") = NacosRibbonClientConfiguration.class / null
registerClientConfiguration(registry, name,
attrs.get("defaultConfiguration"));
}
}
private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name,
Object configuration) {
// 创建一个RibbonClientSpecification的beanDefinition
BeanDefinitionBuilder builder = BeanDefinitionBuilder
.genericBeanDefinition(RibbonClientSpecification.class);
// 将name = default.org.springframework.cloud.netflix.ribbon.RibbonNacosAutoConfiguration / RibbonAutoConfiguration
// configuration = NacosRibbonClientConfiguration.class / null设置到其构造方法的参数中
builder.addConstructorArgValue(name);
builder.addConstructorArgValue(configuration);
// 注册beanDefinition
registry.registerBeanDefinition(name + ".RibbonClientSpecification",
builder.getBeanDefinition());
}
}
我们再来看看该RibbonClientSpecification的类定义:
public class RibbonClientSpecification implements NamedContextFactory.Specification {
private String name;
private Class<?>[] configuration;
public RibbonClientSpecification(String name, Class<?>[] configuration) {
this.name = name;
this.configuration = configuration;
}
// ...
}
因此,NacosDiscovery整合Reibbon其实就是利用@RibbonClients注解创建一个RibbonClientSpecification这个bean,然后给该bean的name属性赋值为default.org.springframework.cloud.netflix.ribbon.RibbonNacosAutoConfiguration / RibbonAutoConfiguration,同时configuration属性赋值为NacosRibbonClientConfiguration.class / null
SpringCloud对外提供的为RestTemplate添加拦截器的接口
SpringCloud-common包的spring.factories中帮我们自动配置了org.springframework.cloud.client.loadbalancer.LoadBalancerAutoConfiguration,其结构如下:
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RestTemplate.class)
@ConditionalOnBean(LoadBalancerClient.class)
@EnableConfigurationProperties(LoadBalancerRetryProperties.class)
public class LoadBalancerAutoConfiguration {
// 依赖注入容器中所有添加了@LoadBalanced注解的RestTemplate
@LoadBalanced
@Autowired(required = false)
private List<RestTemplate> restTemplates = Collections.emptyList();
// 依赖注入容器中所有的LoadBalancerRequestTransformer(接口内部定义了一个transformRequest方法,之后在使用ip:port替换URL服务名的时候会用到)
@Autowired(required = false)
private List<LoadBalancerRequestTransformer> transformers = Collections.emptyList();
// 向容器中添加了一个SmartInitializingSingleton接口的实现类,该实现类的afterSingletonsInstantiated()方法会在实例化所有的非懒加载单实例bean之后调用
@Bean
public SmartInitializingSingleton loadBalancedRestTemplateInitializerDeprecated(
// 依赖注入RestTemplateCustomizer(下面会配置)
final ObjectProvider<List<RestTemplateCustomizer>> restTemplateCustomizers) {
// 遍历上面依赖注入进来的每一个RestTemplate,调用RestTemplateCustomizer#customize定制化每一个RestTemplate
return () -> restTemplateCustomizers.ifAvailable(customizers -> {
for (RestTemplate restTemplate : LoadBalancerAutoConfiguration.this.restTemplates) {
for (RestTemplateCustomizer customizer : customizers) {
customizer.customize(restTemplate);
}
}
});
}
// 主要是用来实现 ip:port 替换 URL中的服务名的
@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 {
// 创建一个LoadBalancerInterceptor用于拦截RestTemplate的每一个请求
// loadBalancerClient -> Ribbon包下会配置一个实现:RibbonLoadBalancerClient
@Bean
public LoadBalancerInterceptor loadBalancerInterceptor(
LoadBalancerClient loadBalancerClient,
LoadBalancerRequestFactory requestFactory) {
return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);
}
// 创建一个RestTemplateCustomizer,即RestTemplate的定制化器,作用是给RestTemplate添加上面配置的loadBalancerInterceptor
@Bean
@ConditionalOnMissingBean
public RestTemplateCustomizer restTemplateCustomizer(
final LoadBalancerInterceptor loadBalancerInterceptor) {
return restTemplate -> {
List<ClientHttpRequestInterceptor> list = new ArrayList<>(
restTemplate.getInterceptors());
list.add(loadBalancerInterceptor);
restTemplate.setInterceptors(list);
};
}
}
// ... 由于没有导入RetryTemplate类,因此下面的配置都不会生效
}
总结:SpringCloud-Common包对外提供了一个接口,可以给容器中所有添加了@LoadBalanced注解的RestTemplate添加一个LoadBalancerInterceptor,该LoadBalancerInterceptor会从容器中拿LoadBalancerClient,对于Ribbon这样的框架来说,只需要实现一个LoadBalancerClient即可对RestTemplate的请求进行拦截增强
Ribbon包提供的自动配置
Ribbon包的spring.factories中只指定了一个自动配置类org.springframework.cloud.netflix.ribbon.RibbonAutoConfiguration
@Configuration
@Conditional(RibbonAutoConfiguration.RibbonClassesConditions.class)
@RibbonClients
@AutoConfigureAfter(
name = "org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration")
@AutoConfigureBefore({ LoadBalancerAutoConfiguration.class,
AsyncLoadBalancerAutoConfiguration.class })
@EnableConfigurationProperties({ RibbonEagerLoadProperties.class,
ServerIntrospectorProperties.class })
public class RibbonAutoConfiguration {
// 依赖注入注册中心整合Ribbon所创建的RibbonClientSpecification
@Autowired(required = false)
private List<RibbonClientSpecification> configurations = new ArrayList<>();
// Ribbon懒加载的配置信息类
@Autowired
private RibbonEagerLoadProperties ribbonEagerLoadProperties;
// 创建一个springClientFactory并传入了注册中心整合Ribbon所创建的RibbonClientSpecification
// 该类就用来为每一个服务创建单独的Spring容器
// 注意:
// 1. 其父类为NamedContextFactory
// 2. 其构造方法中会指定父类的defaultConfigType属性为RibbonClientConfiguration.class
// 3. 其构造方法中会指定propertySourceName = "ribbon",propertyName = "ribbon.client.name"
@Bean
@ConditionalOnMissingBean
public SpringClientFactory springClientFactory() {
SpringClientFactory factory = new SpringClientFactory();
factory.setConfigurations(this.configurations);
return factory;
}
// 创建一个RibbonLoadBalancerClient,该bean就会被添加到LoadBalancerInterceptor中,执行Ribbon对RestTemplate的拦截逻辑
@Bean
@ConditionalOnMissingBean(LoadBalancerClient.class)
public LoadBalancerClient loadBalancerClient() {
return new RibbonLoadBalancerClient(springClientFactory());
}
// 处理ribbon是否懒加载,默认为懒加载(这也是为啥Ribbon默认对一个服务的第一次调用很慢的原因)
@Bean
@ConditionalOnProperty("ribbon.eager-load.enabled")
public RibbonApplicationContextInitializer ribbonApplicationContextInitializer() {
return new RibbonApplicationContextInitializer(springClientFactory(),
ribbonEagerLoadProperties.getClients());
}
// ...
}
总结:Ribbon包下的自动配置类主要帮我们做了三件事:
- 创建了一个SpringClientFactory并注入了注册中心整合Ribbon所创建的RibbonClientSpecification
- 创建一个RibbonLoadBalancerClient,该bean就会被添加到LoadBalancerInterceptor中,执行Ribbon对RestTemplate的拦截逻辑
- 处理Ribbon是否懒加载
Ribbon对RestTemplate的拦截
我们知道SpringCloud-Common包向RestTemplate中添加了一个拦截器,因此RestTemplate在发送HTTP请求之前肯定会先走这个拦截器的逻辑,进入LoadBalancerInterceptor.interceptor()方法
public class LoadBalancerInterceptor implements ClientHttpRequestInterceptor {
// Ribbon自动配置的 RibbonLoadBalancerClient
private LoadBalancerClient loadBalancer;
private LoadBalancerRequestFactory requestFactory;
@Override
public ClientHttpResponse intercept(final HttpRequest request, final byte[] body,
final ClientHttpRequestExecution execution) throws IOException {
// 拿到URI
final URI originalUri = request.getURI();
// 从URI中拿到服务名
String serviceName = originalUri.getHost();
// 调用RibbonLoadBalancerClient的execute()方法
return this.loadBalancer.execute(serviceName,
this.requestFactory.createRequest(request, body, execution));
}
}
可以看到,拦截逻辑的核心就是调用RibbonLoadBalancerClient的execute()方法
@Override
public <T> T execute(String serviceId, LoadBalancerRequest<T> request)
throws IOException {
return execute(serviceId, request, null);
}
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);
}
整个Ribbon最主要也是最核心的逻辑就是在第一行代码中:getLoadBalancer(serviceId)。这里我们可以抛出三个问题:
- 负载均衡器是什么?
- 负载均衡器是什么时候创建的?
-
负载均衡器里面的服务实例是什么时候从注册中心获取到的? ```java protected ILoadBalancer getLoadBalancer(String serviceId) { private SpringClientFactory clientFactory;
public RibbonLoadBalancerClient(SpringClientFactory clientFactory) { this.clientFactory = clientFactory; }
// clientFactory就是创建RibbonLoadBalancerClient时传入的SpringClientFactory(注意SpringClientFactory在创建的时候还注入了注册中心整合Ribbon所创建的RibbonClientSpecification) return this.clientFactory.getLoadBalancer(serviceId); }
public ILoadBalancer getLoadBalancer(String name) { return getInstance(name, ILoadBalancer.class); }
@Override
public
public
protected AnnotationConfigApplicationContext getContext(String name) { // Ribbon会为每一个服务都创建一个容器 // key为服务名,value为该服务所对应的容器 private Map<String, AnnotationConfigApplicationContext> contexts = new ConcurrentHashMap<>();
// 如果该当前服务所对应的容器还没有创建,就创建,否则就直接返回
if (!this.contexts.containsKey(name)) {
synchronized (this.contexts) { // 双重检测锁
if (!this.contexts.containsKey(name)) {
this.contexts.put(name, createContext(name));
}
}
}
return this.contexts.get(name); } ```
createContext(name)为服务创建容器
public abstract class NamedContextFactory<C extends NamedContextFactory.Specification>
implements DisposableBean, ApplicationContextAware {
// Ribbon包的RibbonAutoConfiguration中创建SpringClientFactory的时候,构造方法指定 = "ribbon"
private final String propertySourceName;
// Ribbon包的RibbonAutoConfiguration中创建SpringClientFactory的时候,构造方法指定 = "ribbon.client.name"
private final String propertyName;
// 用来保存每一个服务的容器
private Map<String, AnnotationConfigApplicationContext> contexts = new ConcurrentHashMap<>();
// Ribbon包的RibbonAutoConfiguration中创建SpringClientFactory的时候,调用setConfigurations(this.configurations)传入进来的注册中心配置的RibbonClientSpecification
private Map<String, C> configurations = new ConcurrentHashMap<>();
// 实现了ApplicationContextAware,拿到SpringBoot容器,作为每个服务容器的父容器
private ApplicationContext parent;
// Ribbon包的RibbonAutoConfiguration中创建SpringClientFactory时,构造方法指定 = RibbonClientConfiguration.class
private Class<?> defaultConfigType;
/**
* public SpringClientFactory() {
* super(RibbonClientConfiguration.class, "ribbon", "ribbon.client.name");
* }
*/
// Ribbon自动配置类中创建SpringClientFactory时会指定属性值
public NamedContextFactory(Class<?> defaultConfigType, String propertySourceName,
String propertyName) {
this.defaultConfigType = defaultConfigType;
this.propertySourceName = propertySourceName;
this.propertyName = propertyName;
}
@Override
public void setApplicationContext(ApplicationContext parent) throws BeansException {
this.parent = parent;
}
protected AnnotationConfigApplicationContext createContext(String name) {
// 创建一个空容器(注意,用的无参构造函数,容器并没有刷新)
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
// 遍历每一个RibbonClientSpecification,此时就会遍历到之前Nacos自动配置所导入的RibbonClientSpecification,其内部的configuration属性 = NacosRibbonClientConfiguration
for (Map.Entry<String, C> entry : this.configurations.entrySet()) {
if (entry.getKey().startsWith("default.")) {
// entry.getValue() -> RibbonClientSpecification -> RibbonClientSpecification.getConfiguration() - >
// 从RibbonClientSpecification中拿到configuration,里面就有Nacos整合Ribbon注入的NacosRibbonClientConfiguration
// NacosRibbonClientConfiguration才是真正自动配置ServerList的配置类,也就说,每一个服务容器都有一个ServerList
for (Class<?> configuration : entry.getValue().getConfiguration()) {
// 注册NacosRibbonClientConfiguration
context.register(configuration);
}
}
}
// 注册 defaultConfigType = RibbonClientConfiguration
context.register(PropertyPlaceholderAutoConfiguration.class,
this.defaultConfigType);
// 服务容器的Environment指定"ribbon.client.name" = name = 服务名
context.getEnvironment().getPropertySources().addFirst(new MapPropertySource(
this.propertySourceName,
Collections.<String, Object>singletonMap(this.propertyName, name)));
if (this.parent != null) {
// 设置服务容器的父容器为SpringBoot容器
context.setParent(this.parent);
context.setClassLoader(this.parent.getClassLoader());
}
context.setDisplayName(generateDisplayName(name));
// 刷新容器,此时就会解析NacosRibbonClientConfiguration和RibbonClientConfiguration配置类
context.refresh();
return context;
}
}
我们直接来看服务容器所解析的这两个配置类的内容: (1)先看RibbonClientConfiguration
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties
@Import({ HttpClientConfiguration.class, OkHttpRibbonConfiguration.class,
RestClientRibbonConfiguration.class, HttpClientRibbonConfiguration.class })
public class RibbonClientConfiguration {
/**
* Ribbon client default connect timeout.
*/
public static final int DEFAULT_CONNECT_TIMEOUT = 1000;
/**
* Ribbon client default read timeout.
*/
public static final int DEFAULT_READ_TIMEOUT = 1000;
/**
* Ribbon client default Gzip Payload flag.
*/
public static final boolean DEFAULT_GZIP_PAYLOAD = true;
// @Value("${ribbon.client.name}"),在调用createContext创建当前容器的时候就已经在服务容器的Environment指定"ribbon.client.name" = name = 服务名
@RibbonClientName
private String name = "client";
// TODO: maybe re-instate autowired load balancers: identified by name they could be
// associated with ribbon clients
@Autowired
private PropertiesFactory propertiesFactory;
@Bean
@ConditionalOnMissingBean
public IClientConfig ribbonClientConfig() {
// 创建一个IClientConfig的默认实现,用来保存当前服务的Ribbon参数
DefaultClientConfigImpl config = new DefaultClientConfigImpl();
// 加载配置:1.设置clientName=name=服务名 2.加载ribbon的默认参数 3.拿到我们在yaml中配置的自定义参数(例如指定服务使用的IRule)对默认参数进行覆盖
config.loadProperties(this.name);
config.set(CommonClientConfigKey.ConnectTimeout, DEFAULT_CONNECT_TIMEOUT);
config.set(CommonClientConfigKey.ReadTimeout, DEFAULT_READ_TIMEOUT);
config.set(CommonClientConfigKey.GZipPayload, DEFAULT_GZIP_PAYLOAD);
return config;
}
@Bean
@ConditionalOnMissingBean // 条件注解,如果我们自己配置了负载均衡算法,就不会再帮我们配置
public IRule ribbonRule(IClientConfig config) {
// 如果我们在yaml中指定了服务使用的负载均算法就会走到这里(因此,如果手动配置了全局的负载均衡算法,yaml中的手动配置是不会生效的,要么全部全局,要么全部手动配置)
if (this.propertiesFactory.isSet(IRule.class, name)) {
// 这里会通过yaml中配置的全类名反射创建负载均衡算法对象
return this.propertiesFactory.get(IRule.class, config, name);
}
// 可以看到,默认配置的负载均衡算法是ZoneAvoidanceRule
ZoneAvoidanceRule rule = new ZoneAvoidanceRule();
rule.initWithNiwsConfig(config);
return rule;
}
@Bean
@ConditionalOnMissingBean
public IPing ribbonPing(IClientConfig config) {
if (this.propertiesFactory.isSet(IPing.class, name)) {
return this.propertiesFactory.get(IPing.class, config, name);
}
return new DummyPing();
}
// 这里创建的ServerList是针对我们直接在yaml中手动指定被调用服务的地址的方式(Ribbon可以独立于注册中心使用)
@Bean
@ConditionalOnMissingBean // 条件注解
public ServerList<Server> ribbonServerList(IClientConfig config) {
if (this.propertiesFactory.isSet(ServerList.class, name)) {
return this.propertiesFactory.get(ServerList.class, config, name);
}
ConfigurationBasedServerList serverList = new ConfigurationBasedServerList();
serverList.initWithNiwsConfig(config);
return serverList;
}
// LoadBalancer的自动配置,之前说过的最重要的一步getLoadBalancer()就是获取它
@Bean
@ConditionalOnMissingBean
public ILoadBalancer ribbonLoadBalancer(IClientConfig config,
ServerList<Server> serverList, ServerListFilter<Server> serverListFilter,
IRule rule, IPing ping, ServerListUpdater serverListUpdater) {
if (this.propertiesFactory.isSet(ILoadBalancer.class, name)) {
return this.propertiesFactory.get(ILoadBalancer.class, config, name);
}
// 这里的serverList由NacosDiscovery包中的NacosRibbonClientConfiguration配置
return new ZoneAwareLoadBalancer<>(config, rule, ping, serverList,
serverListFilter, serverListUpdater);
}
// ...
}
(2)再来看NacosRibbonClientConfiguration
@Configuration(proxyBeanMethods = false)
@ConditionalOnRibbonNacos
public class NacosRibbonClientConfiguration {
@Bean
@ConditionalOnMissingBean
public ServerList<?> ribbonServerList(IClientConfig config,
NacosDiscoveryProperties nacosDiscoveryProperties) {
// 创建一个NacosServerList并传入nacos在application.yaml中的配置
NacosServerList serverList = new NacosServerList(nacosDiscoveryProperties);
// 这个里面只有一步:this.serviceId = iClientConfig.getClientName(),给NacosServerList中的serviceId属性设置为服务名
serverList.initWithNiwsConfig(config);
return serverList;
}
@Bean
@ConditionalOnMissingBean
public NacosServerIntrospector nacosServerIntrospector() {
return new NacosServerIntrospector();
}
}
我们来看看负载均衡器ZoneAwareLoadBalancer创建的过程中都做了什么
public ZoneAwareLoadBalancer(IClientConfig clientConfig, IRule rule, IPing ping, ServerList<T> serverList, ServerListFilter<T> filter, ServerListUpdater serverListUpdater) {
super(clientConfig, rule, ping, serverList, filter, serverListUpdater);
}
public DynamicServerListLoadBalancer(IClientConfig clientConfig, IRule rule, IPing ping,ServerList<T> serverList, ServerListFilter<T> filter,ServerListUpdater serverListUpdater) {
// supper主要是赋值
super(clientConfig, rule, ping);
this.serverListImpl = serverList;
this.filter = filter;
this.serverListUpdater = serverListUpdater;
if (filter instanceof AbstractServerListFilter) {
((AbstractServerListFilter) filter).setLoadBalancerStats(getLoadBalancerStats());
}
// 重点
restOfInit(clientConfig);
}
void restOfInit(IClientConfig clientConfig) {
boolean primeConnection = this.isEnablePrimingConnections();
this.setEnablePrimingConnections(false);
// 创建一个线程,默认每隔30s去NacosClient的serviceInfoMap中同步最新的服务节点信息(可以在配置文件配置ServerListRefreshInterval)
// 线程内就是调用updateListOfServers():将同步到的最新的服务节点信息赋值给当前ZoneAwareLoadBalancer的allServerList
enableAndInitLearnNewServersFeature();
// 可以看到,在创建同步线程之后,会立刻从serviceInfoMap中获取一次服务节点信息
updateListOfServers();
if (primeConnection && this.getPrimeConnections() != null) {
this.getPrimeConnections().primeConnections(getReachableServers());
}
this.setEnablePrimingConnections(primeConnection);
}
@VisibleForTesting
public void updateListOfServers() {
List<T> servers = new ArrayList<T>();
if (serverListImpl != null) {
// serverListImpl = NacosServerList(注意:NacosServerList是一个服务容器一个的,其serviceId属性代表服务名)
// ServerList<T> serverListImpl
// 调用NacosServerList获取到对应服务的节点信息
servers = serverListImpl.getUpdatedListOfServers();
if (filter != null) {
servers = filter.getFilteredListOfServers(servers);
}
}
updateAllServerList(servers);
}
@Override
public List<NacosServer> getUpdatedListOfServers() {
return getServers();
}
private List<NacosServer> getServers() {
try {
String group = discoveryProperties.getGroup();
// 拿到NacosNamingService,调用其selectInstances()
// selectInstances(String serviceName, String groupName, boolean healthy),注意这里传入的healthy = true,表示Ribbon只会同步健康的节点信息
List<Instance> instances = discoveryProperties.namingServiceInstance()
.selectInstances(serviceId, group, true);
// 将拿到的instance封装成一个NacosServer
return instancesToServerList(instances);
} catch (Exception e) {
}
}
// NacosNamingService.class 这里其实已经进入Nacos的服务发现的逻辑了
@Override
public List<Instance> selectInstances(String serviceName, String groupName, List<String> clusters, boolean healthy,boolean subscribe) throws NacosException {
ServiceInfo serviceInfo;
if (subscribe) { // 传入的subscribe默认都会true,这里就会从serviceInfoMap中拿数据,没有就从注册中心拿并创建NacosClient的同步线程
serviceInfo = hostReactor.getServiceInfo(NamingUtils.getGroupedName(serviceName, groupName),StringUtils.join(clusters, ","));
} else {
serviceInfo = hostReactor.getServiceInfoDirectlyFromServer(NamingUtils.getGroupedName(serviceName, groupName),StringUtils.join(clusters, ","));
}
// 过滤掉不健康的实例,仅保留健康的实例
return selectInstances(serviceInfo, healthy);
}
至此,ZoneAwareLoadBalancer的创建就结束了,之后随着服务容器Refresh()的结束,createContext()也就结束了,会到调用createContext()的地方
protected AnnotationConfigApplicationContext getContext(String name) {
if (!this.contexts.containsKey(name)) {
synchronized (this.contexts) {
if (!this.contexts.containsKey(name)) {
// createContext()结束
this.contexts.put(name, createContext(name));
}
}
}
// 拿到服务所对应的服务容器
return this.contexts.get(name);
}
其实也可以思考一下,如果我们提前将服务的context创建好,是不是在第一次调用的时候,就不需要再去走上面的创建逻辑了,因为contexts中已经有对应服务的context了,这里直接就返回(这个就是之后会说的关闭Ribbon懒加载的逻辑)
public <T> T getInstance(String name, Class<T> type) {
// 拿到服务对应的容器
AnnotationConfigApplicationContext context = getContext(name);
try {
// 从容器中拿bean,type = ILoadBalancer.class,拿到的就是ZoneAwareLoadBalancer
return context.getBean(type);
}
catch (NoSuchBeanDefinitionException e) {
// ignore
}
return null;
}
再回到我们之前的execute()方法
public <T> T execute(String serviceId, LoadBalancerRequest<T> request, Object hint) throws IOException {
// 从调用服务所对应的容器中拿到ZoneAwareLoadBalancer
ILoadBalancer loadBalancer = getLoadBalancer(serviceId);
// 根据负载均衡算法从ZoneAwareLoadBalancer的allServerList中选择一个服务节点
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);
}
现在我们再来看,是如何从ZoneAwareLoadBalancer中选节点的
protected Server getServer(ILoadBalancer loadBalancer, Object hint) {
if (loadBalancer == null) {
return null;
}
// Use 'default' on a null hint, or just pass it on?
// hint默认传的是null,因此传入的参数 = "default"
return loadBalancer.chooseServer(hint != null ? hint : "default");
}
@Override
public Server chooseServer(Object key) {
// 我们一般不会配置zone,因此第二个条件getAvailableZones().size() <= 1成立
if (!ENABLED.get() || getLoadBalancerStats().getAvailableZones().size() <= 1) {
return super.chooseServer(key);
}
// ... 这里还有很多关于ZoneRegion的逻辑,我们一般不会用这个东西
return super.chooseServer(key);
}
public Server chooseServer(Object key) {
if (counter == null) {
counter = createCounter();
}
counter.increment();
if (rule == null) {
return null;
} else {
try {
// 关键,调用rule.choose(key),key = "default"
return rule.choose(key);
} catch (Exception e) {
logger.warn("LoadBalancer [{}]: Error choosing server for key {}", name, key, e);
return null;
}
}
}
// 这里我们只看Ribbon默认配置的rule -> ZoneAvoidanceRule
@Override
public Server choose(Object key) {
// 拿到ZoneAwareLoadBalancer
ILoadBalancer lb = getLoadBalancer();
// lb.getAllServers() -> 返回allServerList
Optional<Server> server = getPredicate().chooseRoundRobinAfterFiltering(lb.getAllServers(), key);
if (server.isPresent()) {
return server.get();
} else {
return null;
}
}
public Optional<Server> chooseRoundRobinAfterFiltering(List<Server> servers, Object loadBalancerKey) {
// Zone的一些过滤,我们不会用这些东西
List<Server> eligible = getEligibleServers(servers, loadBalancerKey);
if (eligible.size() == 0) {
return Optional.absent();
}
// 因此直接走轮训的逻辑,可以看到,默认的负载均衡算法类,在不使用zone的情况下,就是轮训
return Optional.of(eligible.get(incrementAndGetModulo(eligible.size())));
}
再次回到之前的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 ribbonServer = new RibbonServer(serviceId, server,
isSecure(server, serviceId),
serverIntrospector(serviceId).getMetadata(server));
return execute(serviceId, ribbonServer, request);
}
@Override
public <T> T execute(String serviceId, ServiceInstance serviceInstance, LoadBalancerRequest<T> request) throws IOException {
// ...
try {
// 这一步,这个request是什么时候创建的呢?
T returnVal = request.apply(serviceInstance);
return returnVal;
} catch (IOException ex) {
// ..
}
return null;
}
再之前调用LoadBalancerInterceptor.intercept()方法的时候,我们通过requestFactory创建了一个request
@Override
public ClientHttpResponse intercept(final HttpRequest request, final byte[] body,
final ClientHttpRequestExecution execution) throws IOException {
final URI originalUri = request.getURI();
String serviceName = originalUri.getHost();
// loadBalancer -> RibbonLoadBalancerClient
return this.loadBalancer.execute(serviceName,
// 创建Request(注意不是HttpRequest)
this.requestFactory.createRequest(request, body, execution));
}
public LoadBalancerRequest<ClientHttpResponse> createRequest(
final HttpRequest request, final byte[] body,
final ClientHttpRequestExecution execution) {
// lambda表达式,创建的是一个LoadBalancerRequest,重写了里面的apply()方法
return instance -> {
HttpRequest serviceRequest = new ServiceRequestWrapper(request, instance,
this.loadBalancer);
if (this.transformers != null) {
// 使用transformRequest将serviceRequest中带服务名的URL中的服务名替换为ip:port
for (LoadBalancerRequestTransformer transformer : this.transformers) {
serviceRequest = transformer.transformRequest(serviceRequest,
instance);
}
}
// 继续往后执行(这之后就是调用下一个拦截器了,就是RestTemplate的逻辑了)
return execution.execute(serviceRequest, body);
};
}
因此,requset.app()实际上就是调用上面的方法,上面的方法主要做了两件事,一个就是替换URL中的服务名为选中的ip:port,另一个就是继续调用
Ribbon实现非懒加载儿原理
至此,整个完整的调用逻辑也就结束了,再来说说Ribbon的懒加载问题 我们知道,通过Ribbon第一次调用一个服务是很慢的,解决方式是在yaml中配置ribbon.eager-load.enabled=true,为什么配置了就能解决第一次调用慢的问题呢? 我们再进入Ribbon包的自动配置类
// RibbonAutoConfiguration
@Bean
// 可以看到,通过条件注解,如果我们在配置文件中配置ribbon.eager-load.enabled=true,那么就会在容器中添加一个Bean:RibbonApplicationContextInitializer
@ConditionalOnProperty("ribbon.eager-load.enabled")
public RibbonApplicationContextInitializer ribbonApplicationContextInitializer() {
// 创建bean的时候,会传入SpringClientFactory以及我们在配置文件中配置的非懒加载的服务
return new RibbonApplicationContextInitializer(springClientFactory(),
ribbonEagerLoadProperties.getClients());
}
查看这个类的类图可以发现,RibbonApplicationContextInitializer实现了ApplicationListener并且关注ApplicationReadyEvent事件,springboot容器刷新完之后就会发布该事件:
refreshContext(context);
afterRefresh(context, applicationArguments);
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
}
listeners.started(context); // 发布ApplicationStartedEvent事件
我们再来看其onApplicationEvent方法
@Override
public void onApplicationEvent(ApplicationReadyEvent event) {
initialize();
}
protected void initialize() {
if (clientNames != null) {
// 遍历我们在配置文件中指定的服务名
for (String clientName : clientNames) {
// 调用getContext()
this.springClientFactory.getContext(clientName);
}
}
}
可以看到,关闭懒加载其实就是在SpringBoot容器刷新了之后去将指定的服务容器创建好,同时服务容器刷新的过程中就会去注册中心拉取服务节点信息
NacosDiscovery包做的事情:
NacosDiscovery包的spring.factories中导入了RibbonNacosAutoConfiguration,上面有@RibbonClients(defaultConfiguration = NacosRibbonClientConfiguration.class)
实际上是向容器中添加了一个RibbonClientSpecification类,这个类的configuration属性 = NacosRibbonClientConfiguration.class,因此可以看到,实际上NacosRibbonClientConfiguration
不会被Sringboot的容器解析,因为没有创建其beanDefinition,而是直接设置到RibbonClientSpecification这个bean中,这个bean并不是配置类
注意:NacosRibbonClientConfiguration中帮我们配置了ServerList = NacosServerList
Ribbon包做的事情:
Ribbon包的spring.factories中导入了RibbonAutoConfiguration,里面首先通过@Autowired直接帮我们注入了所有的RibbonClientSpecification(上面创建的)
同时帮我们配置了SpringClientFactory(构造函数中还会指定defaultConfigType = RibbonClientConfiguration)并将所有的RibbonClientSpecification设置到其中
同时还帮我们配置了一个LoadBalancerClient的实现RibbonLoadBalancerClient,同时创建RibbonLoadBalancerClient的时候传入了上一步配置的SpringClientFactory
common包做得事情
common包的spring.factories中导入了LoadBalancerAutoConfiguration,里面首先通过@Autowired+@LoadBalanced拿到容器中所有标注了@LoadBalanced注解的RestTemplate
1. 帮我们配置了一个LoadBalancerInterceptor并且从容器中拿到loadBalancerClient(上面ribbon包有帮我们配置RibbonLoadBalancerClient并设置到其中
2. 帮我们配置了一个RestTemplateCustomizer即RestTemplate的定制化器,这个定制化器中的void customize(RestTemplate restTemplate)方法会给传入的restTemplate设置上一步配置好的loadBalancerInterceptor
3. 帮我们配置了一个SmartInitializingSingleton的实现类并重写了afterSingletonsInstantiated()方法,在容器启动时,在所有非懒加载的单实例bean的getBean()方法调用完之后会调用其afterSingletonsInstantiated(),方法内会拿到上一步配置的RestTemplateCustomizer调用其customize方法,方法的参数就是当前配置类中注入的所有标注了@LoadBalanced注解的RestTemplate
因此,实际上common包配置类的作用就是给所有标注了@LoadBalanced注解的RestTemplate中设置一个LoadBalancerInterceptor,这个LoadBalancerInterceptor在创建的时候引用了RibbonLoadBalancerClient
因此整个结构就比较明确了:
每一个@LoadBalanced注解的RestTemplate中有一个LoadBalancerInterceptor,这个LoadBalancerInterceptor中有RibbonLoadBalancerClient,RibbonLoadBalancerClient中有SpringClientFactory,SpringClientFactory中有RibbonClientSpecification和RibbonClientConfiguration,RibbonClientSpecification中有NacosRibbonClientConfiguration,NacosRibbonClientConfiguration中又有ServerList的nacos实现NacosServerList,NacosServerList中引用了NacosDiscoveryProperties,NacosDiscoveryProperties中依赖注入了nacosServiceManager,nacosServiceManager中就可以拿到NacosNamingService