本文源代码分析基于spring cloud 版本:Hoxton.SR3,spring cloud alibaba 版本:2.2.1.RELEASE。
spring cloud gateway 使用 LoadBalancerClientFilter 来实现载均衡的功能,该过滤器通过LoadBalancerClient.choose(ServerWebExchange exchange)方法来获取目标实例。 LoadBalancerClient 为spring-cloud-commons包里提供的接口,默认实现类为netflix的RibbonLoadBalancerClient。
public class LoadBalancerClientFilter implements GlobalFilter, Ordered { ………… //默认实现类为netflix的RibbonLoadBalancerClient protected final LoadBalancerClient loadBalancer; ………… public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { ………… final ServiceInstance instance = choose(exchange); ………… URI requestUrl = loadBalancer.reconstructURI( new DelegatingServiceInstance(instance, overrideScheme), uri); ………… exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, requestUrl); return chain.filter(exchange); } protected ServiceInstance choose(ServerWebExchange exchange) { return loadBalancer.choose( ((URI) exchange.getAttribute(GATEWAY_REQUEST_URL_ATTR)).getHost()); } }重点RibbonLoadBalancerClient的处理流程,先上一个看完源码的总结图: 加载完后对应的结构下:
Netflix所有自动配置都在spring-cloud-netflix-core-xxx.jar中,根据其META-INF/spring.factories中的配置得知,Spring Cloud Ribbon的自动配置类为 RibbonAutoConfiguration RibbonAutoConfiguration如下:
@Configuration @ConditionalOnClass({ IClient.class, RestTemplate.class, AsyncRestTemplate.class, Ribbon.class}) @RibbonClients @AutoConfigureAfter(name = "org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration") @AutoConfigureBefore({LoadBalancerAutoConfiguration.class, AsyncLoadBalancerAutoConfiguration.class}) @EnableConfigurationProperties(RibbonEagerLoadProperties.class) public class RibbonAutoConfiguration { // 所有针对某个RibbonClient指定的配置 @Autowired(required = false) private List<RibbonClientSpecification> configurations = new ArrayList<>(); // ribbon是否懒加载的配置文件 @Autowired private RibbonEagerLoadProperties ribbonEagerLoadProperties; // Spring会给每个RibbonClient创建独立的ApplicationContext上下文 // 并在其上下文中创建RibbonClient对应的Bean:如IClient、ILoadbalancer等 @Bean public SpringClientFactory springClientFactory() { SpringClientFactory factory = new SpringClientFactory(); factory.setConfigurations(this.configurations); return factory; } // Spring创建的带负载均衡功能的Client,会使用SpringClientFactory创建对应的Bean和配置 @Bean @ConditionalOnMissingBean(LoadBalancerClient.class) public LoadBalancerClient loadBalancerClient() { return new RibbonLoadBalancerClient(springClientFactory()); } // 到Spring environment中加载针对某个Client的Ribbon的核心接口实现类 @Bean @ConditionalOnMissingBean public PropertiesFactory propertiesFactory() { return new PropertiesFactory(); } // 如果不是懒加载,启动时就使用RibbonApplicationContextInitializer加载并初始化客户端配置 @Bean @ConditionalOnProperty(value = "ribbon.eager-load.enabled", matchIfMissing = false) public RibbonApplicationContextInitializer ribbonApplicationContextInitializer() { return new RibbonApplicationContextInitializer(springClientFactory(), ribbonEagerLoadProperties.getClients()); } ...... }调用链:LoadBalancerClientFilter.choose-->RibbonLoadBalancerClient.choose-->RibbonLoadBalancerClient.getLoadBalancer-->SpringClientFactory.getLoadBalancer--> NamedContextFactory.getContext. 核心类和方法:
//## org.springframework.cloud.netflix.ribbon.SpringClientFactory public class SpringClientFactory extends NamedContextFactory<RibbonClientSpecification> { static final String NAMESPACE = "ribbon"; public SpringClientFactory() { super(RibbonClientConfiguration.class, NAMESPACE, "ribbon.client.name"); } /** * Get the rest client associated with the name. * @throws RuntimeException if any error occurs */ public <C extends IClient<?, ?>> C getClient(String name, Class<C> clientClass) { return getInstance(name, clientClass); } // name代表当前Ribbon客户端,type代表要获取的实例类型,如IClient、IRule @Override public <C> C getInstance(String name, Class<C> type) { // 先从父类NamedContextFactory中直接从客户端对应的ApplicationContext中获取实例 // 如果没有就根据IClientConfig中的配置找到具体的实现类,并通过反射初始化后放到Client对应的ApplicationContext中 C instance = super.getInstance(name, type); if (instance != null) { return instance; } IClientConfig config = getInstance(name, IClientConfig.class); return instantiateWithConfig(getContext(name), type, config); } // 使用IClientConfig实例化 static <C> C instantiateWithConfig(AnnotationConfigApplicationContext context, Class<C> clazz, IClientConfig config) { C result = null; try { // 通过以IClientConfig为参数的构造创建clazz类实例 Constructor<C> constructor = clazz.getConstructor(IClientConfig.class); result = constructor.newInstance(config); } catch (Throwable e) { // Ignored } // 如果没创建成功,使用无惨构造 if (result == null) { result = BeanUtils.instantiate(clazz); // 调用初始化配置方法 if (result instanceof IClientConfigAware) { ((IClientConfigAware) result).initWithNiwsConfig(config); } // 处理自动织入 if (context != null) { context.getAutowireCapableBeanFactory().autowireBean(result); } } return result; } } //## 父类 org.springframework.cloud.context.named.NamedContextFactory public abstract class NamedContextFactory<C extends NamedContextFactory.Specification> implements DisposableBean, ApplicationContextAware { // 维护Ribbon客户端对应的ApplicationContext上下文 private Map<String, AnnotationConfigApplicationContext> contexts = new ConcurrentHashMap<>(); // 维护Ribbon客户端的@Configuration配置类 private Map<String, C> configurations = new ConcurrentHashMap<>(); private ApplicationContext parent; private Class<?> defaultConfigType; // 默认配置类为 RibbonClientConfiguration private final String propertySourceName; // 默认为 ribbon private final String propertyName; // 默认读取RibbonClient名的属性为ribbon.client.name public NamedContextFactory(Class<?> defaultConfigType, String propertySourceName, String propertyName) { this.defaultConfigType = defaultConfigType; this.propertySourceName = propertySourceName; this.propertyName = propertyName; } // 如果包含Client上下文直接返回 // 如果不包含,调用createContext(name),并放入contexts集合 protected AnnotationConfigApplicationContext getContext(String name) { if (!this.contexts.containsKey(name)) { synchronized (this.contexts) { if (!this.contexts.containsKey(name)) { this.contexts.put(name, createContext(name)); } } } return this.contexts.get(name); } // 创建名为name的RibbonClient的ApplicationContext上下文 protected AnnotationConfigApplicationContext createContext(String name) { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); // configurations集合中是否包含当前Client相关配置类,包含即注入到ApplicationContext if (this.configurations.containsKey(name)) { for (Class<?> configuration : this.configurations.get(name) .getConfiguration()) { context.register(configuration); } } //configurations集合中是否包含default.开头的通过@RibbonClients(defaultConfiguration=xxx)配置的默认配置类 for (Map.Entry<String, C> entry : this.configurations.entrySet()) { if (entry.getKey().startsWith("default.")) { for (Class<?> configuration : entry.getValue().getConfiguration()) { context.register(configuration); } } } // 注册PropertyPlaceholderAutoConfiguration、RibbonClientConfiguration context.register(PropertyPlaceholderAutoConfiguration.class, this.defaultConfigType); // 添加 ribbon.client.name=具体RibbonClient name的enviroment配置 context.getEnvironment().getPropertySources().addFirst(new MapPropertySource( this.propertySourceName, Collections.<String, Object> singletonMap(this.propertyName, name))); // 设置父ApplicationContext,这样可以使得当前创建的子ApplicationContext可以使用父上下文中的Bean if (this.parent != null) { // Uses Environment from parent as well as beans context.setParent(this.parent); } context.refresh(); //刷新Context return context; } public <T> T getInstance(String name, Class<T> type) { AnnotationConfigApplicationContext context = getContext(name); if (BeanFactoryUtils.beanNamesForTypeIncludingAncestors(context, type).length > 0) { return context.getBean(type); } return null; } }在context.refresh()这一行断点,内存结构如下:
上面已说明context.register(PropertyPlaceholderAutoConfiguration.class,this.defaultConfigType)
由于我们引入了spring-cloud-starter-alibaba-nacos-discovery,而在RibbonNacosAutoConfiguration类中可以看到有RibbonClients注解。
@Configuration(proxyBeanMethods = false) @EnableConfigurationProperties @ConditionalOnBean(SpringClientFactory.class) @ConditionalOnRibbonNacos @ConditionalOnNacosDiscoveryEnabled @AutoConfigureAfter(RibbonAutoConfiguration.class) @RibbonClients(defaultConfiguration = NacosRibbonClientConfiguration.class) public class RibbonNacosAutoConfiguration { }configurations配置类集合是根据@RibbonClient 和 @RibbonClients注解配置的,分别有 针对具体某个RibbonClient的配置 和 default默认配置,详见后面的 “3.2.4 RibbonClient创建" 所在在configrattions可以看到nacos针对ribbon配置
context.refresh()执行时,就会注册上面两个核心的configuration:
NacosRibbonClientConfiguration配置如下:
@Configuration(proxyBeanMethods = false) @ConditionalOnRibbonNacos public class NacosRibbonClientConfiguration { @Autowired private PropertiesFactory propertiesFactory; @Bean @ConditionalOnMissingBean public ServerList<?> ribbonServerList(IClientConfig config, NacosDiscoveryProperties nacosDiscoveryProperties) { if (this.propertiesFactory.isSet(ServerList.class, config.getClientName())) { ServerList serverList = this.propertiesFactory.get(ServerList.class, config, config.getClientName()); return serverList; } NacosServerList serverList = new NacosServerList(nacosDiscoveryProperties); serverList.initWithNiwsConfig(config); return serverList; } @Bean @ConditionalOnMissingBean public NacosServerIntrospector nacosServerIntrospector() { return new NacosServerIntrospector(); } }RibbonClientConfiguration配置如下:
@Import({OkHttpRibbonConfiguration.class, RestClientRibbonConfiguration.class, HttpClientRibbonConfiguration.class}) public class RibbonClientConfiguration { @Value("${ribbon.client.name}") 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() { DefaultClientConfigImpl config = new DefaultClientConfigImpl(); config.loadProperties(this.name); return config; } @Bean @ConditionalOnMissingBean public IRule ribbonRule(IClientConfig config) { if (this.propertiesFactory.isSet(IRule.class, name)) { return this.propertiesFactory.get(IRule.class, config, name); } ZoneAvoidanceRule rule = new ZoneAvoidanceRule(); rule.initWithNiwsConfig(config); return rule; }上面只截取了一段代码,给出了Ribbon相关的IClientConfig客户端配置 和 某一个核心接口IRule实现类 是如何加载配置并创建的
IClientConfig就是Ribbon客户端配置的接口,可以看到先是创建了DefaultClientConfigImpl默认实现类,再config.loadProperties(this.name)加载当前Client相关的配置
//## com.netflix.client.config.DefaultClientConfigImpl#loadProperties() /** * Load properties for a given client. It first loads the default values for all properties, * and any properties already defined with Archaius ConfigurationManager. */ @Override public void loadProperties(String restClientName){ enableDynamicProperties = true; setClientName(restClientName); // 1、使用Netflix Archaius的ConfigurationManager从Spring env中加载“ribbon.配置项”这类默认配置 // 如没加载到有默认静态配置 loadDefaultValues(); // 2、使用Netflix Archaius的ConfigurationManager从Spring env中加载“client名.ribbon.配置项”这类针对某个Client的配置信息 Configuration props = ConfigurationManager.getConfigInstance().subset(restClientName); for (Iterator<String> keys = props.getKeys(); keys.hasNext(); ){ String key = keys.next(); String prop = key; try { if (prop.startsWith(getNameSpace())){ prop = prop.substring(getNameSpace().length() + 1); } setPropertyInternal(prop, getStringValue(props, key)); } catch (Exception ex) { throw new RuntimeException(String.format("Property %s is invalid", prop)); } } }根据如上注释,如果你没有在项目中指定ribbon相关配置,那么会使用DefaultClientConfigImpl中的默认静态配置,如果Spring enviroment中包含“ribbon.配置项”这类针对所有Client的配置会被加载进来,有“client名.ribbon.配置项”这类针对某个Client的配置信息也会被加载进来 静态配置如下:
上面说完IClientConfig配置项是如何加载的,按道理说其中已经包含了当前RibbonClient使用哪个核心接口实现类的配置,但Spring Cloud在此处定义了自己的实现逻辑
@Autowired private PropertiesFactory propertiesFactory; @Bean @ConditionalOnMissingBean public IRule ribbonRule(IClientConfig config) { // 查看propertiesFactory是否有关于当前接口的配置,如有就使用,并创建实例返回 if (this.propertiesFactory.isSet(IRule.class, name)) { return this.propertiesFactory.get(IRule.class, config, name); } // spring cloud 默认配置 ZoneAvoidanceRule rule = new ZoneAvoidanceRule(); rule.initWithNiwsConfig(config); return rule; }下面看看PropertiesFactory的逻辑
public class PropertiesFactory { @Autowired private Environment environment; private Map<Class, String> classToProperty = new HashMap<>(); public PropertiesFactory() { classToProperty.put(ILoadBalancer.class, "NFLoadBalancerClassName"); classToProperty.put(IPing.class, "NFLoadBalancerPingClassName"); classToProperty.put(IRule.class, "NFLoadBalancerRuleClassName"); classToProperty.put(ServerList.class, "NIWSServerListClassName"); classToProperty.put(ServerListFilter.class, "NIWSServerListFilterClassName"); } // 查看当前clazz是否在classToProperty管理的几个核心接口之一 // 如是,查看Spring environment中是否能找到 “clientName.ribbon.核心接口配置项”的配置信息 public boolean isSet(Class clazz, String name) { return StringUtils.hasText(getClassName(clazz, name)); } public String getClassName(Class clazz, String name) { if (this.classToProperty.containsKey(clazz)) { String classNameProperty = this.classToProperty.get(clazz); String className = environment.getProperty(name + "." + NAMESPACE + "." + classNameProperty); return className; } return null; } // 也是先调用getClassName()获取Spring enviroment中配置的核心接口实现类名 // 再使用IClientConfig配置信息创建其实例 @SuppressWarnings("unchecked") public <C> C get(Class<C> clazz, IClientConfig config, String name) { String className = getClassName(clazz, name); if (StringUtils.hasText(className)) { try { Class<?> toInstantiate = Class.forName(className); return (C) instantiateWithConfig(toInstantiate, config); } catch (ClassNotFoundException e) { throw new IllegalArgumentException("Unknown class to load "+className+" for class " + clazz + " named " + name); } } return null; } }故以上面创建IRule接口实现类的逻辑 ● 先通过propertiesFactory查看Spring enviroment中是否配置了针对当前Ribbon Client的IRule核心接口实现类的配置信息,如有,就创建其实例返回(相关配置格式: clientName.ribbon.NFLoadBalancerRuleClassName=具体IRule实现类) ● 如没有,那么没有直接使用Netflix在其DefaultClientConfigImpl中的静态配置,而是使用Spring Cloud自定义的默认实现类,拿IRule规则接口来说是ZoneAvoidanceRule
针对每次首次访问某个client时都会执行createContext(String name),所以首次访问时可能会慢一点。
