SpringCloud統一配置中心
SpringCloud統一配置中心
本篇簡介
通過上兩篇的介紹我們已經掌握了SpringCloud中的註冊中心元件Eureka以及服務間的呼叫方式RestTemplate和Feign。那麼通過這兩篇的內容,我們基本可以滿足一些簡單專案的開發需求了。但同樣上述的專案架構還是有一些問題的。例如:
- 不方便維護: 因為在公司開發專案時,是有多個團隊多個成員同時開發的,這樣就避免不了如果有人修改專案的相關配置,那麼可能會對其它專案組產生影響,甚至可以會導致專案程式碼衝突。
- 資訊不安全: 因為按照我們之前的專案架構,我們是需要將所有配置寫到專案中的。例如資料庫賬號及密碼等。但如果將這些敏感的資料資訊放到專案中的話,難免會有一些安全性的問題。因為所有專案開發者都有這些敏感資訊的所有許可權。
- 開發效率低: 因為我們之前的文章中介紹過了,每當我們修改配置檔案時,都是需要重新啟動專案的。當我們平時開發時還好,但如果修改生產已經執行的專案配置的話,那麼就會涉及到專案上線及其重啟,這可能會導致一些線上問題的產生。
SpringCloud為了解決上述的問題,於是提供了統一註冊中心元件Config。通過這個元件,不同的服務都可以從這個元件獲取相關的配置資訊。而這個元件也不儲存其它服務的配置資訊,而是通過遠端倉庫獲取相關的配置資訊。例如:Git、Svn等。這樣當我們修改不同服務的配置資訊時,我們只要在遠端倉庫更改後,其它的服務也就可以通過配置中心獲取到最新的配置資訊了。並且這樣還有一個好處,就是我們可以通過遠端倉庫來設定不同人員的許可權,這樣就解決了敏感資訊的洩漏問題了。下面我們來詳細介紹一下怎麼搭建一個配置中心。
搭建配置中心
- 還是和之前專案一樣建立一個SpringBoot的專案。
- 這一步驟也是和之前建立的專案一樣,設定專案相關引數。
- 因為我們配置中心也是一個Eureka的客戶端,所以我們在這一步還要選擇Eureka的客戶端的依賴。
除了要新增Eureka的客戶端的依賴外,還要新增一個非常重要的依賴,也就是統一配置中心的組建Config依賴。

- 這一步驟沒什麼介紹的了,只要點選完成就可以了。
這樣我們就完成了配置中心的建立了,但這還沒完。我們還沒有搭建完一個配置中心。因為按照之前我們搭建註冊中心的方式,我們還是需要在啟動類上新增配置中心的註解的,下面我們看一下啟動類的原始碼:
package com.jilinwula.springcloudconfig; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.config.server.EnableConfigServer; import org.springframework.cloud.netflix.eureka.EnableEurekaClient; @SpringBootApplication @EnableEurekaClient @EnableConfigServer public class SpringcloudConfigApplication { public static void main(String[] args) { SpringApplication.run(SpringcloudConfigApplication.class, args); } }
因為配置中心是一個Eureka的客戶端,所以我們需要在啟動類上添加了@EnableEurekaClient註解。還有另一個註解就是配置中心的核心註解也就是@EnableConfigServer。下面我們看一下該專案的配置資訊:
eureka: client: service-url: defaultZone: http://127.0.0.1:8761/eureka spring: application: name: springcloud-config server: port: 8081
配置資訊很簡單和一個普通的Eureka客戶端的配置一樣,下面我們啟動這個專案來看一下專案的執行結果。下面為專案啟動日誌:
Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled. 2019-03-17 18:41:54.442 ERROR 1522 --- [main] o.s.b.d.LoggingFailureAnalysisReporter: *************************** APPLICATION FAILED TO START *************************** Description: Invalid config server configuration. Action: If you are using the git profile, you need to set a Git URI in your configuration.If you are using a native profile and have spring.cloud.config.server.bootstrap=true, you need to use a composite configuration.
當我們滿懷期待的覺的專案可以正常執行的時候,我們發現專案啟動居然報錯了。這是為什麼呢?按照我們之前搭建註冊中心時我們的理解。配置中心是不是也會提供一個什麼管理介面之類的。但結果讓我們很意外,程式居然丟擲了異常。這到底是為什麼呢?這是因為我們之前介紹過配置中心也不會儲存其它服務的配置資訊的,而是從遠端倉庫中獲取配置資訊,然後將配置資訊暫存到本地,然後在對外提供服務。所以上述錯誤的根本原因是因為我們沒有配置遠端倉庫的地址。如果我們觀察仔細的話,我們可以通過上述的錯誤日誌發現,日誌已經提示我們沒有配置遠端倉庫的地址了,也就是日誌中的Git URI。下面我們配置一下配置中心的遠端倉庫地址。在配置之前,我們首先要建立一個遠端倉庫。
建立配置中心倉庫
建立遠端倉庫可以有很多種方式。比較常用的例如:GitHub、GitLab等。為了演示方便我們就不在手動搭建Git伺服器了,而是直接使用免費的GitHub了。下面我們看一下怎麼在GitHub中建立遠端倉庫。我們首先訪問GitHub的官網。下面為官方地址:

在使用GitHub建立倉庫時需要先註冊賬號,因為我本人已經註冊過了,所以我們就直接演示登陸了。也就上圖中紅色標識的地方。
當我們登陸成功後,就會顯示自己的個人主頁,也就是上圖所示,我們直接點選上圖中紅色表示,來建立一個新的倉庫。

當我們輸完倉庫的名字及其倉庫描述後,然後點下方綠色的按鈕即可。
當我們建立完倉庫後,我們點選上圖中紅色的連結,在該倉庫中建立一個新的檔案,然後將服務的相關配置寫到這個檔案中。
我們直接點選下圖中的綠色按鈕即可。

下圖是儲存完檔案後返回的頁面。

我們看上圖中瀏覽器位址列顯示的路徑不是我們預設建立的倉庫地址,而顯示了分支的名字。我們在配置中心配置時是不能配置這個地址的。我們點選一下倉庫地址。這樣瀏覽器位址列就會顯示倉庫正確的地址了。也就是下圖中的地址。
指定配置中心倉庫地址
既然配置中心的倉庫我們已經建立完了,下面我們將該倉庫的地址配置到配置中心的專案中,然後在啟動一下專案看看專案還會不會丟擲異常。下圖為配置中心倉庫的配置:
eureka: client: service-url: defaultZone: http://127.0.0.1:8761/eureka spring: application: name: springcloud-config cloud: config: server: git: uri: https://github.com/jilinwula/springcloud-config server: port: 8081
如果我們此時啟動專案並觀察啟動日誌,我們會發現日誌在啟動時已經不會丟擲異常了,這就說明我們的遠端倉庫配置成功了。下面我們看一下怎麼訪問配置中心返回的配置。
訪問配置中心
按照我們之前的理解,我們直接訪問該專案的埠,然後看看會返回什麼樣的資訊。下面為訪問路徑:
GET http://127.0.0.1 :8081
返回結果:
GET http://127.0.0.1:8081 HTTP/1.1 404 Content-Type: application/json;charset=UTF-8 Transfer-Encoding: chunked Date: Mon, 18 Mar 2019 03:27:23 GMT { "timestamp": "2019-03-18T03:27:23.930+0000", "status": 404, "error": "Not Found", "message": "No message available", "path": "/" } Response code: 404; Time: 81ms; Content length: 121 bytes
我們看程式居然又丟擲了異常,但這異常我們應該比較常見了,因為之前文章中都介紹過,這是因為我們沒有配置Controller導致。實際在配置中心中我們是不需要寫任何Controller的,甚至連一行程式碼都不需要寫的。因為上面已經介紹過,配置中心只是將遠端倉庫的配置資訊拉取到本地,然後在對外提供服務。這時可能有人會想,既然不需要寫Controller那我們怎麼訪問配置中心返回的配置資訊呢?不用著急,既然配置中心不需要我們寫任何程式碼而可以支援其它服務訪問配置資訊,那結果一定是SpringCloud預設為我們封裝好了,我們只需要按照配置中心中預設的訪問規則就可以返回配置資訊的結果了。下面我們看一下配置中心的訪問規則。
配置中心訪問規則
還記著我們在遠端倉庫中新建立的client.yml檔案嗎?我們已經將相關的配置資訊新增到了該檔案中。配置中心實際上就是返回該檔案的內容。也就是通過配置中心裡的Git URI找到倉庫中的配置檔案。所以下面我們直接訪問這個檔案看看會返回什麼結果。
GET http://127.0.0.1 :8081/client.yml
返回結果:
GET http://127.0.0.1:8081/client.yml HTTP/1.1 404 Content-Type: application/json;charset=UTF-8 Transfer-Encoding: chunked Date: Mon, 18 Mar 2019 03:42:49 GMT { "timestamp": "2019-03-18T03:42:49.473+0000", "status": 404, "error": "Not Found", "message": "No message available", "path": "/client.yml" } Response code: 404; Time: 30ms; Content length: 131 bytes
我們看介面的返回還是出錯。剛剛不是說SpringCloud自動為我們處理嗎?這是因為我們在通過配置中心訪問遠端倉庫檔案時有一個特殊的規則。就是: 倉庫檔名-隨機名.字尾
。只有這樣配置中心才會正確的返回遠端倉庫中的配置資訊。也就是下面的訪問路徑:
http://127.0.0.1 :8081/client-test.yml
返回結果:
GET http://127.0.0.1:8081/client-test.yml HTTP/1.1 200 Content-Type: text/plain Content-Length: 156 Date: Mon, 18 Mar 2019 03:59:35 GMT eureka: client: service-url: defaultZone: http://127.0.0.1:8761/eureka server: port: 8082 spring: application: name: springcloud-client Response code: 200; Time: 2306ms; Content length: 156 bytes
我們看這時介面就成功的返回遠端倉庫中的配置資訊了。為了更直觀的感受,我們修改一下遠端倉庫的配置,然後在請求一下上述的介面,看看還能不能返回倉庫中最新的配置。下面為遠端倉庫中配置檔案中的更改。
原倉庫配置:
eureka: client: service-url: defaultZone: http://127.0.0.1:8761/eureka spring: application: name: springcloud-client server: port: 8082
現倉庫配置:
eureka: client: service-url: defaultZone: http://127.0.0.1:8761/eureka spring: application: name: springcloud-client server: port: 8082 userinfo: username: jilinwula password: 123456789
下面我們繼續訪問下面的介面,然後看一下配置中心返回的結果。
GET http://127.0.0.1 :8081/client-test.yml
返回結果:
GET http://127.0.0.1:8081/client-test.yml HTTP/1.1 200 Content-Type: text/plain Content-Length: 210 Date: Mon, 18 Mar 2019 07:10:10 GMT eureka: client: service-url: defaultZone: http://127.0.0.1:8761/eureka server: port: 8082 spring: application: name: springcloud-client userinfo: password: 123456789 username: jilinwula Response code: 200; Time: 4266ms; Content length: 210 bytes
我們看配置中心已經成功的返回了遠端倉庫中最新的配置資訊了。(備註:特別注意如果我們在遠端倉庫中的配置資訊包含中文的話,配置中心在返回時可能會導致中文亂碼,這種問題我們在之後的文章中在做介紹)。下面我們介紹了一個配置中心返回配置的規則。全部的路徑為:
/{label}/{application}-{profile}.yml
下面我們詳細介紹上面引數的含義:
- label: 遠端倉庫的分支名字。如果我沒有指定該引數預設按照master訪問,但在訪問時,如果分支為master可以省略。
- application: 遠端倉庫檔案的名字,也就是上圖中的client。
- profile: 不同環境的名字。例如:dev(開發環境)、test(測試環境)、pro(生產環境)等。
這時可能有人會產生疑問?上面的遠端倉庫中也沒有什麼client-dev.yml檔案啊為什麼我們訪問這個路徑還可以成功的返回配置資訊呢?這裡面還有一個預設的約定。那就是配置中心在從遠端倉庫獲取配置資訊時,除了要獲取client-dev.yml檔案外,還會獲取client.yml。然後將這兩份檔案的內容合併在一起給服務返回。因為我們沒有client-dev.yml檔案,所以我們在訪問client-dev.yml時就會直接獲取client.yml檔案的內容。為了測試我們上面所說的,我們在遠端倉庫新建立一個client-dev.yml檔案,然後我們故意將這兩份檔案的內容編寫的不一致。然後看一下配置中返回的結果。
client-dev.yml:
profile: active: dev
訪問以下介面地址:
GET http://127.0.0.1 :8081/client-dev.yml
返回結果:
GET http://127.0.0.1:8081/client-dev.yml HTTP/1.1 200 Content-Type: text/plain Content-Length: 233 Date: Mon, 18 Mar 2019 08:05:39 GMT eureka: client: service-url: defaultZone: http://127.0.0.1:8761/eureka profile: active: dev server: port: 8082 spring: application: name: springcloud-client userinfo: password: 123456789 username: jilinwula Response code: 200; Time: 2360ms; Content length: 233 bytes
我們看配置中心返回的結果已經包含遠端倉庫中client-dev.yml的內容了。那此時如果我們訪問client-test.yml呢?會返回什麼樣的結果呢?我們驗證一下。
GET http://127.0.0.1 :8081/client-test.yml
返回的結果:
GET http://127.0.0.1:8081/client-test.yml HTTP/1.1 200 Content-Type: text/plain Content-Length: 210 Date: Mon, 18 Mar 2019 08:08:19 GMT eureka: client: service-url: defaultZone: http://127.0.0.1:8761/eureka server: port: 8082 spring: application: name: springcloud-client userinfo: password: 123456789 username: jilinwula Response code: 200; Time: 2291ms; Content length: 210 bytes
我們看配置中心返回的結果還是client.yml檔案而沒有返回client-dev.yml的內容。但為什麼配置中心要這樣處理呢?這樣處理有什麼好處呢?如果我們熟悉SpringBoot開發我們就會知道application.yml檔案,該檔案就是不同環境的共有檔案。我們可以將不同環境配置的資訊配置在這個檔案中,這樣就減少了不同環境中有大量相同配置的問題了。所以配置中心中的上述功能也是解決這樣問題的。除了上述的功能外,配置中心還對遠端倉庫中返回的資料的格式做了優化。因為按照我們遠端倉庫中的名字是yml檔案。但配置中心可以直接支援返回json格式的配置資訊。也就是下面的請求路徑:
GET http://127.0.0.1 :8081/client-dev.json
返回結果:
GET http://127.0.0.1:8081/client-dev.json HTTP/1.1 200 Content-Type: application/json Content-Length: 246 Date: Mon, 18 Mar 2019 08:19:11 GMT { "eureka": { "client": { "service-url": { "defaultZone": "http://127.0.0.1:8761/eureka" } } }, "profile": { "active": "dev" }, "server": { "port": 8082 }, "spring": { "application": { "name": "springcloud-client" } }, "userinfo": { "password": 123456789, "username": "jilinwula" } } Response code: 200; Time: 3762ms; Content length: 246 byte
我們看這時配置中心返回的格式就是json型別了。上述內容就是配置中心的訪問規則,當然我們還沒有測試不同分支的情況,在這裡就不依依演示了,和上述的內容基本一致。下面我們介紹一下怎麼在客戶端服務中使用配置中心返回配置。
配置中心客戶端配置
為了方便測試,我們需新建一個Eureka客戶端專案,然後在這個專案中新建立的專案中使用配置中心獲取該專案的相關配置。因為我們之前已經介紹過了怎麼建立一個Eureka客戶端專案了。在這裡我們就不演示怎麼建立了。而是直接在該專案中新增配置中心的依賴。具體配置如下:
pom.xml:
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-config-client</artifactId> </dependency>
application.yml:
spring: application: name: client cloud: config: discovery: enabled: true service-id: springcloud-config profile: dev
下面我們詳細介紹一下上述配置中引數的說明:
- service-id: 配置中心中的專案名字
- profile: 對應遠端倉庫中環境的名字
- name: 遠端倉庫裡配置名字的名字
正是上面這3個引數的配置,客戶端就可以按照配置中心訪問的規則獲取遠端倉庫的資訊。下面我們啟動專案並訪問一下注冊中心看一下該服務是否註冊成功。

我們看註冊中心已經成功的檢測到了該服務。下面我們試一下能不能正確的通過配置中心獲取遠端倉庫的配置。我們在專案中新建立一個使用者類然後按照我們之前介紹過的知識,將配置檔案中的資訊讀取到該類屬性中,然後通過Controller返回該類的資訊。下面為具體程式碼:
UserInfo:
package com.jilinwula.springcloudclient; import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; @Data @Component @ConfigurationProperties("userinfo") public class UserInfo { private String username; private String password; }
UserInfoController:
package com.jilinwula.springcloudclient; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("/userinfo") public class UserInfoController { @Autowired private UserInfo userInfo; @GetMapping("/get") public Object get() { return userInfo; } }
下面我們訪問一下UserInfoController看一下能否返回遠端倉庫的配置。
GET http://127.0.0.1 :8082/userinfo/get
返回結果:
org.apache.http.conn.HttpHostConnectException: Connect to 127.0.0.1:8082 [/127.0.0.1] failed: Connection refused (Connection refused)
我們介面返回的結果居然丟擲了異常。這是為什麼呢?這是因為程式在啟動時通過@ConfigurationProperties註解直接獲取專案中配置檔案中的配置。但又由於我們的配置是從配置中心中遠端倉庫獲取的,所以該註解就獲取不到配置中心中的配置了。那怎麼解決呢?其實解決這樣的問題也很簡單,我們只要保證在註解獲取配置時,專案已經從配置中心獲取到了配置就可以了。那怎麼保證呢?SpringBoot為了解決配置載入的順序的問題,於是提供了除application.yml檔案外,還提供了bootstrap.yml檔案,並且預設優先載入bootstrap.yml檔案。也就是在程式啟動時就已經載入完了。這樣就解決了上述的錯誤了。下面我們將application.yml檔名字修改為bootstrap.yml檔案在訪問一下上述介面。
GET http://127.0.0.1 :8082/userinfo/get
返回結果:
GET http://127.0.0.1:8082/userinfo/get HTTP/1.1 200 Content-Type: application/json;charset=UTF-8 Transfer-Encoding: chunked Date: Mon, 18 Mar 2019 15:36:14 GMT { "username": "jilinwula", "password": "123456789" } Response code: 200; Time: 147ms; Content length: 47 bytes
我們看介面這次返回遠端倉庫中的配置了。為了更直觀的感受,我們在修改一下遠端倉庫中的配置資訊,然後在訪問一下該介面。下面為遠端倉庫配置:
eureka: client: service-url: defaultZone: http://127.0.0.1:8761/eureka server: port: 8082 userinfo: username: admin password: 123456789
我們將username引數的值由jilinwula修改成了admin。下面我們在訪問一下介面看一下返回結果。
GET http://127.0.0.1 :8082/userinfo/get
返回結果:
GET http://127.0.0.1:8082/userinfo/get HTTP/1.1 200 Content-Type: application/json;charset=UTF-8 Transfer-Encoding: chunked Date: Mon, 18 Mar 2019 15:42:33 GMT { "username": "jilinwula", "password": "123456789" } Response code: 200; Time: 14ms; Content length: 47 bytes
我們發現介面返回的居然還是之前的資訊。這是為什麼呢?這個原因是專案只有在啟動時才通過配置中心獲取遠端倉庫中的資訊。我們雖然改了遠端倉庫中的資訊,但該專案獲取的還是上一次啟動時的遠端倉庫的資訊,所以該介面返回的還是之前的資訊。所以解決這樣的問題很簡單,我們只要重啟一下專案就可以了。下面我們啟動專案後在看一下該介面的返回結果。
GET http://127.0.0.1 :8082/userinfo/get
返回結果:
GET http://127.0.0.1:8082/userinfo/get HTTP/1.1 200 Content-Type: application/json;charset=UTF-8 Transfer-Encoding: chunked Date: Mon, 18 Mar 2019 15:44:27 GMT { "username": "admin", "password": "123456789" } Response code: 200; Time: 131ms; Content length: 43 bytes
我們看這時該介面就正確的返回了遠端倉庫中最新的配置資訊了。下面我們介紹一下多配置中心的配置。
多配置中心
為了解決讓程式高可用,通常在專案開發時,如果只有一個配置中心是不穩定的。如果該配置中心出現了問題就會導致整個專案執行失敗。所以我們通常會配置多個配置中心。下面我們建立第二個配置中心。我們同樣不演示配置中心具體的建立了,而是直接看該配置中心的配置。
eureka: client: service-url: defaultZone: http://127.0.0.1:8761/eureka spring: application: name: springcloud-config cloud: config: server: git: uri: https://github.com/jilinwula/springcloud-config server: port: 8083
下面我們啟動該專案並看一下注冊中心看看是否註冊成功。
下面我們啟動客戶端服務看一下客戶端服務會呼叫哪個配置中心獲取配置資訊。下面啟動日誌。
2019-03-19 10:01:07.493INFO 13533 --- [main] c.c.c.ConfigServicePropertySourceLocator : Multiple Config Server Urls found listed. 2019-03-19 10:01:07.494INFO 13533 --- [main] c.c.c.ConfigServicePropertySourceLocator : Fetching config from server at : http://bogon:8081/
我們看客戶端的日誌輸出了8081埠,這也就是說當前專案呼叫的是8081埠的配置中心服務。如果我們多次重啟專案,我們就會發現客戶端服務獲取配置中心的服務埠是隨機變化的。這也就是說明配置中心服務的負載策略是隨機,至於怎麼修改,在這裡我們就不詳細介紹了,如有不瞭解的,可以看一下注冊中那篇文章中的負載策略。
修改註冊中心預設埠
我們知道註冊中心的預設埠為8761。那如果我們修改了註冊中心中的預設埠然後在啟動客戶端的專案會怎麼樣呢?下面我們測試一下。下面我們將配置中心的埠修改為8084。然後在啟動一下客戶端的服務看一下啟動日誌。(注意別忘記將配置中心裡的註冊中心的地址修改為8084)。下面我客戶端專案的啟動日誌。
2019-03-19 10:22:57.833 ERROR 13635 --- [main] c.n.d.s.t.d.RedirectingEurekaHttpClient: Request execution error. endpoint=DefaultEndpoint{ serviceUrl='http://localhost:8761/eureka/} com.sun.jersey.api.client.ClientHandlerException: java.net.ConnectException: Connection refused (Connection refused)
我們發現,我們只是修改了註冊中心的預設埠,客戶端在啟動的時候,居然丟擲了異常了。並且通過啟動日誌發現,客戶端里居然還是呼叫了8761埠。這是為什麼呢?如果我們仔細想想,好像有一個註冊中心的配置的地方沒有改。也就是遠端倉庫中的地址。下面我們訪問下面的地址看一下遠端倉庫中的配置。
GET http://127.0.0.1 :8081/client-dev.yml
返回結果:
GET http://127.0.0.1:8081/client-dev.yml HTTP/1.1 200 Content-Type: text/plain Content-Length: 154 Date: Tue, 19 Mar 2019 02:30:37 GMT eureka: client: service-url: defaultZone: http://127.0.0.1:8761/eureka server: port: 8082 userinfo: password: 123456789 username: admin Response code: 200; Time: 2638ms; Content length: 154 bytes
我們發現遠端倉庫中的註冊中心地址還是8761埠。下面我們將遠端倉庫中的註冊中心埠也修改為8084。然後我們在訪問上述埠看一下配置中心返回的配置是不是最新的。
GET http://127.0.0.1 :8081/client-dev.yml
返回結果:
GET http://127.0.0.1:8081/client-dev.yml HTTP/1.1 200 Content-Type: text/plain Content-Length: 154 Date: Tue, 19 Mar 2019 02:36:32 GMT eureka: client: service-url: defaultZone: http://127.0.0.1:8084/eureka server: port: 8082 userinfo: password: 123456789 username: admin Response code: 200; Time: 3622ms; Content length: 154 bytes
現在我們在啟動一下客戶端專案看一下啟動日誌。
2019-03-19 10:43:05.672 ERROR 13686 --- [nfoReplicator-0] c.n.d.s.t.d.RedirectingEurekaHttpClient: Request execution error. endpoint=DefaultEndpoint{ serviceUrl='http://localhost:8761/eureka/} com.sun.jersey.api.client.ClientHandlerException: java.net.ConnectException: Connection refused (Connection refused)
我們發現專案啟動時還是會丟擲異常,並且日誌顯示還是獲取的是8761埠。這時有人就會感覺到奇怪了,為什麼還會丟擲異常呢?我們已經將專案中所有註冊中心配置的地方都修改為了8084了。為什麼專案啟動時還找8761埠呢?我們再看一下客戶端中的配置資訊,然後在分析一下。
spring: application: name: client cloud: config: discovery: enabled: true service-id: springcloud-config profile: dev
上面的配置我們之前介紹過,主要是通過name+profile引數來通過配置中心返回遠端倉庫的資訊。但這裡我們忽略了一個很重要的問題。就是客戶端本身首先要通過註冊中心獲取到配置中心的地址,然後在通過配置中心在獲取遠端倉庫的配置。上面我們的配置中沒有指定註冊中心的地址,所以程式在啟動時就會丟擲異常。因為它根本就獲取不到配置中心。這時可能有人會問,那為什麼我們沒有改註冊中心預設埠時,專案啟動不會丟擲異常呢?這是因為SpringCloud預設會找專案中配置檔案裡的註冊中心地址,如果沒有找到則呼叫預設的註冊中心地址。如果找到了就按照配置檔案中註冊中心地址進行服務呼叫。所以我們之前的專案在啟動時是不會丟擲異常的。既然我們知道了問題所在,那解決就比較容易了,我們只要將遠端倉庫中註冊中心的配置放到專案裡就可以了。下面為客戶端專案配置:
eureka: client: service-url: defaultZone: http://127.0.0.1:8084/eureka spring: application: name: client cloud: config: discovery: enabled: true service-id: springcloud-config profile: dev
這時我們在啟動專案時就會發現專案可以正常啟動了,如些此時檢視註冊中心發現客戶端服務已經在註冊中心註冊成功了。
自動更新配置
我們之前介紹過如果我們直接更改遠端倉庫的配置時,客戶端是獲取不到最新的配置的。只有當我們重啟自動客戶端服務才可以。但這明顯不太方便。下面我們分析一下為什麼當遠端倉庫更新配置時,客戶端獲取不到最新的配置。答案很簡單,因為服務只有在啟動的時候才通過配置中心獲取遠端倉庫的配置,當服務啟動後,如果遠端倉庫中的配置進行了修改,客戶端服務因為在啟動時已經獲取了配置資訊了,現在就不會在獲取了。所以也就不會獲取到最新的配置了。那怎麼解決了呢?解決的方式很簡單,就是每當遠端倉庫配置做修改後,都通知客戶端服務,這樣客戶端服務就可以獲取到最新的配置資訊了。那怎麼通知呢?那就是使用訊息佇列。每當遠端倉庫更新時,都向訊息佇列中發起一條訊息,然後客戶端服務發現訊息佇列裡的訊息後,然後在重新獲取最新的配置。那我們使用哪個訊息佇列呢?SpringCloud預設為我們支援了RabbitMQ。當然我們也可以使用其它的訊息佇列,只不過如果使用RabbitMQ的話,SpringCloud會為我們提供自動化預設配置。也就是我們什麼都不需要配置就可以直接使用該佇列服務。下面我們看一下具體怎麼配置。如果要使用佇列服務時,那就需要在使用的專案中新增相應的依賴,下面我們看一下配置中心裡依賴的修改。
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-bus-amqp</artifactId> </dependency>
我們只要在配置中心的專案裡新增上面的依賴就可以。然後我們同樣在客戶端的服務中新增上面的依賴。然後我們啟動專案後,在看一下訊息佇列裡的訊息是否有變化。怎麼安裝RabbitMQ的內容我們就不在這裡做詳細介紹了,我們直接訪問訊息佇列的管理介面即可。下面地址為RabbitMQ預設的管理介面地址:
http://127.0.0.1 :15672
我們看上圖中的訊息佇列中預設多了兩個佇列並對應著配置中心服務和客戶端服務。如果我們這時修改遠端倉庫中的配置,然後在請求客戶端介面時,那麼客戶端獲取還是和之前一樣是舊的配置資訊。雖然我們使用了佇列但是當配置中心修改時,訊息佇列也不知道配置進行了修改,所以我們需要一個觸發點讓訊息佇列知道配置資訊進行了更改。在SpringCloud中於是提供了
bus-refresh
介面,該介面的目的就是告訴配置中心,遠端倉庫中的配置進行了修改。下面我們訪問一下該介面。
POST http://127.0.0.1 :8081/actuator/bus-refresh
返回結果:
POST http://127.0.0.1:8081/actuator/bus-refresh HTTP/1.1 405 Allow: GET Content-Type: application/json;charset=UTF-8 Transfer-Encoding: chunked Date: Tue, 19 Mar 2019 13:45:28 GMT { "timestamp": "2019-03-19T13:45:28.346+0000", "status": 405, "error": "Method Not Allowed", "message": "Request method 'POST' not supported", "path": "/actuator/bus-refresh" } Response code: 405; Time: 142ms; Content length: 165 bytes
當我們訪問SpringCloud為我們提供的bus-refresh介面時,居然丟擲了異常。這是為什麼呢?這是因為SpringCloud預設配置了訪問許可權,並且該許可權預設是禁止訪問的。所以我們在使用bus-refresh介面時,必須先將該介面的訪問許可權設定為允許,這樣就可以了訪問該介面了。下面為配置中心裡配置的修改:
eureka: client: service-url: defaultZone: http://127.0.0.1:8084/eureka spring: application: name: springcloud-config cloud: config: server: git: uri: https://github.com/jilinwula/springcloud-config server: port: 8081 management: endpoints: web: exposure: include: "bus-refresh"
下面我們重新啟動配置中心後,在訪問一下下面的介面。
POST http://127.0.0.1 :8081/actuator/bus-refresh
返回結果:
POST http://127.0.0.1:8081/actuator/bus-refresh HTTP/1.1 204 Date: Tue, 19 Mar 2019 13:56:43 GMT <Response body is empty> Response code: 204; Time: 3487ms; Content length: 0 bytes
我們看這時介面就訪問正常了。下面我們測試一下,看看這樣的方式,會不會自動更新配置。我們首先訪問下面客戶端介面看一下該介面返回的資訊是什麼?
GET http://127.0.0.1 :8082/userinfo/get
返回結果:
GET http://127.0.0.1:8082/userinfo/get HTTP/1.1 200 Content-Type: application/json;charset=UTF-8 Transfer-Encoding: chunked Date: Tue, 19 Mar 2019 14:23:34 GMT { "username": "admin", "password": "12345" } Response code: 200; Time: 134ms; Content length: 39 bytes
下面我們修改一下遠端倉庫的配置資訊。
server: port: 8082 userinfo: username: admin password: 54321
如果我們此時在訪問上述的介面的話,那該介面返回的結果一定還是早的資訊。這是咱們之前測試過的。下面我們先呼叫一下bus-refresh介面。
POST http://127.0.0.1 :8081/actuator/bus-refresh
返回結果:
POST http://127.0.0.1:8081/actuator/bus-refresh HTTP/1.1 204 Date: Tue, 19 Mar 2019 14:28:52 GMT <Response body is empty> Response code: 204; Time: 3344ms; Content length: 0 bytes
我們這時在訪問客戶端介面看一下客戶端能不能獲取到最新的配置資訊。
GET http://127.0.0.1 :8082/userinfo/get
返回結果:
GET http://127.0.0.1:8082/userinfo/get HTTP/1.1 200 Content-Type: application/json;charset=UTF-8 Transfer-Encoding: chunked Date: Tue, 19 Mar 2019 14:30:20 GMT { "username": "admin", "password": "12345" } Response code: 200; Time: 13ms; Content length: 39 bytes
我們發現客戶端服務還是沒有獲取到最新遠端倉庫中的配置,這又是怎麼回事呢?這是因為我們還差最後一個環節,也就是需要在我們獲取動態引數的地方新增@RefreshScope註解。只有添加了該註解,才能達到動態重新整理的功能。下面我們在客戶端的Controller中新增@RefreshScope註解。然後在重新啟動一下客戶端的服務。(備註:特別注意當我們客戶端服務重新啟動時,就會重新通過配置中心獲取遠端倉庫最新的配置了。所以我們為了測試動態重新整理的功能,應該在該服務啟動後,然後在重新修改一下遠端倉庫的配置,然後在呼叫bus-refresh介面,看一下客戶端服務是否能獲取到最新的配置)。下面為訪問的客戶端介面:
GET http://127.0.0.1 :8082/userinfo/get
返回結果:
GET http://127.0.0.1:8082/userinfo/get HTTP/1.1 200 Content-Type: application/json;charset=UTF-8 Transfer-Encoding: chunked Date: Tue, 19 Mar 2019 14:30:20 GMT { "username": "admin", "password": "54321" } Response code: 200; Time: 13ms; Content length: 39 bytes
下面我們將遠端倉庫中的引數修改一下然後在測試一下。
server: port: 8082 userinfo: username: admin password: 12345
我們在訪問一下客戶端的介面。(備註:不要忘記了我們應該先訪問一下bus-refresh介面)。下面繼續請求客戶端介面:
GET http://127.0.0.1 :8082/userinfo/get
返回的結果:
GET http://127.0.0.1:8082/userinfo/get HTTP/1.1 200 Content-Type: application/json;charset=UTF-8 Transfer-Encoding: chunked Date: Tue, 19 Mar 2019 14:56:26 GMT { "username": "admin", "password": "12345" } Response code: 200; Time: 13ms; Content length: 39 bytes
我們看這回我們就達到了動態重新整理的功能了。但我們發現還有一點不太方便就是每當我們修改遠端倉庫配置時,都要手動呼叫了bus-refresh介面。那有沒有什麼辦法呢?答案一定還是有的。但這不是SpringCloud的功能,而是GitHub的功能。下面我們詳細介紹一下。
Webhooks配置
GitHub中有一個功能就是當檔案變更、提交、評論時,可以自動觸發一個請求。我們正好可以利用這個功能來滿足我們自動重新整理的功能。下面我們看一下怎麼配置Webhooks。下面我們開啟遠端倉庫中的專案。然後點選紅色連結。
我們看上面最後一張圖中需要我們指定的Payload URL引數,也就是當遠端倉庫中有變化時,GitHub就會呼叫我們指定的這個URL。因為Payload URL引數是需要外網呼叫的,所以為了測試方便,我們直接使用了NATAPP工具,通過該工具可以做內網穿透,也就是通過該工具為我們分配的隨機的域名可以對映到我們本地的介面。我們只要將該工具外網域名地址對映到我們配置中心的埠即可。下面我們在更新一下遠端倉庫中的配置,然後直接訪問客戶端的介面,看看能不能直接獲取到最新的遠端倉庫中的配置。
server: port: 8082 userinfo: username: jilinwula password: jilinwula
下面我們直接訪問客戶端的服務的介面看一下返回的結果。
GET http://127.0.0.1 :8082/userinfo/get
返回的結果:
GET http://127.0.0.1:8082/userinfo/get HTTP/1.1 200 Content-Type: application/json;charset=UTF-8 Transfer-Encoding: chunked Date: Wed, 20 Mar 2019 05:46:34 GMT { "username": "admin", "password": "12345" } Response code: 200; Time: 204ms; Content length: 49 bytes
我們發現客戶端服務獲取的配置資訊還是舊的配置,還是沒有獲取到最新的配置,這是為什麼呢?我們檢視一下GitHub上的WebHook,在網頁下面居然有警告資訊。
我們看一下這警告資訊是什麼錯誤。
{"timestamp":"2019-03-19T16:02:46.565+0000","status":400,"error":"Bad Request","message":"JSON parse error: Cannot deserialize instance of `java.lang.String` out of START_ARRAY token; nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize instance of `java.lang.String` out of START_ARRAY token\n at [Source: (PushbackInputStream); line: 1, column: 300] (through reference chain: java.util.LinkedHashMap[\"commits\"])","path":"/actuator/bus-refresh"}
我們看上述報的錯誤是格式化的錯誤。這是為什麼呢?我們在本地呼叫一下這個介面看一下返回的結果。
POST http://mzqha4.natappfree.cc/a...
返回的結果:
POST http://mzqha4.natappfree.cc/actuator/bus-refresh HTTP/1.1 204 Date: Wed, 20 Mar 2019 06:15:14 GMT <Response body is empty> Response code: 204; Time: 4074ms; Content length: 0 bytes
我們看這是該介面返回的結果。但我們發現上述介面中Response返回的Code碼是204,意味著返回的結果是空的。因為該介面壓根就不需要有返回值。如果我們本地呼叫該介面的話,沒有任何問題,但是WebHook中對返回的Code碼有要求,必須返回200才會認為該請求傳送成功。那怎麼解決呢?很簡單,我們自己在配置中心新建立一個Controller,讓WebHook直接呼叫這個Controller中的介面,然後我們在這個Controller中自己在呼叫一下 actuator/bus-refresh
介面。因為是我們自己寫的Controller了,所以我們可以很方便的控制該介面的返回值,這樣也就能保證該介面返回的Code碼為200了。(備註:只要我們正常返回資料,Code碼預設就會是200)。下面我們看一下這個Contrller中的原始碼:
package com.jilinwula.springcloudconfig; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.client.loadbalancer.LoadBalancerClient; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; import java.util.HashMap; import java.util.Map; @RestController @RequestMapping("/config") public class ConfigController { @Autowired private LoadBalancerClient loadBalancerClient; @Autowired private RestTemplate restTemplate; @PostMapping("/sync") public Object sync() { Map<String, Integer> map = new HashMap<String, Integer>(); ServiceInstance serviceInstance = loadBalancerClient.choose("springcloud-config"); String url = String.format("http://%s:%s/actuator/bus-refresh", serviceInstance.getHost(), serviceInstance.getPort()); restTemplate.postForObject(url, map, Object.class); map.put("code", 0); return map; } }
上述的程式碼比較簡單我們就不詳細介紹了。下面我們先在本地呼叫一下,看一下該介面是否能夠達到自動重新整理配置。
POST http://127.0.0.1 :8081/config/sync
返回結果:
POST http://127.0.0.1:8081/config/sync HTTP/1.1 200 Content-Type: application/json;charset=UTF-8 Transfer-Encoding: chunked Date: Wed, 20 Mar 2019 06:57:58 GMT { "code": 0 } Response code: 200; Time: 54531ms; Content length: 10 bytes
我們看這回介面返回的Code碼為200了。下面我們將上述介面配置到WebHook中。然後我們在修改一下配置,看看WebHook中是否還有警告資訊。
我們看這回WebHook不顯示警告資訊了。如果我們這時在訪問一下客戶端的服務,我們就會發現,客戶端介面可以返回最新的配置資訊了。
上述內容就是本篇的全部內容,如有不正確的地方歡迎留言,謝謝。
原始碼地址:
https://github.com/jilinwula/jilinwula-springcloud-config