1. 程式人生 > >Spring Cloud系列(二十七) 分散式配置中心Spring Cloud Config——Finchley版本

Spring Cloud系列(二十七) 分散式配置中心Spring Cloud Config——Finchley版本

Spring Cloud Config是Spring Cloud團隊建立的一個全新專案,用來為分散式系統中的基礎設施和微服務應用提供集中化的外部配置支援,它分為服務端與客戶端兩個部分。其中服務端也稱為分散式配置中心,它是一個獨立的微服務應用,用來連線配置倉庫併為客戶端提供獲取配置資訊、加密/解密資訊等訪問介面;而客戶端則是微服務架構中的各個微服務應用或基礎設施,它們通過指定的配置中心來管理應用資源與業務相關的配置內容,並在啟動的時候從配置中心獲取和載入配置資訊。Spring Cloud Config實現了對服務端和客戶端中環境變數和屬性配置的抽象對映,所以它除了適用於Spring構建的應用程式之外,也可以在任何其他語言執行的應用程式中使用。由於Spring Cloud Config實現的配置中心預設採用Git來儲存配置資訊,所以使用Spring Cloud Config構建的配置伺服器,天然就支援對微服務應用配置資訊的版本管理,並且可以通過Git客戶端工具來方便的管理和訪問配置內容。當然它也提供了對其他儲存方式的支援,比如:SVN倉庫、本地化檔案系統。

在本文中,我們將學習如何構建一個基於Git儲存的分散式配置中心,並對客戶端進行改造,並讓其能夠從配置中心獲取配置資訊並繫結到程式碼中的整個過程。

配置服務端

準備配置倉庫

準備一個git倉庫,可以在碼雲或Github上建立都可以,這是我的git倉庫,具體如何建立git倉庫可以參考我這篇文章《建立GitHub倉庫並與本地Git繫結》。

構建配置中心

第一步,構建一個Spring Boot專案,命名config-server-vFinchley.RC2,並引入如下依賴

<parent>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-parent</artifactId>
	<version>2.0.2.RELEASE</version>
	<relativePath /> <!-- lookup parent from repository -->
</parent>

<properties>
	<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
	<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
	<java.version>1.8</java.version>
	<spring-cloud.version>Finchley.RC2</spring-cloud.version>
</properties>

<dependencies>
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-web</artifactId>
	</dependency>
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-test</artifactId>
    	<scope>test</scope>
	</dependency>

	<dependency>
		<groupId>org.springframework.cloud</groupId>
		<artifactId>spring-cloud-config-server</artifactId>
	</dependency>
</dependencies>

第二步,建立Application主類,新增@EnableConfigServer註解,開啟Spring Cloud Config的服務端功能。

@EnableConfigServer
@SpringBootApplication
public class Application {

	public static void main(String[] args) {
		SpringApplication.run(Application.class, args);
	}
}

第三步,建立application.yml配置檔案

spring:
  application:
    name: config-server
  cloud:
    config:
      server:
        git:
          uri: https://github.com/WYA1993/spring_cloud_config_demo
          search-paths:
          - config/config_repo/
server:
  port: 5666

其中git的配置資訊如下:

spring.cloud.config.server.git.uri:配置git倉庫位置

spring.cloud.config.server.git.search-paths:配置倉庫路徑下的相對搜尋位置(遠端倉庫中配置檔案所在路徑),可以配置多個,如果沒有配置此屬性,會預設在倉庫根路徑找,如果你的配置檔案在根路徑下的目錄內會找不到。

如果Git倉庫需要許可權訪問(公開倉庫不需要使用者名稱密碼),那麼可以通過配置下面的兩個屬性來實現:

spring.cloud.config.server.git.username:訪問Git倉庫的使用者名稱

spring.cloud.config.server.git.password:訪問Git倉庫的使用者密碼

第四步,啟動專案,確保沒有報錯。

四個配置檔案的內容依次為:

form=git-dev-1.0

form=git-prod-1.0

form=git-test-1.0

form=git-deafult-1.0

為了測試版本控制,加入一個新的分支config_label_test,建立上面的檔案,只是將form屬性的值改為2.0字尾。

第六步,接下來就可以訪問這些配置內容了。訪問配置資訊的URL與配置檔案的對映關係如下:

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

解釋一下上面的對映關係

  • / { 應用名 } / { 環境名 } [ / { 分支名 } ]
  • / { 應用名 } - { 環境名 }.yml
  • / { 應用名 } - { 環境名 }.properties
  • / { 分支名 } / { 應用名 } - { 環境名 }.yml
  • / { 分支名 } / { 應用名 } - { 環境名 }.properties

注意:

  1. 第一個規則的分支名是可以省略的,預設是master分支
  2. 無論你的配置檔案是properties,還是yml,只要是應用名+環境名能匹配到這個配置檔案,那麼就能取到
  3. 如果是想直接定位到沒有寫環境名的預設配置,那麼就可以使用default去匹配沒有環境名的配置檔案
  4. 使用第一個規則會匹配到預設配置
  5. 如果直接使用應用名來匹配,會出現404錯誤,此時可以加上分支名匹配到預設配置檔案
  6. 如果配置檔案的命名很由多個-分隔,此時直接使用這個檔名去匹配的話,會出現直接將內容以源配置檔案內容直接返回,內容前可能會有預設配置檔案的內容(已測試)

我們可以嘗試構造不同的url來訪問不同的配置內容,比如,要訪問master分支,repo應用的test環境,就可以訪問這個url:http://localhost:5666/repo/test/master,並獲得如下返回:

其餘四種對映的url示例(這四種是直接返回檔案的內容)

注意

1.如果propertySources值是空的,看一下你的配置檔案是否存在,檔案存在的話就看你application.yml的配置路徑是否寫對。

2.如果你報這個錯:

springboot: Finchley.RC2 Cannot load environment] with root cause
java.lang.StringIndexOutOfBoundsException: String index out of range: -1
    at java.lang.String.substring(String.java:1967) ~[na:1.8.0_131]
    at org.springframework.cloud.config.server.environment.HttpClientConfigurableHttpConnectionFactory.lookupHttpClientBuilder(HttpClientConfigurableHttpConnectionFactory.java:69) ~[spring-cloud-config-server-2.0.0.RC2.jar:2.0.0.RC2]
    at org.springframework.cloud.config.server.environment.HttpClientConfigurableHttpConnectionFactory.create(HttpClientConfigurableHttpConnectionFactory.java:56) ~[spring-cloud-config-server-2.0.0.RC2.jar:2.0.0.RC2]
    at org.eclipse.jgit.transport.TransportHttp.httpOpen(TransportHttp.java:830) ~[org.eclipse.jgit-4.11.0.201803080745-r.jar:4.11.0.201803080745-r]
    at org.eclipse.jgit.transport.TransportHttp.connect(TransportHttp.java:491) ~[org.eclipse.jgit-4.11.0.201803080745-r.jar:4.11.0.201803080745-r]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-8.5.31.jar:8.5.31]
    at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52) ~[tomcat-embed-websocket-8.5.31.jar:8.5.31]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-8.5.31.jar:8.5.31]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-8.5.31.jar:8.5.31]
    at org.springframework.boot.actuate.metrics.web.servlet.WebMvcMetricsFilter.filterAndRecordMetrics(WebMvcMetricsFilter.java:158) ~[spring-boot-actuator-2.0.3.RELEASE.jar:2.0.3.RELEASE]
    at org.springframework.boot.actuate.metrics.web.servlet.WebMvcMetricsFilter.filterAndRecordMetrics(WebMvcMetricsFilter.java:126) ~[spring-boot-actuator-2.0.3.RELEASE.jar:2.0.3.RELEASE]
    at 
    at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) [tomcat-embed-core-8.5.31.jar:8.5.31]
    at java.lang.Thread.run(Thread.java:748) [na:1.8.0_131]

解決方法:是Finchley.RC2版本問題,改成Finchley.RC1即可。

我們可以看到該JSON返回了應用名repo,環境名test,分支名master,以及default環境和test環境的配置內容。另外version對應得是Git上的commit號。同時在控制檯還輸出瞭如下內容。

2018-09-25 11:36:02.144  INFO 3016 --- [nio-5666-exec-5] s.c.a.AnnotationConfigApplicationContext : Refreshing org.spring[email protected]58e3d589: startup date [Tue Sep 25 11:36:02 CST 2018]; root of context hierarchy
2018-09-25 11:36:02.153  INFO 3016 --- [nio-5666-exec-5] o.s.c.c.s.e.NativeEnvironmentRepository  : Adding property source: file:/C:/Users/ADMINI~1/AppData/Local/Temp/config-repo-8649563955543866185/config/config_repo/repo-test.properties
2018-09-25 11:36:02.153  INFO 3016 --- [nio-5666-exec-5] o.s.c.c.s.e.NativeEnvironmentRepository  : Adding property source: file:/C:/Users/ADMINI~1/AppData/Local/Temp/config-repo-8649563955543866185/config/config_repo/repo.properties
2018-09-25 11:36:02.153  INFO 3016 --- [nio-5666-exec-5] s.c.a.AnnotationConfigApplicationContext : Closing org.spring[email protected]58e3d589: startup date [Tue Sep 25 11:36:02 CST 2018]; root of context hierarchy

配置伺服器在從Git中獲取配置資訊後,會儲存一份在config-server的檔案系統中,實質上config-server是通過git clone命令將配置內容複製了一份在本地儲存,然後讀取這些內容並返回給微服務應用進行載入。

config-server通過Git在本地倉庫暫存,可以有效防止當Git倉庫出現故障而引起無法載入配置資訊的情況。我們可以斷開網路再次發起http://localhost:5666/repo/test/master請求,在控制檯輸出瞭如下內容,但是它依然會返回配置內容,這些內容源自於之前訪問時存於config-server的本地檔案系統中的配置內容。

2018-09-25 11:46:53.166  WARN 3016 --- [nio-5666-exec-8] .c.s.e.MultipleJGitEnvironmentRepository : Could not fetch remote for master remote: https://github.com/WYA1993/spring_cloud_config_demo
2018-09-25 11:46:53.479  INFO 3016 --- [nio-5666-exec-8] s.c.a.AnnotationConfigApplicationContext : Refreshing org.spring[email protected]81e8011: startup date [Tue Sep 25 11:46:53 CST 2018]; root of context hierarchy
2018-09-25 11:46:53.487  INFO 3016 --- [nio-5666-exec-8] o.s.c.c.s.e.NativeEnvironmentRepository  : Adding property source: file:/C:/Users/ADMINI~1/AppData/Local/Temp/config-repo-8649563955543866185/config/config_repo/repo-test.properties
2018-09-25 11:46:53.487  INFO 3016 --- [nio-5666-exec-8] o.s.c.c.s.e.NativeEnvironmentRepository  : Adding property source: file:/C:/Users/ADMINI~1/AppData/Local/Temp/config-repo-8649563955543866185/config/config_repo/repo.properties
2018-09-25 11:46:53.487  INFO 3016 --- [nio-5666-exec-8] s.c.a.AnnotationConfigApplicationContext : Closing org.spring[email protected]81e8011: startup date [Tue Sep 25 11:46:53 CST 2018]; root of context hierarchy

構建客戶端

完成上述驗證後,確定了配置服務中心已經正常工作,下面嘗試如何在微服務應用中獲取上述配置資訊。

第一步,建立Spring Boot專案,命名config-client-vFinchley.RC2,引入如下依賴

<parent>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-parent</artifactId>
	<version>2.0.2.RELEASE</version>
	<relativePath /> <!-- lookup parent from repository -->
</parent>

<properties>
	<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
	<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
	<java.version>1.8</java.version>
	<spring-cloud.version>Finchley.RC1</spring-cloud.version>
</properties>

<dependencies>
	<!-- web的依賴,必須加 -->
	<dependency>
	    <groupId>org.springframework.boot</groupId>
    	<artifactId>spring-boot-starter-web</artifactId>
	</dependency>
	<!--Spring Boot Actuator,感應服務端變化 -->
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-test</artifactId>
		<scope>test</scope>
	</dependency>
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-actuator</artifactId>
	</dependency>

	<!--Spring Cloud Config 客戶端依賴 -->
	<dependency>
		<groupId>org.springframework.cloud</groupId>
		<artifactId>spring-cloud-config-server</artifactId>
	</dependency>
</dependencies>

第二步,建立應用主類

@SpringBootApplication
public class Application {

	public static void main(String[] args) {
		SpringApplication.run(Application.class, args);
	}
}

第三步,建立bootstrap.yml配置檔案

spring:
  application:
    name: repo
  cloud:
    config:
      uri: http://localhost:5666/
      label: master
      profile: dev
      fail-fast: true  #沒有讀取成功則執行快速失敗
server:
  port: 5777

上述配置引數與Git中儲存的配置檔案中各個部分的對應關係如下:

  • spring.application.name:對應配置檔案規則中的{application}部分
  • spring.cloud.config.profile:對應配置檔案規則中的{profile}部分
  • spring.cloud.config.label:對應配置檔案規則中的{label}部分
  • spring.cloud.config.uri:配置中心config-server的地址
  • fail-fast:沒有讀取成功則執行快速失敗

這裡需要格外注意:上面這些屬性必須配置在bootstrap.yml或者properties檔案中中,這樣config-server中的配置資訊才能被正確載入。由於本應用jar包之外的配置檔案會優先於應用jar包內的配置檔案的載入,通過bootstrap.yml或者properties檔案對config-server的配置,使得該應用會從config-server中獲取一些外部配置資訊,這些資訊的優先順序比本地內容要高,從而實現了外部化配置。

第四步,建立一個RESTful介面返回配置中心的form屬性,通過@Value("${form}")繫結配置服務中心配置的form屬性,具體實現如下:

@RefreshScope
@RestController
public class TestController {

	@Value("${form}")
	private String form;
	
	@GetMapping("test1")
	public String getForm(){
		return form;
	}
}

除了通過@Value註解繫結注入外,也可以通過Environment物件來獲取配置屬性。

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.core.env.Environment;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RefreshScope
@RestController
public class TestController {

	@Value("${form}")
	private String form;
	
	@GetMapping("test1")
	public String getForm(){
		return form;
	}
	
	@Autowired
	private Environment env;
	
	@GetMapping("test2")
	public String getForm2(){
		return env.getProperty("form", "undefined");
	}
}

測試