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]);
	}
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章