Ribbon与Eureka整合分析(四)、客户端配置延迟加载和启用饥饿加载

客户端配置延迟加载和启用饥饿加载

该博文为Ribbon与Eureka整合分析系列文章中的第四篇,主要介绍客户端所需配置,默认情况下,如何在创建客户端时,才加载配置,以及如何在启动时,加载客户端配置(即饥饿加载)。

一、客户端配置加载

最近在调试和研究Ribbon源码的时候,发现,客户端所需配置,默认情况下,并不是随着容器启动而加载,而是在使用时(请求经过LoadBalancerInterceptor拦截器处理),才进行加载。之所以能达到这个效果,是借助于Spring的AnnotationConfigApplicationContext类。该类的注释信息如下:

Standalone application context, accepting annotated classes as input - in particular {@link Configuration @Configuration}-annotated classes, but also plain {@link org.springframework.stereotype.Component @Component} types and JSR-330 compliant classes using {@code javax.inject} annotations. Allows for registering classes one by one using {@link #register(Class…)} as well as for classpath scanning using {@link #scan(String…)}.

In case of multiple {@code @Configuration} classes, @{@link Bean} methods defined in later classes will override those defined in earlier classes. This can be leveraged to deliberately override certain bean definitions via an extra {@code @Configuration} class.

即通过AnnotationConfigApplicationContext,达到Bean注册效果。另外,通过其构造方法上注释,需要先调用register方法,然后调用refresh方法。

/**
	 * Create a new AnnotationConfigApplicationContext that needs to be populated
	 * through {@link #register} calls and then manually {@linkplain #refresh refreshed}.
	 */
	public AnnotationConfigApplicationContext() {
		this.reader = new AnnotatedBeanDefinitionReader(this);
		this.scanner = new ClassPathBeanDefinitionScanner(this);
	}

如下代码,是Ribbon根据客户端serviceId,创建AnnotationConfigApplicationContext的方法。其中register,都是从configurations属性获取需要注册的内容,且分为两部分,key为客户端serviceId的注册以及以default开头的注册。

protected AnnotationConfigApplicationContext createContext(String name) {
		AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
		if (this.configurations.containsKey(name)) {
			for (Class<?> configuration : this.configurations.get(name)
					.getConfiguration()) {
				context.register(configuration);
			}
		}
		for (Map.Entry<String, C> entry : this.configurations.entrySet()) {
			if (entry.getKey().startsWith("default.")) {
				for (Class<?> configuration : entry.getValue().getConfiguration()) {
					context.register(configuration);
				}
			}
		}
		context.register(PropertyPlaceholderAutoConfiguration.class,
				this.defaultConfigType);
		context.getEnvironment().getPropertySources().addFirst(new MapPropertySource(
				this.propertySourceName,
				Collections.<String, Object> singletonMap(this.propertyName, name)));
		if (this.parent != null) {
			// Uses Environment from parent as well as beans
			context.setParent(this.parent);
		}
		context.setDisplayName(generateDisplayName(name));
		context.refresh();
		return context;
	}

那么,configurations这个属性值,是如何生成的呢?这里涉及到ImportBeanDefinitionRegistrar接口,该接口注释信息如下

Interface to be implemented by types that register additional bean definitions when processing @{@link Configuration} classes. Useful when operating at the bean definition level (as opposed to {@code @Bean} method/instance level) is desired or necessary.

Along with {@code @Configuration} and {@link ImportSelector}, classes of this type may be provided to the @{@link Import} annotation (or may also be returned from an {@code ImportSelector}).

An {@link ImportBeanDefinitionRegistrar} may implement any of the following {@link org.springframework.beans.factory.Aware Aware} interfaces, and their respective methods will be called prior to {@link #registerBeanDefinitions}:

RIbbon针对于该接口,提供RibbonClientConfigurationRegistrar实现类,用于处理RibbonClients以及RibbonClient注解。然后将收集到的数据信息,注册为RibbonClientSpecification bean对象。

@Override
	public void registerBeanDefinitions(AnnotationMetadata metadata,
			BeanDefinitionRegistry registry) {
		Map<String, Object> attrs = metadata.getAnnotationAttributes(
				RibbonClients.class.getName(), true);
		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"));
		}
		Map<String, Object> client = metadata.getAnnotationAttributes(
				RibbonClient.class.getName(), true);
		String name = getClientName(client);
		if (name != null) {
			registerClientConfiguration(registry, name, client.get("configuration"));
		}
	}

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

在解析这里,基于RibbonClients注解的defaultConfiguration属性,增加default前缀进行标识,其余的通过value(或name属性,对应于客户端serviceId),作为RibbonClientSpecification的name属性。

经过上述分析,默认情况下,创建客户端配置时,会通过RibbonAutoConfiguration和RibbonEurekaAutoConfiguration两个配置类,完成客户端注册操作。
在这里插入图片描述
在上述getInstance处,在AnnotationConfigApplicationContext创建完成之后,能根据指定类型,从客户端上下文环境中,获取指定的配置bean。
在这里插入图片描述

二、客户端饥饿加载

默认情况下,客户端所需配置,是在第一次请求到来之后,才进行创建。这种情况下,会增加第一次请求处理时长,从而增加请求处理超时的风险。为此,Ribbon增加RibbonEagerLoadProperties配置类,用于指定是否需要在容器启动时加载指定客户端所需配置信息。默认是关闭的。

@ConfigurationProperties(prefix = "ribbon.eager-load")
public class RibbonEagerLoadProperties {
	private boolean enabled = false;
	private List<String> clients;

	public boolean isEnabled() {
		return enabled;
	}

	public void setEnabled(boolean enabled) {
		this.enabled = enabled;
	}

	public List<String> getClients() {
		return clients;
	}

	public void setClients(List<String> clients) {
		this.clients = clients;
	}
}

该配置类,在RibbonAutoConfiguration配置类中使用。当ribbon.eager-load.enabled设置为true,将开启如下配置。而RibbonApplicationContextInitializery为Spring事件监听处理类,用于监听ApplicationReadyEvent事件。

@Bean
	@ConditionalOnProperty(value = "ribbon.eager-load.enabled")
	public RibbonApplicationContextInitializer ribbonApplicationContextInitializer() {
		return new RibbonApplicationContextInitializer(springClientFactory(),
				ribbonEagerLoadProperties.getClients());
	}

在接收到ApplicationReadyEvent事件后,调用init方法,先创建客户端所需上下文环境。

protected void initialize() {
		if (clientNames != null) {
			for (String clientName : clientNames) {
				this.springClientFactory.getContext(clientName);
			}
		}
	}

到此,Ribbon的懒加载配置项如下:

ribbon.eager-load.enabled=true
ribbon.eager-load.clients=service_id1,service_id2

总结

客户端配置加载

通过RibbonClientConfigurationRegistrar收集RibbonClients和RibbonClient注解信息,并将其配置为RibbonClientSpecification Bean。然后基于AnnotationConfigApplicationContext,分别针对于客户端,构建其上下文环境信息,然后在获取IRule等配置。

客户端饥饿加载

默认情况下,客户端所需配置,是在接收到请求之后,才会进行创建。这种模式,会增加第一个请求处理时长,从而增加第一个请求处理超时的风险,Ribbon增加饥饿加载配置,通过ribbon.eager-load.enabled=true,开启饥饿加载,同时通过ribbon.eager-load.clients配置项指定,那些客户端需要饥饿加载。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

我要做个有钱人2020

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值