SpringCloud 分布式配置


轉 http://www.cnblogs.com/zhangjianbin/p/6347247.html

前言

在單體式應用中,我們通常的做法是將配置文件和代碼放在一起,這沒有什么不妥。當你的應用變得越來越大從而不得不進行服務化拆分的時候,會發現各種provider實例越來越多,修改某一項配置越來越麻煩,你常常不得不為修改某一項配置而重啟某個服務所有的provider實例,甚至為了灰度上線需要更新部分provider的配置。這個時候有一套配置文件集中管理方案就變得十分重要,SpringCloudConfig和SpringCloudBus就是這種問題的解決方案之一,業界也有些知名的同類開源產品,比如百度的disconf。

相比較同類產品,SpringCloudConfig最大的優勢是和Spring無縫集成,支持Spring里面EnvironmentPropertySource的接口,對於已有的Spring應用程序的遷移成本非常低,在配置獲取的接口上是完全一致,結合SpringBoot可使你的項目有更加統一的標准(包括依賴版本和約束規范),避免了應為集成不同開軟件源造成的依賴版本沖突。

一. 簡介

SpringCloudConfig就是我們通常意義上的配置中心,把應用原本放在本地文件的配置抽取出來放在中心服務器,從而能夠提供更好的管理、發布能力。SpringCloudConfig分服務端和客戶端,服務端負責將git(svn)中存儲的配置文件發布成REST接口,客戶端可以從服務端REST接口獲取配置。但客戶端並不能主動感知到配置的變化,從而主動去獲取新的配置,這需要每個客戶端通過POST方法觸發各自的/refresh

SpringCloudBus通過一個輕量級消息代理連接分布式系統的節點。這可以用於廣播狀態更改(如配置更改)或其他管理指令。SpringCloudBus提供了通過POST方法訪問的endpoint/bus/refresh,這個接口通常由git的鈎子功能調用,用以通知各個SpringCloudConfig的客戶端去服務端更新配置。

下圖是SpringCloudConfig結合SpringCloudBus實現分布式配置的工作流

 

注意:這是工作的流程圖,實際的部署中SpringCloudBus並不是一個獨立存在的服務,這里單列出來是為了能清晰的顯示出工作流程。

二. SpringCloudConfig Server

SpringCloudConfig提供基於以下3個維度的配置管理:

  • 應用
    • 這個比較好理解,每個配置都是屬於某一個應用的
  • 環境
    • 每個配置都是區分環境的,如dev, test, prod等
  • 版本
    • 這個可能是一般的配置中心所缺乏的,就是對同一份配置的不同版本管理
    • Spring Cloud Config提供版本的支持,也就是說對於一個應用的不同部署實例,可以從服務端獲取到不同版本的配置,這對於一些特殊場景如:灰度發布,A/B測試等提供了很好的支持。

2.1 ConfigServer 配置

服務端要在pom中依賴spring-cloud-config-serverspring-cloud-starter-bus-kafka

<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-config-server</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-bus-kafka</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-config-monitor</artifactId> </dependency>

application.properties中要配置倉庫描述和消息隊列地址,如果是私有項目還需要配置用戶名密碼

spring.cloud.config.server.git.uri=https://github.com/seagrape/SpringCloudConfig.git spring.cloud.config.server.git.searchPaths=alan-config-repo #spring.cloud.config.server.git.username=sihan2 #spring.cloud.config.server.git.password=MYPASSWORD spring.cloud.stream.kafka.binder.brokers=10.79.96.52:9092 spring.cloud.stream.kafka.binder.zk-nodes=10.79.96.52:2182

啟動類中要有@EnableConfigServer注解

@SpringBootApplication @EnableConfigServer public class ConfigServerApplication { public static void main(String[] args) { SpringApplication.run(ConfigServerApplication.class, args); } }

2.2 ConfigServer 啟動

Server端啟動后,提供了如下的接口地址,參數說明

  • application:應用名
  • profile:環境
  • label:版本
/{application}/{profile}[/{label}] /{application}-{profile}.yml /{label}/{application}-{profile}.yml /{application}-{profile}.properties /{label}/{application}-{profile}.properties

NOTE:是配置文件的名字一般是有兩部分組成,舉個例子感受下,alan-provider-data-config-dev.properties,其中alan-provider-data-config是第一部分,這部分建議通過命名規則能讓你知道是哪一個項目的配置,並且客戶端要配置spring.cloud.config.name=alan-provider-data-config,才能讓客戶端知道自己要去服務端找哪一個配置文件。dev是第二部分,這部分用以區別配置文件應用的場景,是開發環境、測試環境或者生產環境

接口返回樣例 curl http://localhost:8888/alan-provider-data-config/dev/master

{
    "name": "alan-provider-data-config", "profiles": ["dev"], "label": "master", "version": "78dce2b71473749a5298e11ef0d004ffa8d26bd1", "propertySources": [{ "name": "https://github.com/seagrape/SpringCloudConfig.git/alan-config-repo/alan-provider-data-config-dev.properties", "source": { "spring.datasource.driver-class-name": "com.mysql.jdbc.Driver", "spring.datasource.username": "username", "spring.datasource.password": "password", "spring.datasource.url": "jdbc:mysql://DEVIP:PORT/DBNAME?characterEncoding=UTF-8" } }] }

接口返回樣例 curl http://localhost:8888/alan-provider-data-config-dev.properties

spring.datasource.driver-class-name: com.mysql.jdbc.Driver spring.datasource.password: password spring.datasource.url: jdbc:mysql://DEVIP:PORT/DBNAME?characterEncoding=UTF-8 spring.datasource.username: username

2.3 ConfigServer 文件系統

GIT做文件系統,文件都會被clone到本地文件系統中,默認這些文件會被放置到以config-repo-為前綴的系統臨時目錄,在 linux 上應該是 /tmp/config-repo-目錄,如果你遇到了不可預知的問題出現,你可以通過設置spring.cloud.config.server.git.basedir參數值為非系統臨時目錄。

Config Server中,還有一種從本地classpath 或文件系統中加載配置文件的方式,可以通過spring.cloud.config.server.native.searchLocations進行設置。但如果你連GIT環境都沒有,你還是回去喝奶吧......

三. SpringCloudConfig Client

3.1 ConfigClient 配置

客戶端要在pom中依賴spring-cloud-starter-configspring-cloud-starter-bus-kafka

<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-config</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-bus-kafka</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency>

bootstrap.properties中配置配置中心地址和消息隊列地址

1.特別注意 配置中心的地址一定要放在bootstrap.properties中,這個配置文件是由“根”上下文優先加載,可以保證程序啟動之初就感知到遠程配置中心的存在,並從遠程獲取配置,隨后繼續啟動系統,這點十分重要。 2.而application.properties是由子上下文加載,加載順序低於前者,如果配置中心地址放在這里,並且你遠程配置了一些啟動相關的必要參數,那么,你的程序很可能由於缺少參數而啟動失敗。 3.下面這段代碼,最關鍵的是第一行,第二行如果不配置系統默認讀取spring.application.name,第三行如果不配置,系統默認default,即:${spring.application.name}.properties 4.我們一般的做法是,在系統啟動的時候,用命令行傳入--spring.cloud.config.profile=dev|prod|test的方式,因為在啟動的時候,我們是明確知道我要獲取哪套配置的。 5.bus相關的配置(本例中用的kafka)完全可以放在遠程。

spring.cloud.config.uri=http://127.0.0.1:${config.port:8888} spring.cloud.config.name=alan-provider-data-config spring.cloud.config.profile=${config.profile:dev} spring.cloud.stream.kafka.binder.brokers=10.79.96.52:9092 spring.cloud.stream.kafka.binder.zk-nodes=10.79.96.52:2182

引用配置的類要加@RefreshScope注解

@SpringBootApplication @RestController @RefreshScope public class ConfigClientApplication { @Value("${spring.datasource.username}") String name = "World"; @RequestMapping("/") public String home() { System.out.println(name); return name; } public static void main(String[] args) { SpringApplication.run(ConfigClientApplication.class, args); } }

3.2 RefreshScope 注解

我們知道Spring原生提供了一些scope,如singleton,prototype,request等。 為了實現配置更新后,已經注入bean的值也能更新的目的,Spring Cloud提供了一個新的scope - RefreshScope。

Spring Cloud對RefreshScope的定義如下:

A Scope implementation that allows for beans to be refreshed dynamically at runtime (see refresh(String) and refreshAll()). If a bean is refreshed then the next time the bean is accessed (i.e. a method is executed) a new instance is created.

所以,對於那些有注入值的bean,我們可以把它們標記為RefreshScope,這樣當運行時發現有配置更新的時候,通過調用RefreshScope.refresh(beanName)或RefreshScope.refreshAll(),從而下次這些bean被使用時會被重新初始化,進而會被重新注入值,所以也就達到了更新的目的。

3.3 ConfigClient 啟動順序

ConfigClient最好要在ConfigServer之后啟動,Spring加載配置文件是有順序的,靠前的配置文件會覆蓋靠后的配置文件中相同鍵的值,如果ConfigServer先啟動可以保證ConfigClient將遠程的配置文件加載到最前面,如果使用中沒有注意到這一點,有可能導致你本地的配置文件先於遠程的加載,導致本地的配置覆蓋遠程配置。當然,你也可以讓本地配置和遠程配置完全不重復,這樣也可以避免鍵/值覆蓋的問題。

后面會進一步說明這部分相關的知識點。

四. 背景知識

4.1 Spring中的Environment和PropertySource

  • Environment
    • Spring的ApplicationContext會包含一個Environment
    • Environment自身包含了很多個PropertySource
  • PropertySource
    • 屬性源
    • 可以理解為很多個Key - Value的屬性配置

在運行時的結構形如:

 

需要注意的是,PropertySource之間是有優先級順序的,如果有一個Key在多個property source中都存在,那么在前面的property source優先。所以對上圖的例子:

  • env.getProperty(“key1”) -> value1
  • env.getProperty(“key2”) -> value2
  • env.getProperty(“key3”) -> value4

在ConfigClient啟動階段,從ConfigServer獲取配置,然后組裝成PropertySource並插入到第一個,在隨后的獲取配置過程中,來自Config Server的配置和其它本地的配置對使用者而言是沒有任何差別的,從而實現了無縫集成。

 

需要注意的是,如果ConfigServer在ConfigClient之后啟動,那遠程配置將被加載到最后,這點在使用中需要特別注意。 下圖是可以看到遠程配置被優先加載

 

4.2 /env

/env 是 spring-boot-starter-actuator提供的一個接口,GET方法調用可以查看系統環境變量,POST調用可以更改環境變量的值,並且通過這種方式修改的變量值具有最最高優先級。通過觀察了解這個接口數據的變化,對學習SpringCloudConfig有幫助。

curl -X POST http://localhost:8080/env -d spring.datasource.username=wsh

如果要使上述修改生效,還需要利用/refresh重新載入所有@RefreshScope修飾的bean類。

curl -X POST http://localhost:8080/refresh

如果要重置這些修改

curl -X POST http://localhost:8080/env/reset

4.3 /refresh

使@RefreshScope修飾的bean類在下次調用時重新載入配置。

4.4 /bus/env

作用同/env,區別是會對所有節點生效

curl -X POST http://localhost:8888/bus/env -d spring.datasource.username=wsh

4.5 /bus/refresh

作用同/refresh,區別是會對所有節點生效

向消息broker發送一條信息,所有監聽這個broker的應用會獲得上述消息,並各自開始更新配置。每個SpringCloudBus的節點都有這個接口,並且這些接口是等效的,調用任何一個都可以起到相同的效果。但通常我們會調用在ConfigServer上配置的Bus,這樣從流程上更符合人們的理解習慣。

curl -X POST http://localhost:8888/bus/refresh

五. SpringCloudBus

SpringCloudBus並不是一個獨立的服務,他配置在每個ConfigClient,並通過消息隊列使所有節點感知到狀態變化。SpringCloudConfig沒有直接集成bus的功能是有好處的,bus是可插拔設計並且目前並不完美,如果有個性需求完全可以用自己的方案替換bus,這個剝離bus成本幾乎等於零。

目前Bus有兩種實現spring-cloud-starter-bus-amqpspring-cloud-starter-bus-kafka,官網的例子是基於amqp,需要運行RabbitMQ,本文的例子用的是Kafka。

現在我們已經有能力在無需重啟的情況下對應用程序配置進行更新了。

六. 項目代碼

SpringCloudConfig GitHub

七. 問題

  1. 當代碼倉庫訪問速度慢的時候,讀取配置的速度也會慢,要考慮代碼倉庫的可用性
  2. 為了增強Config Server 的高可靠性,需要按比例增加Config Server的數量
  3. [待完善,隨時補充......]

八. 參考資料

九. 擴展閱讀


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM