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

2022-06-16 10:27:49

本文源代码分析基于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

publicclassLoadBalancerClientFilterimplementsGlobalFilter, Ordered{
…………//默认实现类为netflix的RibbonLoadBalancerClientprotectedfinal LoadBalancerClient loadBalancer;
…………public Mono<Void>filter(ServerWebExchange exchange, GatewayFilterChain chain){
 …………final ServiceInstance instance=choose(exchange);
  …………
   URI requestUrl= loadBalancer.reconstructURI(newDelegatingServiceInstance(instance, overrideScheme), uri);
…………
   exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, requestUrl);return chain.filter(exchange);}protected ServiceInstancechoose(ServerWebExchange exchange){return loadBalancer.choose(((URI) exchange.getAttribute(GATEWAY_REQUEST_URL_ATTR)).getHost());}}

2 RibbonLoadBalancerClient 大体结构

重点RibbonLoadBalancerClient的处理流程,先上一个看完源码的总结图:
在这里插入图片描述
加载完后对应的结构下:
在这里插入图片描述

3 源码简析

3.1 程序启动时创建 SpringClientFactory

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)publicclassRibbonAutoConfiguration{// 所有针对某个RibbonClient指定的配置@Autowired(required=false)private List<RibbonClientSpecification> configurations=newArrayList<>();// ribbon是否懒加载的配置文件@Autowiredprivate RibbonEagerLoadProperties ribbonEagerLoadProperties;// Spring会给每个RibbonClient创建独立的ApplicationContext上下文// 并在其上下文中创建RibbonClient对应的Bean:如IClient、ILoadbalancer等@Beanpublic SpringClientFactoryspringClientFactory(){
		SpringClientFactory factory=newSpringClientFactory();
		factory.setConfigurations(this.configurations);return factory;}// Spring创建的带负载均衡功能的Client,会使用SpringClientFactory创建对应的Bean和配置@Bean@ConditionalOnMissingBean(LoadBalancerClient.class)public LoadBalancerClientloadBalancerClient(){returnnewRibbonLoadBalancerClient(springClientFactory());}// 到Spring environment中加载针对某个Client的Ribbon的核心接口实现类@Bean@ConditionalOnMissingBeanpublic PropertiesFactorypropertiesFactory(){returnnewPropertiesFactory();}// 如果不是懒加载,启动时就使用RibbonApplicationContextInitializer加载并初始化客户端配置@Bean@ConditionalOnProperty(value="ribbon.eager-load.enabled", matchIfMissing=false)public RibbonApplicationContextInitializerribbonApplicationContextInitializer(){returnnewRibbonApplicationContextInitializer(springClientFactory(),
				ribbonEagerLoadProperties.getClients());}......}

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

调用链:LoadBalancerClientFilter.choose-->RibbonLoadBalancerClient.choose-->RibbonLoadBalancerClient.getLoadBalancer-->SpringClientFactory.getLoadBalancer--> NamedContextFactory.getContext.
核心类和方法:

//## org.springframework.cloud.netflix.ribbon.SpringClientFactorypublicclassSpringClientFactoryextendsNamedContextFactory<RibbonClientSpecification>{staticfinal String NAMESPACE="ribbon";publicSpringClientFactory(){super(RibbonClientConfiguration.class, NAMESPACE,"ribbon.client.name");}/**
	 * Get the rest client associated with the name.
	 * @throws RuntimeException if any error occurs
	 */public<CextendsIClient<?,?>> CgetClient(String name, Class<C> clientClass){returngetInstance(name, clientClass);}// name代表当前Ribbon客户端,type代表要获取的实例类型,如IClient、IRule@Overridepublic<C> CgetInstance(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);returninstantiateWithConfig(getContext(name), type, config);}// 使用IClientConfig实例化static<C> CinstantiateWithConfig(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(resultinstanceofIClientConfigAware){((IClientConfigAware) result).initWithNiwsConfig(config);}// 处理自动织入if(context!= null){
				context.getAutowireCapableBeanFactory().autowireBean(result);}}return result;}}//## 父类 org.springframework.cloud.context.named.NamedContextFactorypublicabstractclassNamedContextFactory<CextendsNamedContextFactory.Specification>implementsDisposableBean, ApplicationContextAware{// 维护Ribbon客户端对应的ApplicationContext上下文private Map<String, AnnotationConfigApplicationContext> contexts=newConcurrentHashMap<>();// 维护Ribbon客户端的@Configuration配置类private Map<String, C> configurations=newConcurrentHashMap<>();private ApplicationContext parent;private Class<?> defaultConfigType;// 默认配置类为 RibbonClientConfigurationprivatefinal String propertySourceName;// 默认为 ribbonprivatefinal String propertyName;// 默认读取RibbonClient名的属性为ribbon.client.namepublicNamedContextFactory(Class<?> defaultConfigType, String propertySourceName,
			String propertyName){this.defaultConfigType= defaultConfigType;this.propertySourceName= propertySourceName;this.propertyName= propertyName;}// 如果包含Client上下文直接返回// 如果不包含,调用createContext(name),并放入contexts集合protected AnnotationConfigApplicationContextgetContext(String name){if(!this.contexts.containsKey(name)){synchronized(this.contexts){if(!this.contexts.containsKey(name)){this.contexts.put(name,createContext(name));}}}returnthis.contexts.get(name);}// 创建名为name的RibbonClient的ApplicationContext上下文protected AnnotationConfigApplicationContextcreateContext(String name){
		AnnotationConfigApplicationContext context=newAnnotationConfigApplicationContext();// configurations集合中是否包含当前Client相关配置类,包含即注入到ApplicationContextif(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(newMapPropertySource(this.propertySourceName,
				Collections.<String, Object>singletonMap(this.propertyName, name)));// 设置父ApplicationContext,这样可以使得当前创建的子ApplicationContext可以使用父上下文中的Beanif(this.parent!= null){// Uses Environment from parent as well as beans
			context.setParent(this.parent);}
		context.refresh();//刷新Contextreturn context;}public<T> TgetInstance(String name, Class<T> type){
		AnnotationConfigApplicationContext context=getContext(name);if(BeanFactoryUtils.beanNamesForTypeIncludingAncestors(context,
				type).length>0){return context.getBean(type);}return null;}}

context.refresh()这一行断点,内存结构如下:
在这里插入图片描述

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)publicclassRibbonNacosAutoConfiguration{}

configurations配置类集合是根据@RibbonClient@RibbonClients注解配置的,分别有 针对具体某个RibbonClient的配置 和 default默认配置,详见后面的 “3.2.4 RibbonClient创建"
所在在configrattions可以看到nacos针对ribbon配置

在这里插入图片描述
context.refresh()执行时,就会注册上面两个核心的configuration:

NacosRibbonClientConfiguration配置如下:

@Configuration(proxyBeanMethods=false)@ConditionalOnRibbonNacospublicclassNacosRibbonClientConfiguration{@Autowiredprivate PropertiesFactory propertiesFactory;@Bean@ConditionalOnMissingBeanpublic 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=newNacosServerList(nacosDiscoveryProperties);
      serverList.initWithNiwsConfig(config);return serverList;}@Bean@ConditionalOnMissingBeanpublic NacosServerIntrospectornacosServerIntrospector(){returnnewNacosServerIntrospector();}}

RibbonClientConfiguration配置如下:

@Import({OkHttpRibbonConfiguration.class, RestClientRibbonConfiguration.class, HttpClientRibbonConfiguration.class})publicclassRibbonClientConfiguration{@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@Autowiredprivate PropertiesFactory propertiesFactory;@Bean@ConditionalOnMissingBeanpublic IClientConfigribbonClientConfig(){
		DefaultClientConfigImpl config=newDefaultClientConfigImpl();
		config.loadProperties(this.name);return config;}@Bean@ConditionalOnMissingBeanpublic IRuleribbonRule(IClientConfig config){if(this.propertiesFactory.isSet(IRule.class, name)){returnthis.propertiesFactory.get(IRule.class, config, name);}
		ZoneAvoidanceRule rule=newZoneAvoidanceRule();
		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.
 */@OverridepublicvoidloadProperties(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){thrownewRuntimeException(String.format("Property %s is invalid", prop));}}}

根据如上注释,如果你没有在项目中指定ribbon相关配置,那么会使用DefaultClientConfigImpl中的默认静态配置,如果Spring enviroment中包含“ribbon.配置项”这类针对所有Client的配置会被加载进来,有“client名.ribbon.配置项”这类针对某个Client的配置信息也会被加载进来
静态配置如下:
在这里插入图片描述

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

上面说完IClientConfig配置项是如何加载的,按道理说其中已经包含了当前RibbonClient使用哪个核心接口实现类的配置,但Spring Cloud在此处定义了自己的实现逻辑

@Autowiredprivate PropertiesFactory propertiesFactory;@Bean@ConditionalOnMissingBeanpublic IRuleribbonRule(IClientConfig config){// 查看propertiesFactory是否有关于当前接口的配置,如有就使用,并创建实例返回if(this.propertiesFactory.isSet(IRule.class, name)){returnthis.propertiesFactory.get(IRule.class, config, name);}// spring cloud 默认配置
	ZoneAvoidanceRule rule=newZoneAvoidanceRule();
	rule.initWithNiwsConfig(config);return rule;}

下面看看PropertiesFactory的逻辑

publicclassPropertiesFactory{@Autowiredprivate Environment environment;private Map<Class, String> classToProperty=newHashMap<>();publicPropertiesFactory(){
		classToProperty.put(ILoadBalancer.class,"NFLoadBalancerClassName");
		classToProperty.put(IPing.class,"NFLoadBalancerPingClassName");
		classToProperty.put(IRule
  • 作者:雷X峰
  • 原文链接:https://blog.csdn.net/mapleleafforest/article/details/107128003
    更新时间:2022-06-16 10:27:49