1. 程式人生 > 實用技巧 >Spring Cloud (三):配置中心 Spring Cloud Config 和 Consul

Spring Cloud (三):配置中心 Spring Cloud Config 和 Consul

Spring Boot 的配置檔案一般是放在 application.properties 或 application.yml,修改配置需要重啟程式,而且每個程式管理自己的配置檔案,實際應用不大方便

配置中心的好處

  • 統一管理所有程式的配置
  • 多環境,比如開發環境,生產環境,可以為不同環境維護不同的配置
  • 許可權管理,一個使用者只能讀寫部分配置
  • 配置格式校驗,防止使用者配錯
  • 配置的版本控制和回滾
  • 灰度釋出,指定配置只在部分機器起作用(即可以先在部分機器驗證,再推廣到所有機器)
  • 配置的實時推送,即可以不重啟程式就讓改變的配置起作用

這些功能在不同的配置中心的支援度會不一樣

Spring Cloud 最早的配置中心使用 Config,後來又開發了 Consul(即可以做配置中心又可以做服務發現),也有一些公司自己開源的,比如攜程的 Apollo,比如阿里的 Nacos

Spring Cloud Config

Config 預設使用 git 來儲存配置,除此外也可以用 svn,資料庫,本地檔案系統

(1) 建立 Config Server

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

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-config-server</artifactId>
</dependency>
## bootstrap.yml
spring:
  application:
    name: config-single-server
  cloud:
     config:
        server:
          git:
            uri: https://github.com/spring-cloud-samples/config-repo
            username: xxxxxx
            password: xxxxxx
            default-label: master       # 分支
            search-paths: config-path   # 配置檔案所在的根目錄
## application.yml
server:
  port: 3301
@SpringBootApplication
@EnableConfigServer
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

啟動後可以訪問的介面格式如下

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

application 是 app 的名字,profile 是環境,label 是分支(可選,預設是 master),比如

http://localhost:3301/my-app/dev/master
http://localhost:3301/my-app/prod
http://localhost:3301/my-app-dev.yml
http://localhost:3301/my-app-prod.yml
http://localhost:3301/master/my-app-prod.yml

Config Server 會到 https://github.com/spring-cloud-samples/config-repo 這個 repo 下的 config-path 找到相應的配置檔案比如 my-app-prod.yml


(2) 建立應用

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

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

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
# bootstrap.yml
spring:
  profiles:
    active: dev

---
spring:
  profiles: prod
  application:
    name: my-app
  cloud:
     config:
       uri: http://localhost:3301
       label: master
       profile: prod

---
spring:
  profiles: dev
  application:
    name: my-app
  cloud:
     config:
       uri: http://localhost:3301
       label: master
       profile: dev
# application.yml
server:
  port: 3302
management:
  endpoint:
    shutdown:
      enabled: false
  endpoints:     # 開啟 refresh 介面,允許自動重新整理配置
    web:
      exposure:
        include: "*"

data:
  env: xxxx
  user:
    username: xxxx
    password: xxxx
@Component
@Data
@ConfigurationProperties(prefix = "data")
public class Data {
    public static class UserInfo {
        private String username;
        private String password;

        public String getUsername() {
            return username;
        }
        public void setUsername(String username) {
            this.username = username;
        }
        public String getPassword() {
            return password;
        }
        public void setPassword(String password) {
            this.password = password;
        }
        @Override
        public String toString() {
            return "UserInfo{" +
                    "username='" + username + '\'' +
                    ", password='" + password + '\'' +
                    '}';
        }
    }

    private String env;
    private UserInfo user;
}
@RestController
@RefreshScope
public class Controller {
    @Autowired
    private Data data;

    @GetMapping(value = "show")
    public Object show(){
        return data;
    }
}

訪問 http://localhost:3302/show 可以看到返回的是 git 上儲存的配置


(3) 自動重新整理

可以看到 @RefreshScope 新增到了 Controller 上,這樣 Controller 使用的 Data 配置可以無需重啟就更新

在 git 上修改配置後,通過傳送 POST 請求到 http://localhost:3302/actuator/refresh 就可以觸發自動更新

注意如果是通過 @Value 獲取的配置,無法得到自動更新

Config 原生不支援配置的實時推送,如果每次改動都要發 POST 請求給應用就太麻煩了,尤其配置的應用多的話,為此如果是使用 git 做配置儲存的話,可以使用 git 的 WebHook 功能,將每個應用的 actuator/refresh 地址新增到 git 的 WebHook,這樣當儲存在 git 上的配置有改動時,git 就會發送 POST 請求讓每個應用重新整理配置

需要給每個應用註冊 WebHook 還是比較麻煩,可以利用 Spring Cloud Bus 實現訂閱通知功能,Spring Cloud Bus 的底層需要一個訊息佇列,有 RabbitMQ 和 Kafka 可以選擇

假設已經有了一個 RabbitMQ 佇列,只需要在應用端加入以下配置

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>
spring:
  rabbitmq:
    host: localhost
    port: 5672
    username: guest
    password: guest

假設有多個應用,這時只要將其中一個的 actuator/bus-refresh 介面新增到 git 的 WebHook,這樣配置改動會發送 POST 請求到這個應用,這個應用會自動到 Config Server 拉取最新的配置,同時這個應用會通知 Bus,而 Bus 在收到通知後,會發請求給其他所有應用的 actuator/bus-refresh 介面,其他應用又會自動到 Config Server 拉取資料

但這樣會有一個應用承擔著通知 Bus 的責任,這樣不是很合適,可以將 Config Server 也新增 Bus,只將 Config Server 新增 WebHook,這樣變成由 Config Server 收到 WebHook 的請求,並通知 Bus


(4) 結合 Eureka

假設 Eureka 已經啟動,並且 Config 已經註冊到 Eureka 名字為 config-server

這樣應用需要引入 Eureka Client,配置 Eureka 資訊,同時不再配置 Config 的 URI,而是配置 Config 的 Discovery 資訊

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
eureka:
  client:
    serviceUrl:
      register-with-eureka: true
      fetch-registry: true
      defaultZone: http://localhost:8761/eureka/

spring:
  profiles:
    active: dev
---
spring:
  profiles: prod
  application:
    name: config-client
  cloud:
    config:
      label: master
      profile: prod
      discovery:
        enabled: true
        service-id: config-server
---
spring:
  profiles: dev
  application:
    name: config-client
  cloud:
    config:
      label: master
      profile: dev
      discovery:
        enabled: true
        service-id: config-server
@SpringBootApplication
@EnableEurekaClient
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

訪問啟動的 show 介面,同樣可以得到 git 的配置

Consul

Consul 是一個一站式的元件,集成了不少功能,包括服務發現和配置中心

consul 的安裝參考 Spring Cloud (二):服務發現 Eureka 和 Consul

現在實現一個可以自動獲取配置的應用

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-consul-config</artifactId>
</dependency>
# bootstrap.yml
spring:
  application:
    name: consul-client-service-1
  cloud:
    consul:
      host: localhost    # consul 的 ip 和埠
      port: 8500
      config:
        enabled: true
        format: yaml     # 有四種格式:yaml/files/properties/key-value, 預設 key-value
        prefix: config   # 存在 consul 上的目錄,預設就是 config
        default-context: consul-client-service-1    # 存在 consul 上的應用名字,預設是 spring.application.name
        data-key: data   # 存在 consul 上的配置的 key,對應的 value 就相等於 application.yaml 的所有內容
        watch:
          enabled: true  # 啟用配置自動重新整理,預設 true
          delay: 10000   # 重新整理頻率,單位毫秒,預設 1000
          wait-time: 30  # 查詢等待的時間,單位秒,預設 55
# application.yml
spring:
  profiles:
    active: dev   # 這個值同樣會在 consul 上用到
                  # consul 的路徑應該是 /config/consul-client-service-1,dev/data

server:
  port: 9000

在 Consul 上選擇 KEY/VALUE 然後建立 key,名字為 /config/consul-client-service-1,dev/data,注意最後的 data 後面沒有符號 / 不然會被當成目錄,並且寫上對應的配置內容

database-host: 192.168.1.100

user:
  name: Alice
  sex: female
  age: 30

如下圖

然後在程式中使用配置

@EnableScheduling    // 用於定時拉取配置
@SpringBootApplication
public class DemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}
@RefreshScope     // 用於自動重新整理
@RestController
@RequestMapping("/api/v1")
public class DemoController {
    @Value("${database-host}")
    private String dbHost;

    @Autowired
    private User user;

    @GetMapping(value = "db")
    public String getDB(){
        return dbHost;
    }

    @GetMapping(value = "user")
    public Object getUser(){
        return user;
    }
}
@Component
@EnableConfigurationProperties
@ConfigurationProperties(prefix = "user")
public class User {
    private String name;
    private String sex;
    private int age

    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name= name;
    }

    public String getSex() {
        return sex;
    }
    public void setSex(String sex) {
        this.sex= sex;
    }

    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age= age;
    }
}

啟動程式,訪問程式,可以看到返回的是 Consul 上的配置資訊

修改 Consul 的配置再訪問程式,可以看到配置會自動更新