spring cloud config是一個基於http協議的遠程配置實現方式。經過統一的配置管理服務器進行配置管理,客戶端經過https協議主動的拉取服務的的配置信息,完成配置獲取。git
spring cloud config的使用方式很是簡單,spring cloud config server默認的實現方式是git管理配置,官方文檔介紹已經詳細說明有幾種使用方式。下面看一下git的spring cloud config server實現方式。github
boot版本:springBoot : 2.0.1.RELEASEweb
1.依賴管理(.pom)spring
<dependencyManagement>
<dependencies>
<dependency>
<!-- SpringCloud 全部子項目 版本集中管理. 統一全部SpringCloud依賴項目的版本依賴-->
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Finchley.SR3</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>
</dependencies>
2.啓動類註解(@EnableConfigServer)bootstrap
@SpringBootApplication @EnableConfigServer public class App { private final static Logger log = LoggerFactory.getLogger(App.class); public static void main(String[] args) { SpringApplication.run(App.class, args); log.info("服務啓動成功"); } }
3.application.properties 文件配置瀏覽器
server.port = 8888 spring.application.name=config
# 選擇的同步工具 spring.profiles.active=git # git地址 spring.cloud.config.server.git.uri=https://github.com/wuzhenzhao/spring-cloud-config-repo.git
# 搜索目錄 spring.cloud.config.server.git.search-paths=properties # git倉庫default-label默認值是master spring.cloud.config.server.git.default-label=master
若是倉庫是私有的還須要配置以下:安全
spring.cloud.config.server.git.username= spring.cloud.config.server.git.password=
github 上有以下3個環境的配置文件,內容分別是foo=hello-dev / foo=hello-pro / foo=hello-test服務器
就這樣完成了簡單的配置,啓動程序,若是想獲取開發配置,訪問http://localhost:8888/config/dev能夠讀取到config-dev.properties的配置內容。請求配置的參數經過路徑參數設置。網絡
完成了這些準備工做以後,咱們就能夠經過瀏覽器、 POSTMAN或CURL等工具直接來訪問咱們的配置內容了。訪問配置信息的URL與配置文件的映射關係以下所示:app
上面的 url 會映射 {application}-{profile} .properties 對應的配置文件,其中 {label} 對應Git上不一樣的分支,默認爲 master 。咱們能夠嘗試構造不一樣的 url 來訪問不一樣的配置內容, 例如:http://localhost:8888/{applicationName}/{profile}/{label} , label分支,不傳的話默認master。並得到以下返回信息:
同時, 咱們能夠看到 config-server 的控制檯中還輸出了下面的內容,配置服務器在從 Git 中獲取配置信息後, 會存儲 一 份在 config-server 的文件系統中, 實質上config-server 是經過 git clone 命令將配置內容複製了一 份在本地存儲, 而後讀取這些內容並返回給微服務應用進行加載。config-server 經過 Git 在本地倉庫暫存,能夠有效防止當 Git 倉庫出現故障而引發沒法加載配置信息的狀況。
1.依賴管理(.pom)
<dependencyManagement>
<dependencies>
<dependency>
<!-- SpringCloud 全部子項目 版本集中管理. 統一全部SpringCloud依賴項目的版本依賴-->
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Finchley.SR3</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
</dependencies>
2.bootstrap.properties 配置
server.port = 8889 spring.application.name=config spring.cloud.config.label=master spring.cloud.config.profile=test spring.cloud.config.uri=http://localhost:8888/
3.測試類
@RestController public class TestRestController { @Value("${foo}") String foo; @RequestMapping(value = "/hello") public String hello(){ return foo; } }
4.啓動類註解 @EnableConfigServer
@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Import({ConfigServerConfiguration.class}) public @interface EnableConfigServer { }
項目啓動後訪問測試類的接口,能夠根據配置文件中的spring.cloud.config.profile所配置的環境獲取到不一樣的值。
註解 EnableConfigServer 能夠開啓應用服務對配置中心的支持。當開啓以後,配置服務器就會在啓動時進行自動配置。經過該註解尋找到以下配置。
@Configuration public class ConfigServerConfiguration { public ConfigServerConfiguration() { } @Bean public ConfigServerConfiguration.Marker enableConfigServerMarker() { return new ConfigServerConfiguration.Marker(); } class Marker { Marker() { } } }
能夠看到好像沒有繼續前進的入口了,僅僅是注入了這個Marker類嘛?經過尋找咱們能夠發現該類惟一被引用的地方,就是以下類。
@Configuration @ConditionalOnBean({Marker.class}) @EnableConfigurationProperties({ConfigServerProperties.class}) @Import({EnvironmentRepositoryConfiguration.class,
CompositeConfiguration.class,
ResourceRepositoryConfiguration.class,
ConfigServerEncryptionConfiguration.class,
ConfigServerMvcConfiguration.class}) public class ConfigServerAutoConfiguration { public ConfigServerAutoConfiguration() { } }
@ConditionalOnBean(Marker.class)表示當裝配了ConfigServerConfiguration.Marker的實例時纔會執行ConfigServerAutoConfiguration的處理。
這裏又另外引入了5個配置類。
EnvironmentRepositoryConfiguration: 環境變量存儲相關的配置類
CompositeConfiguration:組合方式的環境倉庫配置類
ResourceRepositoryConfiguration:資源倉庫相關的配置類
ConfigServerEncryptionConfiguration:加密斷點相關的配置類
ConfigServerMvcConfiguration:對外暴露的MVC端點控制器的配置類,
對於服務端來講,其基本職責就是可以將具體存儲中的配置信息先拿到,而後提供出 API 供客戶端來調用。下面從ConfigServerAutoConfiguration 中 import的這些配置類來具體看下實現。
重點是 EnvironmentRepositoryConfiguration 類。
@Configuration @EnableConfigurationProperties({
SvnKitEnvironmentProperties.class,
JdbcEnvironmentProperties.class,
NativeEnvironmentProperties.class,
VaultEnvironmentProperties.class}) @Import({CompositeRepositoryConfiguration.class,
JdbcRepositoryConfiguration.class,
VaultRepositoryConfiguration.class,
SvnRepositoryConfiguration.class,
NativeRepositoryConfiguration.class,
GitRepositoryConfiguration.class,
DefaultRepositoryConfiguration.class}) public class EnvironmentRepositoryConfiguration { ....... }
這裏的@Import又引入了7種配置類,會發現其實恰好對應config server的幾種實現方式git的實現方式使用的配置類就是GitRepositoryConfiguration。以GitRepositoryConfiguration的爲例分析。
@Configuration @Profile({"git"}) class GitRepositoryConfiguration extends DefaultRepositoryConfiguration { GitRepositoryConfiguration() { } }
GitRepositoryConfiguration 集成了 DefaultRepositoryConfiguration,這也說明了 Spring Cloud Config 默認使用的是Git。不一樣的配置類實現都會被標註一個@Profile,能夠經過這個來激活相應的配置類;具體作法是在配置服務端的 application.properties(application.yml) 中來指定:
spring.profile.active=git
DefaultRepositoryConfiguration 的 ConditionalOnMissingBean 能夠知道,若是上下文中沒有 EnvironmentRepository,那麼就使用 DefaultRepositoryConfiguration。最後DefaultRepositoryConfiguration是封裝了一個 MultipleJGitEnvironmentRepository 這個bean。
@Configuration @ConditionalOnMissingBean( value = {EnvironmentRepository.class}, search = SearchStrategy.CURRENT ) class DefaultRepositoryConfiguration { @Autowired private ConfigurableEnvironment environment; @Autowired private ConfigServerProperties server; @Autowired( required = false ) private TransportConfigCallback customTransportConfigCallback; DefaultRepositoryConfiguration() { } @Bean public MultipleJGitEnvironmentRepository defaultEnvironmentRepository(
MultipleJGitEnvironmentRepositoryFactory gitEnvironmentRepositoryFactory,
MultipleJGitEnvironmentProperties environmentProperties) throws Exception { return gitEnvironmentRepositoryFactory.build(environmentProperties); } }
咱們能夠先來看一下 MultipleJGitEnvironmentRepository 類的類圖:
這裏咱們能夠發現 MultipleJGitEnvironmentRepository 實現了 InitializingBean 接口,那麼在配置加載完之後必定要調用 afterPropertiesSet 方法,咱們來看一下具體都作了什麼:
public synchronized void afterPropertiesSet() throws Exception { Assert.state(this.getUri() != null, "You need to configure a uri for the git repository."); this.initialize(); if (this.cloneOnStart) { this.initClonedRepository(); } }
從源碼中咱們看到,首先會將配置從git上clone到本地,而後再進行其餘的操做。接着就本地的git倉庫中獲取指定的數據了。
MultipleJGitEnvironmentRepository 的頂層接口是 EnvironmentRepository ,固然其餘的實現也都是實現了這個接口的。另一個須要關注的是 SearchPathLocator。EnvironmentRepository:定義了獲取指定應用服務環境信息的方法,返回一個Enviroment
SearchPathLocator 中有一個內部類 Locations ,Locdations中定義了應用服務配置存儲信息。
public interface EnvironmentRepository { Environment findOne(String application, String profile, String label); }
AbstractScmEnvironmentRepository 實現了 AbstractScmAccessor 和 EnvironmentRepository ,主要就是EnvironmentRepository 中 findOne 的實現:
public synchronized Environment findOne(String application, String profile, String label) {
//新建了一個本地倉庫做爲代理倉庫來使用 NativeEnvironmentRepository delegate = new NativeEnvironmentRepository(this.getEnvironment(), new NativeEnvironmentProperties());
//獲取本地倉庫中指定應用的位置 Locations locations = this.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, this.getWorkingDirectory().toURI().toString(), this.getUri()); }
getLocations 是一個模板方法,Config Server中提供了三種實現:
以 git 方式爲例 最後會調到 JGitEnvironmentRepository#getLocations 方法:
public synchronized Locations getLocations(String application, String profile, String label) { if (label == null) { label = this.defaultLabel; } // 獲取最新的版本號 String version = this.refresh(label);
// 根據最新的版本號返回 Locations 定位到資源的搜索路徑 return new Locations(application, profile, label, version, this.getSearchLocations(this.getWorkingDirectory(), application, profile, label)); }
refresh 方法作的做用就是刷新本地倉庫的配置狀態,這樣就能保證每次都能拉取到最新的配置信息。下面來分析這個方法。
public String refresh(String label) { Git git = null; String var20; try {
// 建立一個git客戶端 git = this.createGitClient();
// 是否須要執行 git pull if (this.shouldPull(git)) { FetchResult fetchStatus = this.fetch(git, label); if (this.deleteUntrackedBranches && fetchStatus != null) { this.deleteUntrackedLocalBranches(fetchStatus.getTrackingRefUpdates(), git); } // 獲取後checkout,這樣咱們就能夠得到任何新的分支、tag等。 this.checkout(git, label); this.tryMerge(git, label); } else {
// 沒有什麼要更新,因此只是checkout和merge。
// 合併是由於遠程分支之前可能已經更新過
this.checkout(git, label); this.tryMerge(git, label); } // 返回當前的版本 var20 = git.getRepository().findRef("HEAD").getObjectId().getName(); } catch (RefNotFoundException var15) { throw new NoSuchLabelException("No such label: " + label, var15); } catch (NoRemoteRepositoryException var16) { throw new NoSuchRepositoryException("No such repository: " + this.getUri(), var16); } catch (GitAPIException var17) { throw new NoSuchRepositoryException("Cannot clone or checkout repository: " + this.getUri(), var17); } catch (Exception var18) { throw new IllegalStateException("Cannot load environment", var18); } finally { try { if (git != null) { git.close(); } } catch (Exception var14) { this.logger.warn("Could not close git repository", var14); } } return var20; }
這個裏面基本就是經過git客戶端的一些操做。先是檢查遠程倉庫的狀態,而後判斷本地倉庫是否要執行刷新操做。若是有狀態更新,好比新的提交時,Git客戶端就會執行fetch,而後再進行merge,更新到本地倉庫。最終是裝配一個MultipleJGitEnvironmentRepository的bean,實際每種配置類的實現的最終都是裝配一個EnvironmentRepository
的子類,能夠認爲,有一個地方最終會引用到EnvironmentRepository的bean,在ConfigServerAutoConfiguration類中曾經導入了ConfigServerMvcConfiguration 類,而這個類正是向外暴露出端口供客戶端訪問的配置,在裏面組裝了兩個的 Controller:
@Bean public EnvironmentController environmentController(EnvironmentRepository envRepository, ConfigServerProperties server) { EnvironmentController controller = new EnvironmentController(this.encrypted(envRepository, server), this.objectMapper); controller.setStripDocumentFromYaml(server.isStripDocumentFromYaml()); controller.setAcceptEmpty(server.isAcceptEmpty()); return controller; } @Bean @ConditionalOnBean({ResourceRepository.class}) public ResourceController resourceController(ResourceRepository repository, EnvironmentRepository envRepository, ConfigServerProperties server) { ResourceController controller = new ResourceController(repository, this.encrypted(envRepository, server)); return controller; }
而這兩個應該是客戶端獲取服務端配置的入口,以 EnvironmentController 爲例查看代碼以下。
@RequestMapping({"/{name}/{profiles}/{label:.*}"}) public Environment labelled(@PathVariable String name, @PathVariable String profiles, @PathVariable String label) { if (name != null && name.contains("(_)")) { name = name.replace("(_)", "/"); } if (label != null && label.contains("(_)")) { label = label.replace("(_)", "/"); } Environment environment = this.repository.findOne(name, profiles, label); if (this.acceptEmpty || environment != null && !environment.getPropertySources().isEmpty()) { return environment; } else { throw new EnvironmentNotFoundException("Profile Not found"); } }
注意這裏的EnvironmentController#repository屬性就是GitRepositoryConfiguration實例化的MultipleJGitEnvironmentRepository,若是是別的實現方式就是別的EnvironmentRepository。能夠看出」/{name}/{profiles}/{label:.*}」路徑參數正好與咱們的請求方式相對應,所以Config Server是經過創建一個RestController來接收讀取配置請求的,而後使用EnvironmentRepository來進行配置查詢,最後返回一個這個對象的JSON。
public class Environment { private String name; private String[] profiles; private String label; private List<PropertySource> propertySources; private String version; private String state; ......... }
經過訪問 http://localhost:8888/config/pro/master 會獲得信息以下,而這個信息正是 Environment 類
{ "name":"config", "profiles":["pro"], "label":"master", "version":"e6a0ce237a9f9e05608e5c276a9365f0fdd67ed6", "state":null, "propertySources":[{ "name":"https://github.com/wuzhenzhao/spring-cloud-config-repo.git/properties/config-pro.properties", "source":{"foo":"hello-pro"} }] }
Config Server除了支持Git倉庫以外, 也能 使用SYN倉庫, 只須要作以下配置。 在 pom.xml中引入SYN的依賴配置,讓ConfigServer擁有讀取SYN內容的能力:
<dependency>
<groupId>org.tmatesoft.svnkit</groupId>
<artifactId>svnkit</artifactId>
<version>1.8.10</version>
</dependency>
在application.properties中使用SVN的配置屬性來指定SVN服務器的位置, 以及訪問的帳戶名與密碼:
spring.cloud.config.server.svn.uri=svn://localhost:443/wuzz/config-repo
spring.cloud.config.server.svn.username = username spring.cloud.config.server.svn.password = password
經過上面的配置修改,ConfigServer就可使用SVN做爲倉庫來存儲配置文件了, 而對於客戶端來講, 這個過程是透明的, 因此不須要作任何變更。
在使用了Git或SVN倉庫以後, 文件都會在ConfigServer的本地文件系統中存儲一 份,這點上面也體現了。這 些文 件默認會被存 儲於以 config-repo 爲前綴的臨 時 目錄中, 好比名爲/tmp/config-repo-<隨機數>的目錄。 因爲其隨機性以及臨時目錄的特性, 可能會有 一些不可預知的後果, 爲了不未來可能會出現的問題, 最好的辦法就是指定一 個固定的位置來存儲這些重要信息。咱們只須要經過spring.cloud.config.server.git.basedir或 spring.cloud.config.server.svn.basedir來配置一 個咱們準備好的目錄便可。
Config Server 還有 一 個「 屬性覆蓋 」的特性, 它可讓開發人員爲全部的應用提供配置屬性,只須要經過 spring.cloud.config.server.overrides 屬性來設置鍵值對的參數,這些參數會以 Map 的方式加載到客戶端的配置中。 好比:
spring.cloud.config.server.overrides.name = wuzz spring.cloud.config.server.overrides.from = hangzhou
經過該屬性配置的參數,不會被 Spring Cloud 的客戶端修改,而且 Spring Cloud 客戶端從 Config Server 中獲取配置信息時,都會取得這些配置信息。 利用該特性能夠方便地爲Spring Cloud 應用配置 一 些共同屬性或是默認屬性。 固然,這些屬性並不是強制的,咱們能夠經過改變客戶端中更高優先級的配置方式(好比,配置環境變量或是系統屬性),來選擇是否使用 Config Server 提供的默認值。
因爲配置中心存儲的內容比較敏感,作 一 定的安全處理是必需的。 爲配置中心實現安全保護的方式有不少,好比物理網絡限制、 0Auth2 受權等。 不過,因爲咱們的微服務應用和配置中心都構建於 Spring Boot 基礎上,因此與 Spring Security 結合使用會更加方便。咱們只須要在配置中心的 pom.xml 中加入 spring-boot-starter-security 依賴,不須要作任何其餘改動就能實現對配置中心訪問的安全保護。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
大多數狀況下,咱們並不會使用隨機生成密碼的機制。 咱們能夠在配置文件中指定用戶和密碼, 好比:
security.user.name=user security.user.password=37cc5635-559b-4e6f-b633-7e932b813f73
因爲咱們已經爲 config-server 設置了安全保護,若是這時候鏈接到配置中心的客戶端中沒有設置對應的安全信息,在獲取配置信息時會返回401錯誤。 因此,須要經過配置的方式在客戶端中加入安全信息來經過校驗, 好比:
spring.cloud.config.username=user spring.cloud.config.password=37cc5635-559b-4e6f-b633-7e932b813f73
此外,spring cloud 還提供了加解密。這裏就不說明了。由於配置比較繁瑣,並且實際環境中應用的比較少。
當要將配置中心部署到生產環境中時, 與服務註冊中心 一 樣, 咱們也但願它是 一 個高可用的應用。 Spring Cloud Config 實現服務端的高可用很是簡單, 主要有如下兩種方式。
傳統模式: 不須要爲這些服務端作任何額外的配置, 只須要遵照 一 個配置規則, 將全部的 Config Server 都指向同 一 個 Git 倉庫, 這樣全部的配置內容就經過統 一 的共享文件系統來維護。而客戶端在指定 Config Server 位置時,只須要配置 Config Server上層的負載均衡設備地址便可, 就以下圖所示的結構。
服務模式:除了上面這種傳統的實現模式以外, 咱們也能夠將 Config Server 做爲 一個普通的微服務應用,歸入 Eureka 的服務治理體系中。 這樣咱們的微服務應用就能夠經過配置中心的服務名來獲取配置信息, 這種方式比起傳統的實現模式來講更加有利於維護, 由於對於服務端的負載均衡配置和客戶端的配置中心指定都經過服務治理機制一 並解決了, 既實現了高可用, 也實現了自維護。
主要步驟是在服務端添加 Eureka依賴,啓用@EnableDiscoveryClient 註解。在 application.properties 中配置參數eureka.client.serviceUrl.defaultZone以指定服務註冊中心的位置。而後進行配置客戶端,也是須要添加Eureka依賴,開啓@EnableDiscoveryClient註解,而後添加如下配置信息
spring.application.name=wuzz-config-client server.port=7002 #指定服務註冊中心, 用於服務的註冊與發現 eureka.client.serviceUrl.defaultZone = http: / /localhost: 1111/eureka/ #開啓經過服務來訪間 Config Server 的功能 spring.cloud.config.discovery.enabled=true #指定 Config Server 註冊的服務名 spring.cloud.config.discovery.serviceid=config-server spring.cloud.config.profile=dev
Spring Cloud Config的客戶端會預先加載不少其餘信息,而後再開始鏈接ConfigServer進行屬性的注入。 當咱們構建的應用較爲複雜的時候, 可能在鏈接ConfigServer以前花費較長的啓動時間, 而在 一 些特殊場景下, 咱們又但願能夠快速知道當前應用是否能順利地從ConfigSe rve r獲取到配置信息, 這對在初期構建調試環境時, 能夠減小不少等待啓動的時間。 要實現客戶端優先判斷ConfigSe rve r獲取是否正常, 並快速響應失敗內容, 只需在bootstrap.properties中配置參數spring.cloud.config.failFast= true便可。
可是, 若只是由於網絡波動等其餘間歇性緣由致使的問題, 直接啓動失敗彷佛代價有些高。 因此, Config 客戶端還提供了自動重試的功能, 在開啓重試功能前, 先確保已經配置了 spring.cloud.config.failFast=true, 再進行下面的操做。先添加如下依賴
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
不須要再作其餘任何配置, 啓動客戶端應用, 在控制檯中能夠看到以下內容。 客戶端在鏈接 Config Server 失敗以後, 會繼續嘗試, 直到第 6 次失敗後, 才返回錯誤信息。 經過這樣的重試機制, 能夠避免 一 些間歇性問題引發的失敗致使客戶端應用沒法啓動的狀況。
若對默認的最大重試次數和重試間隔等設置不滿意,還能夠經過下面的參數進行調整。
有時候, 咱們須要對配置 內容作 一 些實時更新, 那麼Spring Cloud Config是否能夠實現呢?
在config-client的pom.xml中新增spring-boot-starter-actuator監控模塊。 其中包含了/refresh 端點的 實現,該端點將用於實現客戶端應用配置信息的從新獲取與刷新。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
在修改了Git上面的配置信息以後。經過POST請求發送到http://localhost:8888/refresh .再去獲取配置信息就是修改以後的,若沒法訪問改端口請加上配置:
#開啓這個端點,便於等等咱們查看配置 management.endpoints.web.exposure.include = refresh