sc-config:如何配置客户端

386 阅读5分钟

这是我参与更文挑战的第 13 天,活动详情查看: 更文挑战

《配置中心 Spring Cloud Config 详解》系列文章更新,一起在技术的路上精进!本系列文章将会介绍Spring Cloud 中提供了分布式配置中心Spring Cloud Config。应用服务中除了实现系统功能的代码,还需要连接资源和其它应用,经常有很多需要在外部配置的数据去调整应用的行为,如切换不同的数据库,设置功能开关等。随着微服务的不断增加,需要系统具备可伸缩和可扩展性,除此之外就是管理相当多的服务实例的配置数据。在应用的开发阶段由各个服务自治,但是到了生产环境之后会给运维带来很大的麻烦,特别是微服务的规模比较大,配置的更新更为麻烦。为此,系统需要建立一个统一的配置管理中心。

在前面的文章,我们进一步介绍了 Spring Cloud Config 服务端获取配置和 Resource 的各个端点及其实现。本文将会接会介绍客户端实现的一些细节。

配置客户端

Spring Boot 的应用能够立刻体验 Spring Cloud Config Server 带来的优势,而且还能够意外收获与环境变化事件有关的特性。当我们在 pom 中添加了 spring-cloud-config-client 依赖,即可对 Spring Cloud Config 进行初始化配置。客户端应用在启动时有两种配置方式:

  • 通过 HTTP URI 指定 Config Server
  • 通过服务发现指定 Config Server

与之相关的配置类为 spring.factories 文件中定义的启动上下文:

# Bootstrap components
org.springframework.cloud.bootstrap.BootstrapConfiguration=\
org.springframework.cloud.config.client.ConfigServiceBootstrapConfiguration,\
org.springframework.cloud.config.client.DiscoveryClientConfigServiceBootstrapConfiguration

配置客户端应用在引入spring-cloud-starter-config 依赖后,使得其配置的Bean都会在SpingApplicatin启动前加入到它的上下文里去。下面我们分别介绍这两种启动方式。

通过HTTP URI指定Config Server

通过HTTP URI指定Config Server,是每一个客户端应用默认的启动方式,当config client启动时,通过 spring.cloud.config.uri(如果不配置,默认是"http://localhost:8888")属性绑定到Config Server,并利用获取到的远端环境属性初始化好Spring的环境。

结果是,所有想要获取Config Server中的配置信息的客户端应用,都需要在环境变量中配置spring.cloud.config.uri

@Configuration
@EnableConfigurationProperties
public class ConfigServiceBootstrapConfiguration {

	@Bean
	public ConfigClientProperties configClientProperties() {
		ConfigClientProperties client = new ConfigClientProperties(this.environment);
		return client;
	}

	@Bean
	@ConditionalOnMissingBean(ConfigServicePropertySourceLocator.class)
	@ConditionalOnProperty(value = "spring.cloud.config.enabled", matchIfMissing = true)
	public ConfigServicePropertySourceLocator configServicePropertySource(ConfigClientProperties properties) {
		ConfigServicePropertySourceLocator locator = new ConfigServicePropertySourceLocator(
				properties);
		return locator;
	}

	//...
}

ConfigServiceBootstrapConfiguration配置了两个bean的初始化,分别为ConfigClientPropertiesConfigServicePropertySourceLocatorConfigClientProperties是对ConfigClient的属性进行配置,而ConfigServicePropertySourceLocator用于从远程服务器上请求我们的配置,并注册到 Spring 容器中的Environment对象中。 在Bootstrap配置类中,还有对于retry拦截器的初始化配置,引入spring-retry的依赖,下面是对于retry拦截器的配置。

	@ConditionalOnProperty(value = "spring.cloud.config.failFast")
	@ConditionalOnClass({ Retryable.class, Aspect.class, AopAutoConfiguration.class })
	@Configuration
	@EnableRetry(proxyTargetClass = true)
	@Import(AopAutoConfiguration.class)
	@EnableConfigurationProperties(RetryProperties.class)
	protected static class RetryConfiguration {

		@Bean
		@ConditionalOnMissingBean(name = "configServerRetryInterceptor")
		public RetryOperationsInterceptor configServerRetryInterceptor(
				RetryProperties properties) {
			return RetryInterceptorBuilder
					.stateless()
					.backOffOptions(properties.getInitialInterval(),
							properties.getMultiplier(), properties.getMaxInterval())
					.maxAttempts(properties.getMaxAttempts()).build();
		}
	}

spring.cloud.config.failFast的值为true,且引入了retry和aop的依赖,则会初始化RetryConfiguration类。在该类中注册了Bean名为configServerRetryInterceptorRetryOperationsInterceptor,如果不设置,将会使用RetryProperties默认属性值。

public class ConfigClientProperties {
	//配置的前缀
	public static final String PREFIX = "spring.cloud.config";
	//用来标识是否允许获取远端的配置,默认为true
	private boolean enabled = true;
	// 从远端获取配置的默认profile,默认为default
	private String profile = "default";
	// 获取远端配置的应用名,对应于环境变量spring.application.name,默认为application
	@Value("${spring.application.name:application}")
	private String name;
	// 拉取远端配置的标签名,默认的是在config server中设置(如git默认为master)
	private String label;
	//远端配置服务器的地址,默认为http://localhost:8888
	private String uri = "http://localhost:8888";
	...
}

可以看到,ConfigClientProperties中定义了profile、应用名、标签、远端服务器的地址等属性。这些都是config client启动时必需的信息,如果没有这些客户端的配置,将不能正确地从Config Server获取其对应的配置信息。

另一个属性资源的定位器类ConfigServicePropertySourceLocator依赖于客户端应用配置的属性信息,从远程服务器上请求该应用的配置。下面我们具体看一下该实现类。

public class ConfigServicePropertySourceLocator implements PropertySourceLocator {
	@Retryable(interceptor = "configServerRetryInterceptor")
	public PropertySource<?> locate(Environment environment) {
		ConfigClientProperties properties = this.defaultProperties.override(environment);
		CompositePropertySource composite = new CompositePropertySource("configService");
		RestTemplate restTemplate = this.restTemplate == null ? getSecureRestTemplate(properties)
				: this.restTemplate; // 1
		...
		try {
			String[] labels = new String[] { "" };
			if (StringUtils.hasText(properties.getLabel())) {
				labels = StringUtils.commaDelimitedListToStringArray(properties.getLabel()); // 2
			}
			String state = ConfigClientStateHolder.getState(); // 3
			for (String label : labels) {
				Environment result = getRemoteEnvironment(restTemplate,
						properties, label.trim(), state); // 4
				if (result != null) {
					if (result.getPropertySources() != null) { // 5
						for (PropertySource source : result.getPropertySources()) {
							Map<String, Object> map = (Map<String, Object>) source
									.getSource();
							composite.addPropertySource(new MapPropertySource(source
									.getName(), map));
						}
					}
					if (StringUtils.hasText(result.getState()) || StringUtils.hasText(result.getVersion())) { // 6
						HashMap<String, Object> map = new HashMap<>();
						putValue(map, "config.client.state", result.getState());
						putValue(map, "config.client.version", result.getVersion());
						composite.addFirstPropertySource(new MapPropertySource("configClient", map));
					}
					return composite;
				}
			}
		}
		if (properties.isFailFast()) { // 7
			throw new IllegalStateException(
					"Could not locate PropertySource ..", error);
		}
		return null;
	}
}
  1. RestTemplate 对象是否初始化,如果没有则调用 getSecureRestTemplate,并传入完整前缀的 ConfigClientProperties;
  2. 将传入的 label 转换成 labels 数组,label 的格式诸如dev,test
  3. 这边使用了 ThreadLocal 保存请求中头部的 X-Config-State;
  4. 尝试 labels 数组,通过指定的配置服务器信息,循环调用获取远端的环境配置信息;
  5. PropertySources的设置,当使用XML时,result.getPropertySources()可能为空;
  6. 其他信息的设置,客户端的状态以及版本号等;
  7. FailFast,如果设置快速响应失败,失败时抛出异常。

小结

本文主要介绍了 Spring Cloud Config 客户端的实现的一些细节。下面的文章将会介绍客户端实现中是如何通过 HTTP URI 指定 Config Server。