spring cloud gateway 整合ribbon、nacos discovery实现负载均衡源码简析

本文源代码分析基于spring cloud 版本:Hoxton.SR3,spring cloud alibaba 版本:2.2.1.RELEASE。

1 spring cloud gateway 负载均衡入口

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());
}
}

2 RibbonLoadBalancerClient 大体结构

重点RibbonLoadBalancerClient的处理流程,先上一个看完源码的总结图:
spring cloud gateway 整合ribbon、nacos discovery实现负载均衡源码简析_第1张图片
加载完后对应的结构下:
spring cloud gateway 整合ribbon、nacos discovery实现负载均衡源码简析_第2张图片

3 源码简析

3.1 程序启动时创建 SpringClientFactory

Netflix所有自动配置都在spring-cloud-netflix-core-xxx.jar中,根据其META-INF/spring.factories中的配置得知,Spring Cloud Ribbon的自动配置类为 RibbonAutoConfiguration
spring cloud gateway 整合ribbon、nacos discovery实现负载均衡源码简析_第3张图片
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());
	}

	......
}

3.2 首次访问某个服务时创建以服务名命名的applicationContext

调用链: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()这一行断点,内存结构如下:
spring cloud gateway 整合ribbon、nacos discovery实现负载均衡源码简析_第4张图片

3.2.1 RibbonClientConfiguration注册

上面已说明context.register(PropertyPlaceholderAutoConfiguration.class,this.defaultConfigType)

3.2.2 NacosRibbonClientConfiguration注册

由于我们引入了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配置

spring cloud gateway 整合ribbon、nacos discovery实现负载均衡源码简析_第5张图片
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实现类 是如何加载配置并创建的

3.2.3 IClientConfig创建

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的配置信息也会被加载进来
静态配置如下:
spring cloud gateway 整合ribbon、nacos discovery实现负载均衡源码简析_第6张图片

3.2.4 RibbonClient核心接口实现类配置加载及创建

上面说完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),所以首次访问时可能会慢一点。

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