玩轉Spring Cloud之配置中心(config server &config client)
本文內容導航:
一、搭建配置服務中心(config server)
1.4.解決配置中包含中文內容返回亂碼問題
二、搭建配置消費客戶端(config client)
2.1.通過@value方式獲取配置資訊
2.2.通過建立一個對映遠端配置資訊的Bean(RemoteConfigProperties) 方式獲取配置資訊
2.3.通過Environment來直接獲取配置資訊
2.4.實現配置資訊自動重新整理
2.5.加入到註冊中心配置成高可用叢集環境
上篇總結了 熔斷降級(Hystrix)與監控 的使用方式,裡面涉及到的一些重點的知識細節我都有說明,網上很多的spring cloud教程只是簡單的貼程式碼、簡單描述,而我的文章是要體現知識的積累與開發時的參考,故花了一些時間描述了一些細節,這些細節能幫助我自己(夢在旅途)以及大家少走彎路,本文仍然圍繞這個中心思想來總結描述關於配置中心的搭建與使用,實現了多種搭建配置服務中心(config server),也實現了多種配置消費客戶端的實現方式。(多種配置消費方式目前很少有人總結,而我在本文中有特別介紹,不一定都用得上,但應該要了解,以便舉一反三靈活運用),好了廢話不多說,直接開始本文主題。
首先我們要了解,為什麼需要配置中心?配置中心能解決什麼問題?回答這些問題可以參見: https://www.cnblogs.com/xiaoqi/p/configserver-compair.html ,我這裡簡要說明一下:JAVA應用一般常用的配置檔案是:xxx.Properties 或 xxx.yml檔案,.NET(.NET CORE)一般常用的配置檔案是:xxx.config 或 xxx.json 甚至是xxx.ini,它們都能夠很好的應對單機本地配置的需求(可以多個應用共用同一個目錄位置的配置檔案),但如果是分散式應用,那麼本地配置檔案在某些場景下就顯得不那麼合適了,因為一是存在配置冗餘(不同機器上配置相同的配置資訊),二是無法快速同步配置資訊(如果要更改配置資訊,就得每臺伺服器一臺一臺的更改,或使用檔案同步機制稍微能保證儘快同步),三是沒有多版本管理,出現更新配置後有問題無法快速回滾(如有問題,需重新配置或人為將之前的備份的配置檔案一臺臺伺服器更新或檔案同步到多臺伺服器),比如:資料庫連線字串,如果使用本地配置就存在如上說的三點問題,正因為存在這些問題,所以才有了分散式配置中心框架來解決這些問題。
分散式配置中心的主流框架有很多,由於本文是總結spring cloud系列的文章,故這裡通過DEMO來實現分散式配置中心Server端及Client消費端(讀取配置),並確保高可用。
一、搭建配置服務中心(config server)
首先通過IDEA spring initializer(或直接通過https://start.spring.io/)建立一個spring boot專案(demo專案命名:configserver),建立過程中選擇:config server依賴,生成專案後的POM XML檔案如下:(如果缺少依賴請自行手動新增)
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.3.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>cn.zuowenjun.cloud</groupId> <artifactId>configserver</artifactId> <version>0.0.1-SNAPSHOT</version> <name>configserver</name> <url>http://www.zuowenjun.cn</url> <description>Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> <spring-cloud.version>Greenwich.RELEASE</spring-cloud.version> </properties> <dependencies> <!--spring cloud配置中心依賴[預設GIT]--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-config-server</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${spring-cloud.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <jvmArguments>-Dfile.encoding=UTF-8</jvmArguments> </configuration> </plugin> </plugins> </build> <repositories> <repository> <id>spring-milestones</id> <name>Spring Milestones</name> <url>https://repo.spring.io/milestone</url> </repository> </repositories> </project> View Code
然後下面分別介紹通過三種方式來實現配置中心:( 注意三種方式均使用上面的專案初始環境,下面不再介紹 )
1.1.git方式(即:將GIT倉庫【GITHUB、GITLAB、 GITEE等】作為配置集中儲存的介質)
前提工作:既然是使用GIT倉庫作為配置儲存,那我們首先就得在相關的GIT站點上建立用於存放config資訊的倉庫,如我(夢在旅途)在自己的github上建立了一個learning-demos/config倉庫目錄,然後在config目錄下分別建立兩個環境的配置檔案(http://configclient-dev.properties、http://configclient-prod.properties),配置檔案命名規則應儘可能使用:{application}-{profile}.{properties|yml},如下是GIT中兩個檔案配置的演示內容:(演示倉庫地址: https://github.com/zuowj/learning-demos )
--configclient-dev.properties內容:測試環境 demo-config-profile-env=dev zuowenjun.site=http://www.zuowenjun.cn,http://zuowj.cnblogs.com--20190226.AAAXX66 zuowenjun.skills=.net,java,html,js,css,sql,python,vb--20190226.BBBXX66 zuowenjun.motto=Learning is endless; Opportunity is for the prepared mind;--20190226.CCCXX1 --configclient-prod.properties內容:生產環境 demo-config-profile-env=prod zuowenjun.site=http://www.zuowenjun.cn,http://zuowj.cnblogs.com,http://github.com/zuowj zuowenjun.skills=.net,java,html,js,css,sql,python,vb zuowenjun.motto=Learning is endless; Opportunity is for the prepared mind;學無止境;機會是留給有準備的人 View Code
1.1.1.在spring boot啟動類(ConfigserverApplication)上新增@EnableConfigServer註解,程式碼如下:
package cn.zuowenjun.cloud; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.config.server.EnableConfigServer; @EnableConfigServer @SpringBootApplication public class ConfigserverApplication { public static void main(String[] args) { SpringApplication.run(ConfigserverApplication.class, args); } } View Code
1.1.2.在配置檔案(application.yml,當然也可以是application.properties看個人喜好,本系列均採用yml)中進行如下配置,重點關注spring.cloud.config節點,相關配置引數有註釋說明:
spring: application: name: configserver profiles: active: git #設定使用本地配置(預設是git,可以設定:subversion(SVN),native(本地)) cloud: config: server: #如下是GIT配置 git: uri: https://github.com/zuowj/learning-demos# 配置git倉庫的地址(最後不需要帶/,否則會出現:No custom http config found for URL: XXX) search-paths: config# git倉庫地址下的相對搜尋地址(可用使用萬用字元),可以配置多個,用,分割。可以{application}實現按應用查配置 username:# git倉庫的賬號(公開倉庫無需賬號資訊) password:# git倉庫的密碼(公開倉庫無需賬號資訊) default-label: master#git預設分支
通過上述簡單的兩步即完成搭建基於GIT的配置服務中心,啟動執行專案,GIT倉庫中的配置檔案會被自動轉換成當前專案的web api,若需訪問檢視遠端配置資料可以參照以下的規則:
/{application}/{profile}[/{label}] [/{label}]/{application}-{profile}{.yml|.properties|.json}
規則簡單說明:{application}=配置消費方應用名稱(即:config client的專案名,通俗講:就是誰用這個配置就是誰的名字),{profile}=配置環境(如:dev開發環境,test測試環境,prod生產環境),{label}=倉庫分支名(git或svn方式指定,native本地方式無需指定),.yml|.properties|.json表示指定的響應返回格式,{}表示必需,[]表示可選,|表示或的關係,例如本Demo專案訪問:
http://localhost:8866/configclient/dev 、http://localhost:8866/configclient/dev/master
http://localhost:8866/configclient-dev.yml、http://localhost:8866/configclient-dev.properties、http://localhost:8866/configclient-dev.json、http://localhost:8866/master/configclient-dev.yml
最終可以根據訪問URL規則返回相應的內容,類似如下:
//request: http://localhost:8866/configclient-dev.yml demo-config-profile-env: dev zuowenjun: motto: Learning is endless; Opportunity is for the prepared mind;--20190226.CCCXX1 site: http://www.zuowenjun.cn,http://zuowj.cnblogs.com--20190226.AAAXX66 skills: .net,java,html,js,css,sql,python,vb--20190226.BBBXX66 //request: http://localhost:8866/configclient-dev.properties demo-config-profile-env: dev zuowenjun.motto: Learning is endless; Opportunity is for the prepared mind;--20190226.CCCXX1 zuowenjun.site: http://www.zuowenjun.cn,http://zuowj.cnblogs.com--20190226.AAAXX66 zuowenjun.skills: .net,java,html,js,css,sql,python,vb--20190226.BBBXX66 //request: http://localhost:8866/configclient-dev.json {"demo-config-profile-env":"dev","zuowenjun":{"motto":"Learning is endless; Opportunity is for the prepared mind;--20190226.CCCXX1","site":"http://www.zuowenjun.cn,http://zuowj.cnblogs.com--20190226.AAAXX66","skills":".net,java,html,js,css,sql,python,vb--20190226.BBBXX66"}} //request: http://localhost:8866/configclient/dev {"name":"configclient","profiles":["dev"],"label":null,"version":"d4616a65c8429b9dd3a67ff226b125ae6a0959bb","state":null,"propertySources":[{"name":"https://github.com/zuowj/learning-demos/config/configclient-dev.properties","source":{"zuowenjun.skills":".net,java,html,js,css,sql,python,vb--20190226.BBBXX66","zuowenjun.site":"http://www.zuowenjun.cn,http://zuowj.cnblogs.com--20190226.AAAXX66","demo-config-profile-env":"dev","zuowenjun.motto":"Learning is endless; Opportunity is for the prepared mind;--20190226.CCCXX1"}}]} View Code
可以看到GIT中的配置資訊通過config server的API能夠正常返回,表示搭建成功,另外需要注意的是如果配置檔案中包含中文,那麼可能中文返回的就是亂碼了,上面如果瀏覽prod生產環境【生產環境我特意配置了中文】,如:http://localhost:8866/configclient/prod,那麼中文就會亂碼,解決方案後面會說明,此處瞭解有這個情況即可。
1.2.svn方式(即:將SVN倉庫作為配置集中儲存的介質)
前提工作:如同GIT方式一樣,要使用SVN倉庫作為配置儲存,那就需要SVN伺服器,將建立用於存放config資訊的倉庫,如我(夢在旅途)在SVN伺服器建立了app-config倉庫目錄,然後在trunk主幹中仍然新增兩個配置檔案(dev,prod)與GIT方式檔名相同,內容如下(內容與GIT方式也基本相同,只是稍微改變內容以便區分):(演示倉庫地址:http://svnhost:port/svn/app-config/trunk)
--configclient-dev.properties內容: demo-config-profile-env=dev-svn zuowenjun.site=http://www.zuowenjun.cn,http://zuowj.cnblogs.com--20190227 zuowenjun.skills=.net,java,html,js,css,sql,python,vb--20190227 zuowenjun.motto=Learning is endless; Opportunity is for the prepared mind;--20190227 --configclient-prod.properties內容: demo-config-profile-env=prod-svn zuowenjun.site=http://www.zuowenjun.cn,http://zuowj.cnblogs.com,http://github.com/zuowj zuowenjun.skills=.net,java,html,js,css,sql,python,vb zuowenjun.motto=Learning is endless; Opportunity is for the prepared mind;學無止境;機會是留給有準備的人 View Code
1.2.1.由於GIT是預設的實現方式,當引入spring-cloud-config-server時,它就具備了相關的框架處理能力,而這裡使用SVN,就需要在POM XML中除了新增config-server依賴外還需單獨新增SVN實現方式的依賴,配置如下:
<!--spring cloud配置中心依賴[預設GIT]--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-config-server</artifactId> </dependency> <!--使用SVN作為配置中心依賴--> <dependency> <groupId>org.tmatesoft.svnkit</groupId> <artifactId>svnkit</artifactId> </dependency>
1.2.2.在配置檔案(application.yml)中進行如下配置,仍然重點關注spring.cloud.config節點,相關配置引數有註釋說明:
server: port: 8866 spring: application: name: configserver profiles: active: subversion #設定使用本地配置(預設是git,可以設定:subversion(SVN),native(本地)) cloud: config: server: #如下是SVN配置 svn: uri: http://svnhost:port/svn/app-config #SVN倉庫地址 username: svnuser #SVN賬號(如果沒有許可權可為空) password: svnpassword #SVN密碼(如果沒有許可權可為空) default-label: trunk #預設SVN分支
通過上述簡單的兩步即完成搭建基於SVN的配置服務中心,啟動執行專案,SVN倉庫中的配置檔案會被自動轉換成當前專案的web api,訪問路徑與GIT方式完全相同在此不重述。(參照1.1.2訪問URL,可以得到SVN倉庫trunk分支裡配置檔案配置資訊的內容)
1.3.本地檔案方式(即:將config server專案所有伺服器的本地檔案存放目錄作為配置集中儲存的介質)
1.3.1.先在config server專案檔案伺服器建立指定存放配置的目錄,如本demo是直接建立在專案的resources/configs目錄,仍然是建立與GIT相同的兩個配置檔案(dev、prod),配置內容如下:
--configclient-dev.properties內容: demo-config-profile-env=dev-native zuowenjun.site=http://www.zuowenjun.cn,http://zuowj.cnblogs.com--20190227 zuowenjun.skills=.net,java,html,js,css,sql,python,vb--20190227 zuowenjun.motto=Learning is endless; Opportunity is for the prepared mind;--20190227 --configclient-prod.properties內容: demo-config-profile-env=prod-native zuowenjun.site=http://www.zuowenjun.cn,http://zuowj.cnblogs.com,http://github.com/zuowj zuowenjun.skills=.net,java,html,js,css,sql,python,vb zuowenjun.motto=Learning is endless; Opportunity is for the prepared mind;學無止境;機會是留給有準備的人 View Code
1.3.2.在配置檔案(application.yml)中進行如下配置,仍然重點關注spring.cloud.config節點,相關配置引數有註釋說明:(很簡單,只需要設定profiles.active=native,然後在native節點設定查詢配置的存放目錄即可)
server: port: 8866 spring: application: name: configserver profiles: active: native #設定使用本地配置(預設是git,可以設定:subversion(SVN),native(本地)) cloud: config: server: #如下是本地檔案配置 native: search-locations: classpath:/configs #配置檔案存放的目錄
通過上述簡單的兩步即完成搭建基於本地檔案的配置服務中心,啟動執行專案,存放的配置檔案會被自動轉換成當前專案的web api,訪問路徑與GIT方式完全相同在此不重述。(參照1.1.2訪問URL,可以得到本地檔案配置資訊的內容)
1.4.解決配置中包含中文內容返回亂碼問題(此處實現方式參照網上說明,在此只是集中說明,便於大家理解)--均適用於以上三種方式
亂碼原因及解決方案思路可參見: https://blog.csdn.net/sinat_38843093/article/details/79960777?utm_source=blogxgwz4 ,但由於最新版本的spring boot的PropertySourceLoader介面定義有些不同故我這裡參照該文進行完善並最終解決亂碼。
首先自定義一個實現了PropertySourceLoader的類:MyPropertySourceLoader,並實現介面的方法邏輯,具體程式碼如下:(最重要的程式碼是:properties.load(new InputStreamReader(inputStream, StandardCharsets.UTF_8));即表示使用UTF8的字符集來讀取配置檔案流資訊)
package cn.zuowenjun.cloud; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.boot.env.PropertySourceLoader; import org.springframework.core.env.PropertiesPropertySource; import org.springframework.core.env.PropertySource; import org.springframework.core.io.Resource; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; import java.util.Properties; public class MyPropertySourceLoader implements PropertySourceLoader { private Logger logger= LoggerFactory.getLogger(MyPropertySourceLoader.class); @Override public String[] getFileExtensions() { return new String[]{"properties", "xml"}; } @Override public List<PropertySource<?>> load(String name, Resource resource) throws IOException { Properties properties=new Properties(); InputStream inputStream=null; try{ inputStream=resource.getInputStream(); properties.load(new InputStreamReader(inputStream, StandardCharsets.UTF_8)); }catch (IOException ioEx){ logger.error("load inputStream failed",ioEx); throw ioEx; } catch (Exception e){ logger.error("load inputStream failed",e); }finally { if(inputStream!=null){ inputStream.close(); } } List<PropertySource<?>> propertySourceList=null; if (!properties.isEmpty()) { PropertiesPropertySource propertiesPropertySource = new PropertiesPropertySource(name, properties); propertySourceList=new ArrayList<>(); propertySourceList.add(propertiesPropertySource); } returnpropertySourceList; } } View Code
然後在resources目錄下建立META-INF目錄,然後在該目錄中建立spring.factories檔案(這裡spring boot的擴充套件配置檔案),在該檔案中輸入如下內容即可:
org.springframework.boot.env.PropertySourceLoader=cn.zuowenjun.cloud.MyPropertySourceLoader
若想了解spring.factories相關資訊,可參考: https://blog.csdn.net/lvoyee/article/details/82017057 、 https://blog.csdn.net/boling_cavalry/article/details/83048588
通過上述步驟,現在無論是哪種方式訪問配置中心的API,若涉及中文都能正常返回不會亂碼,如下圖所示:(當然如果使用[/{label}]/{application}-{profile}{.yml|.properties|.json}這種方式訪問可能還會出現亂碼,具體原因我還未了解,求大神們指點,但目前已不影響配置消費【即:配置消費方獲取到的中文資訊均是正常不會亂碼】)
二、搭建配置消費客戶端(config client)
首先通過IDEA spring initializer(或直接通過https://start.spring.io/)搭建spring boot + spring MVC(Rest API)空專案,建立過程中選擇:web、config client依賴,最後生成的POM XML檔案如下:(如果缺少依賴請自行手動新增)
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.3.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>cn.zuowenjun.cloud</groupId> <artifactId>configclient</artifactId> <version>0.0.1-SNAPSHOT</version> <name>configclient</name> <description>Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> <spring-cloud.version>Greenwich.RELEASE</spring-cloud.version> </properties> <dependencies> <!--spring MVC依賴--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!--config client依賴--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-config</artifactId> </dependency> <!--ConfigurationProperties類所需依賴,手動新增的--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-processor</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${spring-cloud.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> <repositories> <repository> <id>spring-milestones</id> <name>Spring Milestones</name> <url>https://repo.spring.io/milestone</url> </repository> </repositories> </project> View Code
然後在resources目錄下建立bootstrap.yml檔案,並在配置檔案中新增如下內容:(如果想了解bootstrap與application配置檔案兩者的區別,請參見: https://www.cnblogs.com/BlogNetSpace/p/8469033.html )
server: port: 8008 spring: application: name: configclient cloud: config: name: configclient #對應config server Url中的{application} profile: prod #配置環境,對應config server Url中的{profile} #label: trunk #配置分支(不配置則預設:git則是master,svn則是trunk), uri: http://localhost:8866 #配置中心地址
最後下面分別介紹實現各種不同的配置消費(讀取配置)方式:(注意均使用上面的專案初始環境,同時以下的幾種服務配置消費方式均可同時並存在一個專案中)
2.1.通過@value方式獲取配置資訊
這種最為簡單,網上也大部份是這種,我這裡為了便於區分配置屬性與controller本身使用了繼承的方式(注意,我這裡用繼承而沒有使用單獨定義一個RemoteConfig配置類+Value,是因為經我驗證發現將會影響配置自動重新整理,具體原因還未查清,初步判斷與加上@RefreshScope註解上生成的代理類有關係),程式碼結構更清晰,實現程式碼如下:
package cn.zuowenjun.cloud.model; import org.springframework.beans.factory.annotation.Value; import org.springframework.cloud.context.config.annotation.RefreshScope; public abstract class RemoteConfig { @Value("${demo-config-profile-env}") public String profileEnv; @Value("${zuowenjun.site}") public String zwjSite; @Value("${zuowenjun.skills}") public String zwjSkills; @Value("${zuowenjun.motto}") public String zwjMotto; } package cn.zuowenjun.cloud.Controller; import cn.zuowenjun.cloud.model.RemoteConfig; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.HashMap; import java.util.Map; @RestController @RequestMapping("/demo") public class DemoController extends RemoteConfig { @RequestMapping("/config/byval") public Object getRemoteConfigByValue(){ Map<String,Object> model=new HashMap<>(); model.put("getMode","@Value"); Map<String,String> remoteCgfMap=new HashMap<>(); remoteCgfMap.put("profileEnv", this.profileEnv); remoteCgfMap.put("zwjSite", this.zwjSite); remoteCgfMap.put("zwjSkills",this.zwjSkills); remoteCgfMap.put("zwjMotto", this.zwjMotto); model.put("remoteConfig",remoteCgfMap); return model; } }
如上程式碼所示,我們只需在類成員欄位上標記@Value註解,並指定要獲取的配置中心上配置項的名稱即可。執行專案,訪問:http://localhost:8008/demo/config/byval,即可響應返回獲取到的config資訊,如下圖示:
2.2.通過建立一個對映遠端配置資訊的Bean(RemoteConfigProperties) 方式獲取配置資訊
首先定義一個對映遠端配置資訊的Bean類:RemoteConfigProperties,該類中所有的屬性名均需與配置中心上配置檔案的配置內容相同(名字以及層級都需相同,若配置項名稱有-,則配置類中請忽略直接拼接命名即可但需符合lowerCamelCase,如:demo-config-profile-env,則欄位名為:demoConfigProfileEnv,如果屬性中有點( . ),則應視為點後面的部分為下級,應再定義相關的配置對映類),該類完整程式碼如下:
package cn.zuowenjun.cloud.model; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; @Component @ConfigurationProperties()//如果有字首,則可以設定prefix=XXX public class RemoteConfigProperties { private String demoConfigProfileEnv; private Zuowenjun zuowenjun; public String getDemoConfigProfileEnv() { return demoConfigProfileEnv; } public void setDemoConfigProfileEnv(String demoConfigProfileEnv) { this.demoConfigProfileEnv = demoConfigProfileEnv; } public Zuowenjun getZuowenjun() { return zuowenjun; } public void setZuowenjun(Zuowenjun zuowenjun) { this.zuowenjun = zuowenjun; } public static class Zuowenjun { private String site; private String skills; private String motto; public String getSite() { return site; } public void setSite(String site) { this.site = site; } public String getSkills() { return skills; } public void setSkills(String skills) { this.skills = skills; } public String getMotto() { return motto; } public void setMotto(String motto) { this.motto = motto; } } }
如上程式碼所示,屬性欄位名與配置項的命名保持一致,若有下級則定義下級的配置類,這裡下級配置類(Zuowenjun)採用了內部靜態類的方式(注意這裡如果是內部類,一定是static,而不能是普通的class,因為內部普通類與內部靜態類是有區別的,具體可參見: java 內部類和靜態內部類的區別 ),也可以單獨定義一個類,同時在配置類上標記了@ConfigurationProperties(這指明瞭該類是配置對映類),@Component這個不用說了吧就是表示能自動被spring IOC容器掃描並註冊。
然後在DemoController中增加欄位注入該類:RemoteConfigProperties,並新增用於讀取RemoteConfigProperties類中的配置資訊的方法(getRemoteConfigByPropertiesBean),具體程式碼如下:(這裡保留了@Value方式的程式碼)
package cn.zuowenjun.cloud.Controller; import cn.zuowenjun.cloud.model.RemoteConfig; import cn.zuowenjun.cloud.model.RemoteConfigProperties; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cloud.context.config.annotation.RefreshScope; import org.springframework.core.env.Environment; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.HashMap; import java.util.Map; @RestController @RequestMapping("/demo") public class DemoController extends RemoteConfig { @Autowired private RemoteConfigProperties remoteConfigProperties; @RequestMapping("/config/byval") public Object getRemoteConfigByValue(){ Map<String,Object> model=new HashMap<>(); model.put("getMode","@Value"); Map<String,String> remoteCgfMap=new HashMap<>(); remoteCgfMap.put("profileEnv", this.profileEnv); remoteCgfMap.put("zwjSite", this.zwjSite); remoteCgfMap.put("zwjSkills",this.zwjSkills); remoteCgfMap.put("zwjMotto", this.zwjMotto); model.put("remoteConfig",remoteCgfMap); return model; } @RequestMapping("/config/byprops") public Object getRemoteConfigByPropertiesBean(){ Map<String,Object> model=new HashMap<>(); model.put("getMode","Properties Bean"); model.put("remoteConfig",remoteConfigProperties); return model; } } View Code
最後啟動執行專案,訪問:http://localhost:8008/demo/config/byprops,即可響應返回獲取到的config資訊,如下圖示:
2.3.通過Environment來直接獲取配置資訊
其實上面2種方式底層都是使用這種方式來獲取配置資訊的只是包裝了後大家無需關心而矣,核心是通過environment.getProperty方法來獲取對應的配置資訊的,這種方式只需在DemoController增加欄位注入Environment的例項,然後新增一個從Environment獲取配置資訊的方法即可(getRemoteConfigByEnv),具體程式碼如下:(保留@Value方式、對映遠端配置資訊的Bean(RemoteConfigProperties) 方式)
package cn.zuowenjun.cloud.Controller; import cn.zuowenjun.cloud.model.RemoteConfig; import cn.zuowenjun.cloud.model.RemoteConfigProperties; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cloud.context.config.annotation.RefreshScope; import org.springframework.core.env.Environment; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.HashMap; import java.util.Map; @RestController @RequestMapping("/demo") public class DemoController extends RemoteConfig { @Autowired private Environment environment; @Autowired private RemoteConfigProperties remoteConfigProperties; @RequestMapping("/config/byval") public Object getRemoteConfigByValue(){ Map<String,Object> model=new HashMap<>(); model.put("getMode","@Value"); Map<String,String> remoteCgfMap=new HashMap<>(); remoteCgfMap.put("profileEnv", this.profileEnv); remoteCgfMap.put("zwjSite", this.zwjSite); remoteCgfMap.put("zwjSkills",this.zwjSkills); remoteCgfMap.put("zwjMotto", this.zwjMotto); model.put("remoteConfig",remoteCgfMap); return model; } @RequestMapping("/config/byprops") public Object getRemoteConfigByPropertiesBean(){ Map<String,Object> model=new HashMap<>(); model.put("getMode","Properties Bean"); model.put("remoteConfig",remoteConfigProperties); return model; } @RequestMapping("/config/byenv") publicObject getRemoteConfigByEnv(){ Map<String,Object> model=new HashMap<>(); model.put("getMode","Environment"); Map<String,String> remoteCgfMap=new HashMap<>(); remoteCgfMap.put("profileEnv", environment.getProperty("demo-config-profile-env")); remoteCgfMap.put("zwjSite", environment.getProperty("zuowenjun.site")); remoteCgfMap.put("zwjSkills", environment.getProperty("zuowenjun.skills")); remoteCgfMap.put("zwjMotto", environment.getProperty("zuowenjun.motto")); model.put("remoteConfig",remoteCgfMap); returnmodel; } } View Code
最後啟動執行專案,訪問:http://localhost:8008/demo/config/byenv,即可響應返回獲取到的config資訊,如下圖示:
如上三種方式配置檔案均不需要改變,如果需要訪問不同的環境,不同的分支,則可以修改專案配置檔案bootstrap.yml中對應的屬性:profile、label等,至於配置中心(config server)使用的是哪種方式儲存配置,配置消費端(config client)無需關心,只管用即可。
2.4.實現配置資訊自動重新整理(可參見:)
2.4.1.在controller類(DemoController)上新增@RefreshScope註解,簡單就不貼程式碼
2.4.2.在POM XML中新增actuator依賴,配置如下:
<!--actuator監控功能所需依賴(內部包含refresh,動態重新整理配置資訊需要)--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency>
2.4.3.在專案配置檔案(bootstrap.yml或application.yml都可以)新增如下配置內容:
management: endpoints: web: exposure: include: "*" #暴露所有埠,也可以指定某一個環境(先management.endpoint.{profile}.enabled=true,然後這裡指定這個{profile},多個用,分隔)
通過上述兩步即完成config client端的自動監聽與重新整理機制(重新整理原理參見: https://blog.csdn.net/cml_blog/article/details/78411312 )
這時可以啟動執行專案,然後POST請求訪問:http://localhost:8008/actuator/refresh,如果能正常響應結果沒有報404就OK了,響應內容為空?沒關係,因為只有當config server端的配置檔案有更新,這時POST這個API時,即會檢測並響應返回更新的配置項資訊,這樣config client內部即可進行配置的重新整理工作。到目前還沒有實現全自動重新整理配置,因為config server端配置檔案有更新,config client端並不會第一時間感知到,如果不去POST請求refresh API則不會更新,所以自動更新的關鍵是如何能夠實現配置中心的配置檔案一旦更改就能通知所有的配置消費端(config client)自動更新配置資訊。如果採用github的方式,那麼可以使用github倉庫的Webhooks功能,具體操作位置如下圖示:
設定webhooks後,只要這個存配置的GIT倉庫發生改變,將針觸發事件並回調refresh介面(當然可以封裝一個通用介面,然後裡面把所有的 config client都調refresh介面),config client端收到重新整理請求後就會重新從config server中獲取配置資訊。
雖然webhooks能夠解決自動重新整理問題,但仍不夠優美,比較好的實現方式是再結合spring cloud bus、mq實現更好的自動重新整理所有config client,具體可參見: https://www.cnblogs.com/willpan-z/p/9483674.html 、 https://blog.csdn.net/wtdm_160604/article/details/83720391
另外actuator還預設集成了健康檢查功能,可訪問:http://localhost:8866/actuator/health,即可看到響應結果,正常則為status=UP,否則可能是DOWN
2.5.加入到註冊中心配置成高可用叢集環境
思路:我們可以把服務配置中心(config server)與服務消費客戶端(config client)均作為服務加入到註冊中心裡面,然後服務消費客戶端(config client)通過註冊中心根據服務配置中心(config server)的服務ID(應用名稱)找到config server IP,然後請求該IP即可。
前提:先把之前文章《 玩轉Spring Cloud之服務註冊發現(eureka)及負載均衡消費(ribbon、feign) 》中的eureka server專案正常啟動;
2.5.1.改造服務配置中心(config server),整合eureka client(這個我之前文章有說明)【操作步驟簡要重述:先在POM XML中新增spring-cloud-starter-netflix-eureka-client依賴,然後在spring boot啟動類上新增@EnableDiscoveryClient註解以便啟用服務發現,最後在application.yml中新增eureka client的配置資訊】,讓其能夠加入到註冊中心中,如下是改造後的配置檔案:(註釋掉的是多種方式配置)--eureka 配置節點為新增
server: port: 8866 spring: application: name: configserver profiles: active: native #設定使用本地配置(預設是git,可以設定:subversion(SVN),native(本地)) cloud: config: server: #如下是GIT配置 #git: #uri: https://github.com/zuowj/learning-demos# 配置git倉庫的地址(最後不需要帶/,否則會出現:No custom http config found for URL: XXX) #search-paths: config# git倉庫地址下的相對搜尋地址(可用使用萬用字元),可以配置多個,用,分割。可以{application}實現按應用查配置 #username:# git倉庫的賬號(公開倉庫無需賬號資訊) #password:# git倉庫的密碼(公開倉庫無需賬號資訊) #default-label: master#git預設分支 #如下是SVN配置 #svn: #uri: http://10.20.244.85:8080/svn/app-config #SVN倉庫地址 #username: zuowj #SVN賬號(如果沒有許可權可為空) #password: z198610088 #SVN密碼(如果沒有許可權可為空) #default-label: trunk #預設SVN分支 #如下是本地檔案配置 native: search-locations: classpath:/configs #配置檔案存放的目錄 eureka: client: serviceUrl: defaultZone: http://localhost:8800/eureka/ View Code
2.5.2.改造服務消費客戶端(config client),整合eureka client(同2.5.1的服務配置中心(config server),整合eureka client步驟),讓其能夠加入到註冊中心中,以便可以通過註冊中心根據服務配置中心(config server)的服務ID(應用名稱)找到config server IP,這裡的eureka相關配置應統一在bootstrap.yml中,完整配置如下:
server: port: 8008 spring: application: name: configclient cloud: config: name: configclient #對應config server Url中的{application} profile: prod #配置環境,對應config server Url中的{profile} #label: trunk #配置分支(不配置則預設:git則是master,svn則是trunk), #uri: http://localhost:8866 #配置中心地址 discovery: enabled: true #啟用服務發現 service-id: configserver #指定要從eureka獲取的config server的服務ID(即:configserverr的applicationName) eureka: client: serviceUrl: defaultZone: http://localhost:8800/eureka/
如上配置,重點是spring.cloud.config.uri不再指定固定的config server IP,而是改成配置spring.cloud.config.discovery【服務發現】,eureka配置節點與所有eureka client基本相同不再重述。
都改造完成後,信次先後啟動執行:服務配置中心(config server)【可以運多個例項以不同的埠號】、服務消費客戶端(config client),發現一切都正常,當服務配置中心(config server)某個例項斷開仍不影響服務消費客戶端(config client),這樣就實現了高可用了。
好了,有關spring cloud config 總結就寫到這裡了,很多細節若需深究的話還有很多,後面專案中有實際用到再補充總結吧。文中若有不足,歡迎指出,碼字不易,請多支援,謝謝!