这是我参与更文挑战的第 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的初始化,分别为ConfigClientProperties和ConfigServicePropertySourceLocator。ConfigClientProperties是对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名为configServerRetryInterceptor的RetryOperationsInterceptor,如果不设置,将会使用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;
}
}
- RestTemplate 对象是否初始化,如果没有则调用 getSecureRestTemplate,并传入完整前缀的 ConfigClientProperties;
- 将传入的 label 转换成 labels 数组,label 的格式诸如
dev,test; - 这边使用了 ThreadLocal 保存请求中头部的 X-Config-State;
- 尝试 labels 数组,通过指定的配置服务器信息,循环调用获取远端的环境配置信息;
- PropertySources的设置,当使用XML时,
result.getPropertySources()可能为空; - 其他信息的设置,客户端的状态以及版本号等;
- FailFast,如果设置快速响应失败,失败时抛出异常。
小结
本文主要介绍了 Spring Cloud Config 客户端的实现的一些细节。下面的文章将会介绍客户端实现中是如何通过 HTTP URI 指定 Config Server。