SpringCloud統一配置中心

本篇簡介

經過上兩篇的介紹咱們已經掌握了SpringCloud中的註冊中心組件Eureka以及服務間的調用方式RestTemplate和Feign。那麼經過這兩篇的內容,咱們基本能夠知足一些簡單項目的開發需求了。但一樣上述的項目架構仍是有一些問題的。例如:java

  • 不方便維護: 由於在公司開發項目時,是有多個團隊多個成員同時開發的,這樣就避免不了若是有人修改項目的相關配置,那麼可能會對其它項目組產生影響,甚至能夠會致使項目代碼衝突。
  • 信息不安全: 由於按照咱們以前的項目架構,咱們是須要將全部配置寫到項目中的。例如數據庫帳號及密碼等。但若是將這些敏感的數據信息放到項目中的話,不免會有一些安全性的問題。由於全部項目開發者都有這些敏感信息的全部權限。
  • 開發效率低: 由於咱們以前的文章中介紹過了,每當咱們修改配置文件時,都是須要從新啓動項目的。當咱們平時開發時還好,但若是修改生產已經運行的項目配置的話,那麼就會涉及到項目上線及其重啓,這可能會致使一些線上問題的產生。

SpringCloud爲了解決上述的問題,因而提供了統一註冊中心組件Config。經過這個組件,不一樣的服務均可以從這個組件獲取相關的配置信息。而這個組件也不存儲其它服務的配置信息,而是經過遠程倉庫獲取相關的配置信息。例如:Git、Svn等。這樣當咱們修改不一樣服務的配置信息時,咱們只要在遠程倉庫更改後,其它的服務也就能夠經過配置中心獲取到最新的配置信息了。而且這樣還有一個好處,就是咱們能夠經過遠程倉庫來設置不一樣人員的權限,這樣就解決了敏感信息的泄漏問題了。下面咱們來詳細介紹一下怎麼搭建一個配置中心。git


搭建配置中心

  1. 仍是和以前項目同樣建立一個SpringBoot的項目。

title

  1. 這一步驟也是和以前建立的項目同樣,設置項目相關參數。

title    
 

  1. 由於咱們配置中心也是一個Eureka的客戶端,因此咱們在這一步還要選擇Eureka的客戶端的依賴。

  
title
    
除了要添加Eureka的客戶端的依賴外,還要添加一個很是重要的依賴,也就是統一配置中心的組建Config依賴。
  
title 
  github

  1. 這一步驟沒什麼介紹的了,只要點擊完成就能夠了。

  
title

  

    
這樣咱們就完成了配置中心的建立了,但這還沒完。咱們尚未搭建完一個配置中心。由於按照以前咱們搭建註冊中心的方式,咱們仍是須要在啓動類上添加配置中心的註解的,下面咱們看一下啓動類的源碼:
  web

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。下面咱們看一下該項目的配置信息:
 spring

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。下面咱們配置一下配置中心的遠程倉庫地址。在配置以前,咱們首先要建立一個遠程倉庫。apache


  

建立配置中心倉庫

  
建立遠程倉庫能夠有不少種方式。比較經常使用的例如:GitHub、GitLab等。爲了演示方便咱們就不在手動搭建Git服務器了,而是直接使用免費的GitHub了。下面咱們看一下怎麼在GitHub中建立遠程倉庫。咱們首先訪問GitHub的官網。下面爲官方地址:
          json

https://github.com

title  
  
在使用GitHub建立倉庫時須要先註冊帳號,由於我本人已經註冊過了,因此咱們就直接演示登錄了。也就上圖中紅色標識的地方。
  
title   bootstrap

當咱們登錄成功後,就會顯示本身的我的主頁,也就是上圖所示,咱們直接點擊上圖中紅色表示,來建立一個新的倉庫。
  
title  
  
當咱們輸完倉庫的名字及其倉庫描述後,而後點下方綠色的按鈕便可。  
  
title
  
當咱們建立完倉庫後,咱們點擊上圖中紅色的連接,在該倉庫中建立一個新的文件,而後將服務的相關配置寫到這個文件中。 
 
title      api

咱們直接點擊下圖中的綠色按鈕便可。    
  
title  
  
下圖是保存完文件後返回的頁面。
  
title  
  
咱們看上圖中瀏覽器地址欄顯示的路徑不是咱們默認建立的倉庫地址,而顯示了分支的名字。咱們在配置中心配置時是不能配置這個地址的。咱們點擊一下倉庫地址。這樣瀏覽器地址欄就會顯示倉庫正確的地址了。也就是下圖中的地址。
  
title


  

指定配置中心倉庫地址

  
既然配置中心的倉庫咱們已經建立完了,下面咱們將該倉庫的地址配置到配置中心的項目中,而後在啓動一下項目看看項目還會不會拋出異常。下圖爲配置中心倉庫的配置:
  

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個參數的配置,客戶端就能夠按照配置中心訪問的規則獲取遠程倉庫的信息。下面咱們啓動項目並訪問一下注冊中心看一下該服務是否註冊成功。
  
title  
咱們看註冊中心已經成功的檢測到了該服務。下面咱們試一下能不能正確的經過配置中心獲取遠程倉庫的配置。咱們在項目中新建立一個用戶類而後按照咱們以前介紹過的知識,將配置文件中的信息讀取到該類屬性中,而後經過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  

下面咱們啓動該項目並看一下注冊中心看看是否註冊成功。
  
title 
下面咱們啓動客戶端服務看一下客戶端服務會調用哪一個配置中心獲取配置信息。下面啓動日誌。

2019-03-19 10:01:07.493  INFO 13533 --- [           main] c.c.c.ConfigServicePropertySourceLocator : Multiple Config Server Urls found listed.
2019-03-19 10:01:07.494  INFO 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
  

這時咱們在啓動項目時就會發現項目能夠正常啓動了,如些此時查看註冊中心發現客戶端服務已經在註冊中心註冊成功了。
  
title 

  

  

自動更新配置 

  
咱們以前介紹過若是咱們直接更改遠程倉庫的配置時,客戶端是獲取不到最新的配置的。只有當咱們重啓自動客戶端服務才能夠。但這明顯不太方便。下面咱們分析一下爲何當遠程倉庫更新配置時,客戶端獲取不到最新的配置。答案很簡單,由於服務只有在啓動的時候才經過配置中心獲取遠程倉庫的配置,當服務啓動後,若是遠程倉庫中的配置進行了修改,客戶端服務由於在啓動時已經獲取了配置信息了,如今就不會在獲取了。因此也就不會獲取到最新的配置了。那怎麼解決了呢?解決的方式很簡單,就是每當遠程倉庫配置作修改後,都通知客戶端服務,這樣客戶端服務就能夠獲取到最新的配置信息了。那怎麼通知呢?那就是使用消息隊列。每當遠程倉庫更新時,都向消息隊列中發起一條消息,而後客戶端服務發現消息隊列裏的消息後,而後在從新獲取最新的配置。那咱們使用哪一個消息隊列呢?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

title 
  
咱們看上圖中的消息隊列中默認多了兩個隊列並對應着配置中心服務和客戶端服務。若是咱們這時修改遠程倉庫中的配置,而後在請求客戶端接口時,那麼客戶端獲取仍是和以前同樣是舊的配置信息。雖然咱們使用了隊列可是當配置中心修改時,消息隊列也不知道配置進行了修改,因此咱們須要一個觸發點讓消息隊列知道配置信息進行了更改。在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。下面咱們打開遠程倉庫中的項目。而後點擊紅色連接。
title 
title   
title   
title   
咱們看上面最後一張圖中須要咱們指定的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,在網頁下面竟然有警告信息。
  
title 
咱們看一下這警告信息是什麼錯誤。

{"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中是否還有警告信息。
  
title 
  
咱們看這回WebHook不顯示警告信息了。若是咱們這時在訪問一下客戶端的服務,咱們就會發現,客戶端接口能夠返回最新的配置信息了。

  

  
上述內容就是本篇的所有內容,若有不正確的地方歡迎留言,謝謝。

  

  

源碼地址

https://github.com/jilinwula/jilinwula-springcloud-config          

相關文章
相關標籤/搜索