SpringCloud配置中心-Config

     本文主要讨论原理,不涉及使用示例。

 搭建Config Server

SpringCloud Config支持通过git、svn等搭建配置中心。因为目前使用git管理代码比较常见,所以接下来介绍通过git搭建配置中心。

1  配置示例

以下git相关的是一些常用的配置:

spring.cloud.config.server.git.uri= Git仓库地址

spring.cloud.config.server.git.searchPaths=仓库中配置文件所在路径

spring.cloud.config.server.git.username= Git账号

spring.cloud.config.server.git.password= Git密码

配置后,可以通过一下url模式访问指定的配置文件的内容,例如:

http://localhost:7001/{application}/{profile}[/{label}]

http://localhost:7001/{application}-{profile}.yml

http://localhost:7001/{label}/{application}-{profile}.yml

http://localhost:7001/{application}-{profile}.properties

http://localhost:7001/{label}/{application}-{profile}.properties

2  整体结构

SpringCloud配置中心-Config

远程git仓库:用来存储配置文件的地方,多环境配置文件使用 {application}-{profile}.yml、{application}-{profile}.propreties

本地git仓库:缓存从远程git仓库中获取的配置信息,即便是远程git不能用,也可以从本地仓库中加载配置内容。

Config Server:分布式配置中心,指定了git仓库uri、搜索路径、访问账号和密码等。

微服务应用:配置客户端(Config Client),指定应用名、配置中心url、环境名、分支名等。

3  启动流程

微服务应用启动,根据微服务系统中配置的应用名(application)、环境名(profile)、分支名(label),向Config Server请求配置信息。

Config Server根据配置的Git(或SVN)仓库信息加上客户端传来的配置定位信息去查配置信息的路径。

Config Server执行git clone命令,将配置信息下载到本地Git仓库中,将配置信息加载到Spring的ApplicationContext读取内容返回给客户端(微服务应用)

客户端将内容加载到ApplicationContext,配置内容的优先级大于客户端内部的配置内容,进行忽略

注意:Config Server从Git上拉取配置项以后,会缓存在本地。

 

4  源码分析

1)   EnvironmentRepository

在前面「整体结构」部分中,我们看到ConfigServer从仓库中获取配置信息,然后给到其他微服务,这里ConfigServer与仓库的操作都是通过EnvironmentRepository来实现的。EnvironmentRepository有一下几种:

NativeEnvironmentRepository

spring.profiles.active=native时使用,从应用的配置文件中加载

EnvironmentEncryptorEnvironmentRepository

加密场景

PassthruEnvironmentRepository

Native时其实使用的是这个类来处理的

SvnKitEnvironmentRepository

通过svn来管理配置项时

JGitEnvironmentRepository

通过git来管理配置项时。

参考EnvironmentRepositoryConfiguration类中的代码,如下所示:

@Configuration
@Profile("native")
protected static class NativeRepositoryConfiguration {
   //
代码略去

   @Bean
   public EnvironmentRepository environmentRepository() {
      return new NativeEnvironmentRepository(this.environment);
   }
}

@Configuration
@ConditionalOnMissingBean(EnvironmentRepository.class)
protected static class GitRepositoryConfiguration {
   //
代码略去

   @Bean
   public EnvironmentRepository environmentRepository() {
      MultipleJGitEnvironmentRepository repository = new MultipleJGitEnvironmentRepository(this.environment);
      if (this.server.getDefaultLabel()!=null) {
        repository.setDefaultLabel(this.server.getDefaultLabel());
      }
      return repository;
   }
}

@Configuration
@Profile("subversion")
protected static class SvnRepositoryConfiguration {
   //
代码略去

   @Bean
   public EnvironmentRepository environmentRepository() {
      SvnKitEnvironmentRepository repository = new SvnKitEnvironmentRepository(this.environment);
      if (this.server.getDefaultLabel()!=null) {
        repository.setDefaultLabel(this.server.getDefaultLabel());
      }
      return repository;
   }
}

上面的所有类都实现了EnvironmentRepository接口,这个接口之定义了一个方法,用来从Repository中查找需要的数据项。

public interface EnvironmentRepository {

      Environment findOne(String application, String profile, String label);

}

 

2)   以git为例,简单介绍仓库代码管理过程

服务器启动时,属性设置完以后就会根据cloneOnStart的值决定是否从远程git仓库clone代码,如下JGitEnvironmentRepository#afterPropertiesSet方法所示:

public void afterPropertiesSet() throws Exception {
   Assert.state(getUri() != null,
         "You need to configure a uri for the git repository");
   if (this.cloneOnStart) {
      initClonedRepository();
   }
}

 

/**
 * Clones the remote repository and then opens a connection to it.
 * @throws GitAPIException
 * @throws IOException
 */
private void initClonedRepository() throws GitAPIException, IOException {
   if (!getUri().startsWith(FILE_URI_PREFIX)) {
      deleteBaseDirIfExists();
      Git git = cloneToBasedir();
      if (git != null) {
         git.close();
      }
      git = openGitRepository();
      if (git != null) {
         git.close();
      }
   }

}

从源码中我们看到,首先会将配置从git上clone到本地,然后再进行其他的操作。接着就本地的git仓库中获取指定的数据了,见AbstractScmEnvironmentRepository#findOne代码,如下所示:

@Override
public synchronized Environment findOne(String application, String profile, String label) {
   NativeEnvironmentRepository delegate = new NativeEnvironmentRepository(
         getEnvironment());
   Locations locations = getLocations(application, profile, label);
   delegate.setSearchLocations(locations.getLocations());
   Environment result = delegate.findOne(application, profile, "");
  result.setVersion(locations.getVersion());
   result.setLabel(label);
   return this.cleaner.clean(result, getWorkingDirectory().toURI().toString(),
         getUri());
}

注意代码中使用的是NativeEnvironmentRepository,这个是以本地的文件作为EnvironmentRepository的。

 

 搭建Client

1  配置说明

在大家server部分我们提到了application、profile、label,他们分别是通过一下配置项配置的。

spring.application.name:对应配置文件规则中的{application}部分

spring.cloud.config.profile:对应配置文件规则中的{profile}部分

spring.cloud.config.label:对应配置文件规则中的{label}部分

spring.cloud.config.uri:配置中心config-server的地址

示例如下:

spring.application.name=demo

spring.cloud.config.profile=dev

spring.cloud.config.label=master

那么可以通过以下url访问:http://localhost:7001/demo/dev

 

2  源码分析

ConfigClientProperties#override方法中,我们可以看到通过客户端配置的spring.application.name、spring.cloud.config.profile、spring.cloud.config.label、spring.cloud.config.uri来生成ConfigClientProperties对象,而ConfigClientProperties中包含了要获取那个应用、分支、环境的配置信息。如下所示:

public static final String ConfigClientProperties

.PREFIX = "spring.cloud.config"

 

public ConfigClientProperties override(
     org.springframework.core.env.Environment environment) {
   ConfigClientProperties override = new ConfigClientProperties();
   BeanUtils.copyProperties(this, override);
  override.setName(environment.resolvePlaceholders("${" + ConfigClientProperties.PREFIX + ".name:${spring.application.name:application}}"));
   if (environment.containsProperty(ConfigClientProperties.PREFIX + ".profile")) {
      override.setProfile(environment.getProperty(ConfigClientProperties.PREFIX + ".profile"));
   }
   if (environment.containsProperty(ConfigClientProperties.PREFIX + ".label")) {
      override.setLabel(
           environment.getProperty(ConfigClientProperties.PREFIX + ".label"));
   }
   return override;
}