ribbon源码分析之自定义配置、全局配置

在上一文EnableDiscoveryClient没用了?Zookeeper是怎么和springboot配合做服务注册中心的?讲过了zk是怎么做服务注册和服务发现的,同时在spring.factories中我们还发现了RibbonZookeeperAutoConfiguration配置类,于是接下来翻阅了相关代码,对ribbon一探究竟

RibbonZookeeperAutoConfiguration

@Configuration
@EnableConfigurationProperties
@ConditionalOnZookeeperEnabled
@ConditionalOnBean(SpringClientFactory.class)
@ConditionalOnRibbonZookeeper
@AutoConfigureAfter(RibbonAutoConfiguration.class)
@RibbonClients(defaultConfiguration = ZookeeperRibbonClientConfiguration.class)
public class RibbonZookeeperAutoConfiguration {
}

一些常见的注解就不再赘述了,这里主要搞明白三个点:

1. SpringClientFactory

SpringClientFactory:会给每个Ribbon Client创建一个独立的Spring应用上下文ApplicationContext,并在其中加载对应的配置及Ribbon核心接口的实现类


/**
 * A factory that creates client, load balancer and client configuration instances. It
 * creates a Spring ApplicationContext per client name, and extracts the beans that it
 * needs from there.
 *
 * @author Spencer Gibb
 * @author Dave Syer
 */
public class SpringClientFactory extends NamedContextFactory<RibbonClientSpecification> {

该工厂提供getClient、getClientConfig、getLoadBalancer、getLoadBalancerContext以分别根据当前Ribbon客户端获取对应的负责方法执行的client、用于初始化客户端或负载均衡器以及方法执行的客户端配置(默认实现是 DefaultClientConfigImpl)、定义软件负载均衡器操作的接口以及负载均衡器的上下文接口

以上四个方法都使用到了getInstance方法,name代表当前Ribbon客户端,type代表要获取的实例类型,如IClient、IRule

public <C> C getInstance(String name, Class<C> type) {
		C instance = super.getInstance(name, type);
		if (instance != null) {
			return instance;
		}
		IClientConfig config = getInstance(name, IClientConfig.class);
		return instantiateWithConfig(getContext(name), type, config);
	}

先从父类NamedContextFactory中直接从客户端对应的ApplicationContext中获取实例,如果没有就根据IClientConfig中的配置找到具体的实现类,并通过反射初始化后放到Client对应的ApplicationContext中

// 使用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;
	}
    
}
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);
	}

此处会使用双关检索机制再次检查对应的上下文是否创建,若仍未创建,则调用createContext方法创建。

// 创建名为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;
	}
  1. 从configurations这个Map中根据RibbonClient name获取专门为其指定的configuration配置类,并注册到其对应的ApplicationContext上下文
  2. 从configurations这个Map中找到 default. 开头 的配置类,即为所有RibbonClient的默认配置,并注册到其对应的ApplicationContext上下文
  3. 如果不是开发者单独指定的话,前两项都是没有数据的,还会注册Spring Cloud的默认配置类RibbonClientConfiguration

那么为什么要引入SpringClientFactory会给每个Ribbon Client创建一个独立的Spring应用上下文ApplicationContext呢?

在一个微服务中,调用微服务 A 与调用微服务 B 的配置可能不同。比较简单的例子就是,A 微服务是一个简单的用户订单查询服务,接口返回速度很快,B 是一个报表微服务,接口返回速度比较慢。这样的话我们就不能对于调用微服务 A 和微服务 B 使用相同的超时时间配置。还有就是,我们可能对于服务 A 通过注册中心进行发现,对于服务 B 则是通过 DNS 解析进行服务发现,所以对于不同的微服务我们可能使用不同的组件,在 Spring 中就是使用不同类型的 Bean

2. RibbonAutoConfiguration

再讲SpringClientFactory时还剩下一个疑问:configurations这个map是怎么来的呢?

@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 {

	@Autowired(required = false)
	private List<RibbonClientSpecification> configurations = new ArrayList<>();

	@Autowired
	private RibbonEagerLoadProperties ribbonEagerLoadProperties;

	@Bean
	public HasFeatures ribbonFeature() {
		return HasFeatures.namedFeature("Ribbon", Ribbon.class);
	}

	@Bean
	public SpringClientFactory springClientFactory() {
		SpringClientFactory factory = new SpringClientFactory();
		factory.setConfigurations(this.configurations);
		return factory;
	}
}

可以看到RibbonAutoConfiguration这个自动配置类中自动注入了RibbonClientSpecification的列表,就是我们要的configurations,同时这个配置类会创建一个springClientFactory出来,并且吧自动注入的configurations设置为springClientFactory的configurations属性。

那么,configurations和RibbonClientSpecification是何时被注册进容器的呢?

3. RibbonClients

@Configuration
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE })
@Documented
@Import(RibbonClientConfigurationRegistrar.class)
public @interface RibbonClients {

	RibbonClient[] value() default {};

	Class<?>[] defaultConfiguration() default {};

}

这里需要注意的也只有RibbonClientConfigurationRegistrar这个类

首先RibbonClientConfigurationRegistrar实现了ImportBeanDefinitionRegistrar接口,表明这里会调用registerBeanDefinitions方法向容器中注册一些组件,看代码:

public void registerBeanDefinitions(AnnotationMetadata metadata,
			BeanDefinitionRegistry registry) {
			// 1、@RibbonClients注解
		Map<String, Object> attrs = metadata.getAnnotationAttributes(
				RibbonClients.class.getName(), true);
				figuration,即默认配置
        //     注册成以default.Classname.RibbonClientSpecification为名的RibbonClientSpecification
		if (attrs != null && attrs.containsKey("value")) {
			AnnotationAttributes[] clients = (AnnotationAttributes[]) attrs.get("value");
			for (AnnotationAttributes client : clients) {
				registerClientConfiguration(registry, getClientName(client),
						client.get("configuration"));
			}
		}
		if (attrs != null && attrs.containsKey("defaultConfiguration")) {
			String name;
			if (metadata.hasEnclosingClass()) {
				name = "default." + metadata.getEnclosingClassName();
			} else {
				name = "default." + metadata.getClassName();
			}
			registerClientConfiguration(registry, name,
					attrs.get("defaultConfiguration"));
		}
		// 2、@RibbonClient注解
        // 注册某个具体Ribbon Client的configuration配置类
		Map<String, Object> client = metadata.getAnnotationAttributes(
				RibbonClient.class.getName(), true);
		String name = getClientName(client);
		if (name != null) {
			registerClientConfiguration(registry, name, client.get("configuration"));
		}
	}

如上可知,configurations配置类集合是根据@RibbonClient 和 @RibbonClients 注解配置的,分别有 针对具体某个RibbonClient的配置 和 default默认配置,而以上注册的实际类型就是RibbonClientSpecification。

private void registerClientConfiguration(BeanDefinitionRegistry registry,
			Object name, Object configuration) {
		BeanDefinitionBuilder builder = BeanDefinitionBuilder
				.genericBeanDefinition(RibbonClientSpecification.class);
		builder.addConstructorArgValue(name);
		builder.addConstructorArgValue(configuration);
		registry.registerBeanDefinition(name + ".RibbonClientSpecification",
				builder.getBeanDefinition());
	}

4. ZookeeperRibbonClientConfiguration (RibbonClientConfiguration)

上面说是如何创建RibbonClient相关的ApplicationContext上下文及注册Ribbon Client相关的配置类的逻辑,在确定配置类后,其中会用到Ribbon的IClientConfig相关的客户端配置来加载Ribbon客户端相关的配置信息,如超时配置、具体创建哪个核心接口的实现类等,因为上面的源码是用zk来来做注册中心,为了通用性,这里可以从Spring Cloud默认注册的 RibbonClientConfiguration来一探究竟

4.1 IClientConfig

@Bean
	@ConditionalOnMissingBean
	public IClientConfig ribbonClientConfig() {
		DefaultClientConfigImpl config = new DefaultClientConfigImpl();
		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;
	}

可以看到先是创建了DefaultClientConfigImpl默认实现类,再config.loadProperties(this.name)加载当前Client相关的配置

@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的配置信息也会被加载进来,以下是静态默认配置

public static final Boolean DEFAULT_PRIORITIZE_VIP_ADDRESS_BASED_SERVERS = Boolean.TRUE;

	public static final String DEFAULT_NFLOADBALANCER_PING_CLASSNAME = "com.netflix.loadbalancer.DummyPing"; // DummyPing.class.getName();

    public static final String DEFAULT_NFLOADBALANCER_RULE_CLASSNAME = "com.netflix.loadbalancer.AvailabilityFilteringRule";

    public static final String DEFAULT_NFLOADBALANCER_CLASSNAME = "com.netflix.loadbalancer.ZoneAwareLoadBalancer";
    
    public static final boolean DEFAULT_USEIPADDRESS_FOR_SERVER = Boolean.FALSE;

    public static final String DEFAULT_CLIENT_CLASSNAME = "com.netflix.niws.client.http.RestClient";

    public static final String DEFAULT_VIPADDRESS_RESOLVER_CLASSNAME = "com.netflix.client.SimpleVipAddressResolver";

    public static final String DEFAULT_PRIME_CONNECTIONS_URI = "/";

    public static final int DEFAULT_MAX_TOTAL_TIME_TO_PRIME_CONNECTIONS = 30000;

    public static final int DEFAULT_MAX_RETRIES_PER_SERVER_PRIME_CONNECTION = 9;

    public static final Boolean DEFAULT_ENABLE_PRIME_CONNECTIONS = Boolean.FALSE;

    public static final int DEFAULT_MAX_REQUESTS_ALLOWED_PER_WINDOW = Integer.MAX_VALUE;

    public static final int DEFAULT_REQUEST_THROTTLING_WINDOW_IN_MILLIS = 60000;

    public static final Boolean DEFAULT_ENABLE_REQUEST_THROTTLING = Boolean.FALSE;

    public static final Boolean DEFAULT_ENABLE_GZIP_CONTENT_ENCODING_FILTER = Boolean.FALSE;

    public static final Boolean DEFAULT_CONNECTION_POOL_CLEANER_TASK_ENABLED = Boolean.TRUE;

    public static final Boolean DEFAULT_FOLLOW_REDIRECTS = Boolean.FALSE;

    public static final float DEFAULT_PERCENTAGE_NIWS_EVENT_LOGGED = 0.0f;

    public static final int DEFAULT_MAX_AUTO_RETRIES_NEXT_SERVER = 1;

    public static final int DEFAULT_MAX_AUTO_RETRIES = 0;

    public static final int DEFAULT_BACKOFF_INTERVAL = 0;
    
    public static final int DEFAULT_READ_TIMEOUT = 5000;

    public static final int DEFAULT_CONNECTION_MANAGER_TIMEOUT = 2000;

    public static final int DEFAULT_CONNECT_TIMEOUT = 2000;

    public static final Boolean DEFAULT_ENABLE_CONNECTION_POOL = Boolean.TRUE;
    
    @Deprecated
    public static final int DEFAULT_MAX_HTTP_CONNECTIONS_PER_HOST = 50;

    @Deprecated
    public static final int DEFAULT_MAX_TOTAL_HTTP_CONNECTIONS = 200;

    public static final int DEFAULT_MAX_CONNECTIONS_PER_HOST = 50;

    public static final int DEFAULT_MAX_TOTAL_CONNECTIONS = 200;

    public static final float DEFAULT_MIN_PRIME_CONNECTIONS_RATIO = 1.0f;

    public static final String DEFAULT_PRIME_CONNECTIONS_CLASS = "com.netflix.niws.client.http.HttpPrimeConnection";

    public static final String DEFAULT_SEVER_LIST_CLASS = "com.netflix.loadbalancer.ConfigurationBasedServerList";

    public static final String DEFAULT_SERVER_LIST_UPDATER_CLASS = "com.netflix.loadbalancer.PollingServerListUpdater";

    public static final int DEFAULT_CONNECTION_IDLE_TIMERTASK_REPEAT_IN_MSECS = 30000; // every half minute (30 secs)

    public static final int DEFAULT_CONNECTIONIDLE_TIME_IN_MSECS = 30000; // all connections idle for 30 secs

4.2 IRule & IPing & ribbonServerList &ILoadBalancer

以IRule为例,查看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;
	}

那么接下来看看this.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;
	}
}

如何自定义RibbonClient配置、全局配置

首先,Spring Cloud Netflix provides the following beans by default for ribbon (BeanType beanName: ClassName):
Spring Cloud 已经提供以下ribbon组件

  1. IClientConfig ribbonClientConfig: DefaultClientConfigImpl
  2. IRule ribbonRule: ZoneAvoidanceRule
  3. IPing ribbonPing: NoOpPing
  4. ServerList ribbonServerList: ConfigurationBasedServerList
  5. ServerListFilter ribbonServerListFilter: ZonePreferenceServerListFilter
  6. ILoadBalancer ribbonLoadBalancer: ZoneAwareLoadBalancer
  7. ServerListUpdater ribbonServerListUpdater: PollingServerListUpdater

如何自定义?

  1. @RibbonClient(name = “foo”, configuration = FooConfiguration.class) 是针对名为 foo 的RibbonClient的配置类
  2. 也可以使用@RibbonClients({@RibbonClient数组}) 的形式给某几个RibbonClient设置配置类
  3. @RibbonClients( defaultConfiguration = { xxx.class } ) 是针对所有RIbbonClient的默认配置

特别注意:

在这里插入图片描述
配置类必须是必须是@Configuration的,这样就必须注意,SpringBoot主启动类不能扫描到FooConfiguration,否则针对某个RibbonClient的配置就会变成全局的,原因是在创建每个RibbonClient时会为其创建ApplicationContext上下文,其parent就是主启动类创建的ApplicationContext,子ApplicationContext中可以使用父ApplicationContext中的Bean,且创建Bean时都使用了@ConditionalOnMissingBean,所以FooConfiguration如果被主启动类的上下文加载,且创建了比如IRule的实现类,在某个RIbbonClient创建其子ApplicationContext并@Bean想创建其自定义IRule实现类时,会发现parent ApplicationContext已经存在,就不会创建了,配置就失效了

如何解决?

  1. 配置类在主启动扫描路径之外
  2. 自定义注解,在扫描的时候显示排除
  3. 使用@RibbonClients{数组}指定配置类,配置类上不添加@Configuration的
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值