Spring Cloud Config服务端----详解一

概要

对于config client来说

  • {application}映射到客户端的“spring.application.name”;

  • {profile}映射到客户端上的“spring.profiles.active”(逗号分隔列表); 和

  • {label}这是一个服务器端功能,标记“版本”的一组配置文件。

客户端作如下如下配置文件

spring:
  cloud:
    config:
      uri: http://localhost:8082
      label: master
  application:
    name: boot

则服务器将从application.yml创建Environmentboot.yml(以boot.yml优先)。
在这里插入图片描述

如下配置文件

spring:
  cloud:
    config:
      uri: http://localhost:8082
      label: master
  application:
    name: boot
  profiles:
    active: test,dev

如果YAML文件在其中指定了{profile},则特定环境的配置文件具有较高优先级(并且按照列出的配置文件的顺序)
在这里插入图片描述
访问方式

/{application}/{profile}[/{label}]
/{application}-{profile}.yml
/{label}/{application}-{profile}.yml
/{application}-{profile}.properties
/{label}/{application}-{profile}.properties

Git

EnvironmentRepository的默认实现使用Git后端,要更改存储库的位置,可以在Config Server中设置“spring.cloud.config.server.git.uri”配置属性(例如application.yml)。如果您使用file:前缀进行设置,则应从本地存储库中运行,以便在没有服务器的情况下快速方便地启动,但在这种情况下,服务器将直接在本地存储库中进行操作,而不会克隆“远程”资源库,因为配置服务器永远不会更改“远程”资源库)。

该存储库实现将HTTP资源的{label}参数映射到git标签(commit id, branch name, or tag)。如果git分支或标签名称包含斜杠(“/”),则应使用特殊字符串“()”指定HTTP URL中的标签,以避免与其他URL路径模糊。例如,如果标签是foo/bar,则替换斜杠将导致类似于foo()bar的标签。如果您使用像curl这样的命令行客户端(例如使用引号将其从shell中转出来),请小心URL中的方括号。

在这里插入图片描述

启动时克隆远程存储库

默认情况下,服务器在首次请求配置时克隆远程存储库。可以将服务器配置为在启动时克隆存储库,需要将 clone-on-start设置为true:

spring:
  cloud:
    config:
      server:
        git:
          uri: https://github.com/MRgzhen/cloud-config
          clone-on-start: true
       

在此示例中,服务器在启动时克隆special的special仓库。其他存储库将不被克隆,直到请求该存储库配置。

配置服务器启动时克隆存储库设置可以帮助启动时快速识别错误配置的源(例如,无效的存储库URI)。不启用cloneOnStart时,服务器启动成功,如果配置错误或无效的配置源,并且不会检测到错误,直到应用程序从该配置源请求配置为止。

源码

public class JGitEnvironmentRepository extends AbstractScmEnvironmentRepository
		implements EnvironmentRepository, SearchPathLocator, InitializingBean {
		@Override
	public synchronized void afterPropertiesSet() throws Exception {
		Assert.state(getUri() != null, MESSAGE);
		initialize();
		if (this.cloneOnStart) {// 当cloneOnStart为真,启动时加载配置信息
			initClonedRepository();
		}
	}

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

	}
}

在这里插入图片描述

认证

跳过SSL证书验证

通过将git.skipSslValidation属性设置为true(默认值为false),可以禁用配置服务器对Git服务器的SSL证书的验证。

spring:
  cloud:
    config:
      server:
        git:
          uri: https://github.com/MRgzhen/cloud-config
          skipSslValidation: true

用户名密码认证

要在远程存储库上使用HTTP基本身份验证,请分别添加“username”和“password”属性(不在URL中),例如

spring:
  cloud:
    config:
      server:
        git:
          uri: https://github.com/MRgzhen/cloud-config
          username: username
          password: strongpassword

设置Http超时

您可以配置配置服务器等待获取HTTP连接的时间(以秒为单位)。使用该git.timeout属性。

spring:
  cloud:
    config:
      server:
        git:
          uri: https://github.com/MRgzhen/cloud-config
          timeout: 4

源码

创建git,如果本地文件夹已经存在(如初始化的时候clone-on-start设置成true),则直接从本地获取;如果本地文件夹不存在,则创建git,并clone信息,如果在配置文件中spring.cloud.config.server.git.uri设置成file:前缀,则直接从本地获取,如果设置成git服务器则从git服务器中获取

public class JGitEnvironmentRepository extends AbstractScmEnvironmentRepository
		implements EnvironmentRepository, SearchPathLocator, InitializingBean {

	@Override
	public synchronized Locations getLocations(String application, String profile,
			String label) {
		if (label == null) {
			label = this.defaultLabel;
		}
		// 获取git文件信息
		String version = refresh(label);
		return new Locations(application, profile, label, version,
				getSearchLocations(getWorkingDirectory(), application, profile, label));
	}
	
	public String refresh(String label) {
		Git git = null;
		try {
			// 创建git
			git = createGitClient();
			if (shouldPull(git)) {
				FetchResult fetchStatus = fetch(git, label);
				if (this.deleteUntrackedBranches && fetchStatus != null) {
					deleteUntrackedLocalBranches(fetchStatus.getTrackingRefUpdates(),
							git);
				}
	
				checkout(git, label);
				tryMerge(git, label);
			}
			else {
				// nothing to update so just checkout and merge.
				// Merge because remote branch could have been updated before
				checkout(git, label);
				tryMerge(git, label);
			}
			// always return what is currently HEAD as the version
			return git.getRepository().findRef("HEAD").getObjectId().getName();
		}
		catch (RefNotFoundException e) {
			throw new NoSuchLabelException("No such label: " + label, e);
		}
		catch (NoRemoteRepositoryException e) {
			throw new NoSuchRepositoryException("No such repository: " + getUri(), e);
		}
		catch (GitAPIException e) {
			throw new NoSuchRepositoryException(
					"Cannot clone or checkout repository: " + getUri(), e);
		}
		catch (Exception e) {
			throw new IllegalStateException("Cannot load environment", e);
		}
		finally {
			try {
				if (git != null) {
					git.close();
				}
			}
			catch (Exception e) {
				this.logger.warn("Could not close git repository", e);
			}
		}
	}

	private Git createGitClient() throws IOException, GitAPIException {
		File lock = new File(getWorkingDirectory(), ".git/index.lock");
		if (lock.exists()) {
			this.logger.info("Deleting stale JGit lock file at " + lock);
			lock.delete();
		}
		// 本地文件夹是否存在
		if (new File(getWorkingDirectory(), ".git").exists()) {
			// 从本地获取
			return openGitRepository();
		}
		else {
			// 创建git,并clone信息
			return copyRepository();
		}
	}

	// 从本地获取git信息
	private Git openGitRepository() throws IOException {
		Git git = this.gitFactory.getGitByOpen(getWorkingDirectory());
		return git;
	}
	
	// 创建git,并clone信息
	private synchronized Git copyRepository() throws IOException, GitAPIException {
		deleteBaseDirIfExists();
		getBasedir().mkdirs();
		Assert.state(getBasedir().exists(), "Could not create basedir: " + getBasedir());
		if (getUri().startsWith(FILE_URI_PREFIX)) {// 如果是file:开头的则从本地获取
			return copyFromLocalRepository();
		}
		else {
			return cloneToBasedir();// 从远程获取
		}
	}	
	private Git cloneToBasedir() throws GitAPIException {// 从远程获取
		CloneCommand clone = this.gitFactory.getCloneCommandByCloneRepository()
				.setURI(getUri()).setDirectory(getBasedir());
		configureCommand(clone);
		try {
			return clone.call();
		}
		catch (GitAPIException e) {
			this.logger.warn("Error occured cloning to base directory.", e);
			deleteBaseDirIfExists();
			throw e;
		}
	}
	private void configureCommand(TransportCommand<?, ?> command) {// 创建git命令
		command.setTimeout(this.timeout);// 设置超时时间
		if (this.transportConfigCallback != null) {
			command.setTransportConfigCallback(this.transportConfigCallback);
		}
		CredentialsProvider credentialsProvider = getCredentialsProvider();
		if (credentialsProvider != null) {
			command.setCredentialsProvider(credentialsProvider);
		}
	}

	private CredentialsProvider getCredentialsProvider() {// 设置uri、username、password、skipSslValidation等属性
		return this.gitCredentialsProviderFactory.createFor(this.getUri(), getUsername(),
				getPassword(), getPassphrase(), isSkipSslValidation());
	}
}

git获取信息

强行拉入Git存储库

配置中心通过git从远程git库,有时本地的拷贝被污染,这时配置中心无法从远程库更新本地配置,设置force-pull=true,则强制从远程库中更新本地库

spring:
  cloud:
    config:
      server:
        git:
          uri: https://github.com/MRgzhen/cloud-config
          force-pull: true

删除Git存储库中未跟踪的分支

由于Spring Cloud Config Server在签出分支到本地存储库后具有远程git存储库的克隆(例如,通过标签获取属性),它将永久保留此分支或直到下一个服务器重新启动(这将创建新的本地存储库)。因此可能存在删除远程分支但仍然可以获取其本地副本的情况。
为了保持本地存储库分支的清洁和远程 - deleteUntrackedBranches可以设置属性。它将使Spring Cloud Config Server 强制从本地存储库中删除未跟踪的分支。例:

spring:
   cloud:
     config:
       server:
         git:
          uri: https://github.com/MRgzhen/cloud-config
          deleteUntrackedBranches: true 

deleteUntrackedBranches属性的默认值是false

源码

在这里插入图片描述

public class JGitEnvironmentRepository extends AbstractScmEnvironmentRepository
		implements EnvironmentRepository, SearchPathLocator, InitializingBean {
	public String refresh(String label) {
		Git git = null;
		try {
			git = createGitClient();
			if (shouldPull(git)) {// 是否需要强制拉去,需要先从远程仓库fetch,再通过checkout从本地仓库拉去
				FetchResult fetchStatus = fetch(git, label);
				if (this.deleteUntrackedBranches && fetchStatus != null) {
					deleteUntrackedLocalBranches(fetchStatus.getTrackingRefUpdates(),
							git);
				}
				// checkout after fetch so we can get any new branches, tags, ect.
				checkout(git, label);
				tryMerge(git, label);
			}
			else {// 只从本地仓库中checkout拉去
				// nothing to update so just checkout and merge.
				// Merge because remote branch could have been updated before
				checkout(git, label);
				tryMerge(git, label);
			}
			// always return what is currently HEAD as the version
			return git.getRepository().findRef("HEAD").getObjectId().getName();
		}
		catch (RefNotFoundException e) {
			throw new NoSuchLabelException("No such label: " + label, e);
		}
		catch (NoRemoteRepositoryException e) {
			throw new NoSuchRepositoryException("No such repository: " + getUri(), e);
		}
		catch (GitAPIException e) {
			throw new NoSuchRepositoryException(
					"Cannot clone or checkout repository: " + getUri(), e);
		}
		catch (Exception e) {
			throw new IllegalStateException("Cannot load environment", e);
		}
		finally {
			try {
				if (git != null) {
					git.close();
				}
			}
			catch (Exception e) {
				this.logger.warn("Could not close git repository", e);
			}
		}
	}

	protected boolean shouldPull(Git git) throws GitAPIException {
		boolean shouldPull;

		if (this.refreshRate > 0 && System.currentTimeMillis()
				- this.lastRefresh < (this.refreshRate * 1000)) {
			return false;
		}

		Status gitStatus = git.status().call();
		boolean isWorkingTreeClean = gitStatus.isClean();
		String originUrl = git.getRepository().getConfig().getString("remote", "origin",
				"url");
	
		// forcePull为真或空间被改变,则可以强制更新(当远程和本地空间同时被污染,并且没有开启forcePull,此时只从本地仓库checkOut,不能获取远程新的内容)
		if (this.forcePull && !isWorkingTreeClean) {
			shouldPull = true;
			logDirty(gitStatus);
		}
		else {// 空间只要干净都强制更新
			shouldPull = isWorkingTreeClean && originUrl != null;
		}
		if (!isWorkingTreeClean && !this.forcePull) {
			this.logger.info("Cannot pull from remote " + originUrl
					+ ", the working tree is not clean.");
		}
		return shouldPull;
	}
}

占位符

Spring Cloud Config Server支持带有占位符的git存储库URL,其中包含{application}和{profile}以及{label}占位符。因此,您可以使用类似于以下的结构配置

spring:
  cloud:
    config:
      server:
        git:
          uri: https://github.com/MRgzhen/{application}

此外,在用占位符时如果包含“/” 特殊字符,那么占位符在HTTP的URL中应该使用“(_)” 替代,如以下示例所示:

spring:
  cloud:
    config:
      server:
        git:
          uri: https://github.com/{application}

其中{application}为organization/application,在请求时应以下格式:organization(_)application。

# 去github组织名为myorg,仓库名为config下找application-dev.yml文件
http://localhost:8082/myorg(_)config/dev.yml
public class MultipleJGitEnvironmentRepository extends JGitEnvironmentRepository {
	@Override
	public Environment findOne(String application, String profile, String label) {
		for (PatternMatchingJGitEnvironmentRepository repository : this.repos.values()) {
			if (repository.matches(application, profile, label)) {
				for (JGitEnvironmentRepository candidate : getRepositories(repository,
						application, profile, label)) {
					try {
						if (label == null) {
							label = candidate.getDefaultLabel();
						}
						Environment source = candidate.findOne(application, profile,
								label);
						if (source != null) {
							return source;
						}
					}
					catch (Exception e) {
						if (this.logger.isDebugEnabled()) {
							this.logger.debug(
									"Cannot load configuration from " + candidate.getUri()
											+ ", cause: (" + e.getClass().getSimpleName()
											+ ") " + e.getMessage(),
									e);
						}
						continue;
					}
				}
			}
		}
		// 替换uri中的通配符
		JGitEnvironmentRepository candidate = getRepository(this, application, profile,
				label);
		if (label == null) {
			label = candidate.getDefaultLabel();
		}
		if (candidate == this) {
			return super.findOne(application, profile, label);
		}
		return candidate.findOne(application, profile, label);
	}

	JGitEnvironmentRepository getRepository(JGitEnvironmentRepository repository,
			String application, String profile, String label) {
		if (!repository.getUri().contains("{")) {
			return repository;
		}
		String key = repository.getUri();

		// cover the case where label is in the uri, but no label was sent with the
		// request
		if (key.contains("{label}") && label == null) {
			label = repository.getDefaultLabel();
		}
		if (application != null) {
			key = key.replace("{application}", application);
		}
		if (profile != null) {
			key = key.replace("{profile}", profile);
		}
		if (label != null) {
			key = key.replace("{label}", label);
		}
		if (!this.placeholders.containsKey(key)) {
			this.placeholders.put(key, getRepository(repository, key));
		}
		return this.placeholders.get(key);
	}
}

子目录存储

每个存储库还可以选择将配置文件存储在子目录中,搜索这些目录的模式可以指定为searchPaths。例如在顶层:

spring:
  cloud:
    config:
      server:
        git:
          uri: https://github.com/MRgzhen/cloud-config
          searchPaths: foo,{application}

服务器在顶级和foo/子目录以及以{application}开头的子目录中搜索配置文件。

源码

public class JGitEnvironmentRepository extends AbstractScmEnvironmentRepository
		implements EnvironmentRepository, SearchPathLocator, InitializingBean {
	@Override
	public synchronized Locations getLocations(String application, String profile,
			String label) {
		if (label == null) {
			label = this.defaultLabel;
		}
		// clone信息到本地
		String version = refresh(label);
		// 通过getSearchLocations获取子目录信息
		return new Locations(application, profile, label, version,
				getSearchLocations(getWorkingDirectory(), application, profile, label));
	}
}
public abstract class AbstractScmAccessor implements ResourceLoaderAware {
	protected String[] getSearchLocations(File dir, String application, String profile,
			String label) {
		// 搜索的目录foo,’{application}‘,并增加默认路径,最终路径会变成/, foo,'{application}'三个组成
		String[] locations = this.searchPaths;
		if (locations == null || locations.length == 0) {
			locations = AbstractScmAccessorProperties.DEFAULT_LOCATIONS;// 默认路径/
		}
		else if (locations != AbstractScmAccessorProperties.DEFAULT_LOCATIONS) {
			locations = StringUtils.concatenateStringArrays(
					AbstractScmAccessorProperties.DEFAULT_LOCATIONS, locations);
		}
		Collection<String> output = new LinkedHashSet<String>();
		for (String location : locations) {
			String[] profiles = new String[] { profile };
			if (profile != null) {
				profiles = StringUtils.commaDelimitedListToStringArray(profile);
			}
			String[] apps = new String[] { application };
			if (application != null) {
				apps = StringUtils.commaDelimitedListToStringArray(application);
			}
			// 替换{application}、{profile}、{label}、
			for (String prof : profiles) {
				for (String app : apps) {
					String value = location;
					if (app != null) {
						value = value.replace("{application}", app);
					}
					if (prof != null) {
						value = value.replace("{profile}", prof);
					}
					if (label != null) {
						value = value.replace("{label}", label);
					}
					if (!value.endsWith("/")) {
						value = value + "/";
					}
					output.addAll(matchingDirectories(dir, value));
				}
			}
		}
		return output.toArray(new String[0]);
	}
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章