SpringCloud系列第08節之配置中心Config

統一配置中心

微服務架構中,每一個微服務的運行,都會讀取不一樣環境的不一樣配置信息java

而Spring Cloud Config(百度的 Disconf 與之相似)便提供了適用於分佈式系統的、集中式的外部化配置支持git

它可以統一集中管理全部應用的、全部環境的配置文件,且支持熱更新github

其默認採用 git 倉庫存儲配置信息,好處是 git 工具即可輕鬆管理配置內容web

(雖然也支持 svn 倉庫存儲和本地存儲,但相信,不多有人這麼作)spring

更多內容,可參考:http://cloud.spring.io/spring-cloud-static/Camden.SR4/#_spring_cloud_configapache

關於配置倉庫裏面文件內容的加密,可考慮:http://cloud.spring.io/spring-cloud-vault/bootstrap

關於配置中心的高可用,可參考:https://github.com/spring-cloud/spring-cloud-config/issues/87c#

關於配置中心與註冊中心聯合使用,可參考:https://github.com/spring-cloud/spring-cloud-config/blob/master/docs/src/main/asciidoc/spring-cloud-config.adoc#discovery-first-bootstrap架構

一些規則

yml的加載順序

應用讀取配置中心參數時,會配置配置中心的地址等相關參數,而這部分配置需優先於 application.yml 被應用讀取app

SpringCloud 中的 bootstrap.yml 是會比 application.yml 先加載的,因此這部分配置要定義在 bootstrap.yml 裏面

這就引伸出兩個須要注意的地方

  • spring.application.name
    它應該配置在 bootstrap.yml,它的名字應該等於配置中心的配置文件的 {application}
    因此配置中心在給配置文件取名字時,最好讓它等於對應的應用服務名

  • 配置中心與註冊中心聯合使用
    若應用經過 serviceId 而非 url 來指定配置中心,則 eureka.client.serviceUrl.defaultZone 也要配置在 bootstrap.yml
    要不啓動的時候,應用會找不到註冊中心,天然也就找不到配置中心了

url的映射關係

/{application}/{profile}[/{label}]

/{application}-{profile}.yml

/{label}/{application}-{profile}.yml

/{application}-{profile}.properties

/{label}/{application}-{profile}.properties

其中,{label} 對應 git 分支,{application}-{profile}.properties 對應配置文件的名字

因此,能夠根據不一樣的 url 來訪問不一樣的配置內容,好比本文的示例就對應下面連接

http://127.0.0.1:4100/demo.cloud.config/dev/master

http://127.0.0.1:4100/demo.cloud.config-dev.yml

http://127.0.0.1:4100/master/demo.cloud.config-test.yml

http://127.0.0.1:4100/demo.cloud.config-prod.properties

http://127.0.0.1:4100/master/demo.cloud.config-dev.properties

屬性的熱加載

一、顯式標註 @RefreshScope

二、添加依賴 spring-boot-starter-actuator

三、刷新屬性 curl -X POST http://127.0.0.1:2100/refresh(大寫的 X 和 POST)

示例代碼

示例代碼以下(也能夠直接從 Github 下載:https://github.com/v5java/demo-cloud-08-config

它是由四個模塊組成的 Maven 工程,包含了一個註冊中心、一個配置中心、兩個讀取了配置中心屬性的服務提供方

這是公共的 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/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.jadyer.demo</groupId>
    <artifactId>demo-cloud-08-config</artifactId>
    <version>1.1</version>
    <packaging>pom</packaging>
    <modules>
        <module>service-config</module>
        <module>service-discovery</module>
        <module>service-server</module>
    </modules>
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.4.5.RELEASE</version>
    </parent>
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Camden.SR6</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.5.1</version>
                <configuration>
                    <source>1.7</source>
                    <target>1.7</target>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

註冊中心

這是註冊中心的 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/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>com.jadyer.demo</groupId>
        <artifactId>demo-cloud-08-config</artifactId>
        <version>1.1</version>
    </parent>
    <artifactId>service-discovery</artifactId>
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-eureka-server</artifactId>
        </dependency>
    </dependencies>
</project>

這是註冊中心的配置文件 /src/main/resources/application.yml

server:
  port: 1100

eureka:
  server:
    enable-self-preservation: false       # 關閉自我保護模式(缺省爲打開)
    eviction-interval-timer-in-ms: 1000   # 續期時間,即掃描失效服務的間隔時間(缺省爲60*1000ms)
  client:
    # 設置是否從註冊中心獲取註冊信息(缺省true)
    # 由於這是一個單點的EurekaServer,不須要同步其它EurekaServer節點的數據,故設爲false
    fetch-registry: false
    # 設置是否將本身做爲客戶端註冊到註冊中心(缺省true)
    # 這裏爲不須要(查看@EnableEurekaServer註解的源碼,會發現它間接用到了@EnableDiscoveryClient)
    register-with-eureka: false
    # 在未設置defaultZone的狀況下,註冊中心在本例中的默認地址就是http://127.0.0.1:1100/eureka/
    # 但奇怪的是,啓動註冊中心時,控制檯仍是會打印這個地址的節點:http://localhost:8761/eureka/
    # 而實際服務端註冊時,要使用1100端口的才能註冊成功,8761端口的會註冊失敗並報告異常
    serviceUrl:
      # 實際測試:若修改尾部的eureka爲其它的,好比/myeureka,註冊中心啓動沒問題,但服務端在註冊時會失敗
      # 報告異常:com.netflix.discovery.shared.transport.TransportException: Cannot execute request on any known server
      defaultZone: http://127.0.0.1:${server.port}/eureka/

這是註冊中心的 SpringBoot 啓動類 ServiceDiscoveryBootStrap.java

package com.jadyer.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;

//建立服務註冊中心
@EnableEurekaServer
@SpringBootApplication
public class ServiceDiscoveryBootStrap {
    public static void main(String[] args) {
        SpringApplication.run(ServiceDiscoveryBootStrap.class, args);
    }
}

配置中心

這是配置中心的 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/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>com.jadyer.demo</groupId>
        <artifactId>demo-cloud-08-config</artifactId>
        <version>1.1</version>
    </parent>
    <artifactId>service-config</artifactId>
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-config-server</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-eureka</artifactId>
        </dependency>
    </dependencies>
</project>

這是配置中心的配置文件 /src/main/resources/application.yml

server:
  port: 4100

spring:
  application:
    name: jadyer-config-server
  cloud:
    config:
      server:
        git:
          uri: https://github.com/v5java/demo-cloud-08-config  # 配置git倉庫的地址
          searchPaths: config-repo                             # git倉庫下的相對地址(多個則用半角逗號分隔)
          # username: username                                 # 只有private的項目才需配置用戶名和密碼
          # password: password                                 # 只有private的項目才需配置用戶名和密碼

eureka:
  instance:
    instance-id: ${spring.application.name}:${server.port}
    prefer-ip-address: true
    lease-renewal-interval-in-seconds: 5
    lease-expiration-duration-in-seconds: 15
  client:
    healthcheck:
      enabled: true
    serviceUrl:
      defaultZone: http://127.0.0.1:1100/eureka/

這是配置中心的 SpringBoot 啓動類 ServiceConfigBootStarp.java

package com.jadyer.demo;
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;

//開啓配置中心
@EnableConfigServer
@EnableEurekaClient
@SpringBootApplication
public class ServiceConfigBootStarp {
    public static void main(String[] args) {
        SpringApplication.run(ServiceConfigBootStarp.class, args);
    }
}

服務提供方01

這是第一個服務提供方的 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/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>com.jadyer.demo</groupId>
        <artifactId>demo-cloud-08-config</artifactId>
        <version>1.1</version>
    </parent>
    <artifactId>service-server</artifactId>
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-config</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-eureka</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
    </dependencies>
</project>

這是第一個服務提供方的配置文件 /src/main/resources/application.yml

server:
  port: 2100

#spring:
#  application:
#    name: CalculatorServer                     # 指定發佈的微服務名(之後調用時,只需該名稱便可訪問該服務)

eureka:
  instance:
    instance-id: ${spring.application.name}:${server.port}
    prefer-ip-address: true                     # 設置微服務調用地址爲IP優先(缺省爲false)
    lease-renewal-interval-in-seconds: 5        # 心跳時間,即服務續約間隔時間(缺省爲30s)
    lease-expiration-duration-in-seconds: 15    # 發呆時間,即服務續約到期時間(缺省爲90s)
  client:
    healthcheck:
      enabled: true                                # 開啓健康檢查(依賴spring-boot-starter-actuator)
    #serviceUrl:
    #  defaultZone: http://127.0.0.1:1100/eureka/  # 指定服務註冊中心的地址

這是第一個服務提供方的配置文件 /src/main/resources/bootstrap.yml

注:實際使用時,spring.cloud.config.profile 不需配置,它會自動讀取 spring.profiles.active 的值

spring:
  application:
    name: demo.cloud.config         # 指定配置中心配置文件的{application}
  cloud:
    config:
      #uri: http://127.0.0.1:4100/  # 指定配置中心的地址
      profile: prod                 # 指定配置中心配置文件的{profile}
      label: master                 # 指定配置中心配置文件的{label}
      discovery:
        enabled: true                    # 使用註冊中內心面已註冊的配置中心
        serviceId: jadyer-config-server  # 指定配置中心註冊到註冊中心的serviceId

eureka:
  client:
    serviceUrl:
      defaultZone: http://127.0.0.1:1100/eureka/

這是第一個服務提供方的 SpringBoot 啓動類 ServiceServerBootStarp.java

package com.jadyer.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;

/**
 * 經過 @EnableEurekaClient 註解,爲服務提供方賦予註冊和發現服務的能力
 * --------------------------------------------------------------------------------------------
 * 也可使用org.springframework.cloud.client.discovery.@EnableDiscoveryClient註解
 * 詳見如下兩篇文章的介紹
 * http://cloud.spring.io/spring-cloud-static/Camden.SR3/#_registering_with_eureka
 * https://spring.io/blog/2015/01/20/microservice-registration-and-discovery-with-spring-cloud-and-netflix-s-eureka
 * --------------------------------------------------------------------------------------------
 * Created by 玄玉<https://jadyer.cn/> on 2017/1/9 16:00.
 */
@EnableEurekaClient
@SpringBootApplication
public class ServiceServerBootStarp {
    public static void main(String[] args) {
        SpringApplication.run(ServiceServerBootStarp.class, args);
    }
}

這是第一個服務提供方讀取配置中心參數(且支持熱加載)的例子 DemoController.java

package com.jadyer.demo;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

//支持配置中心屬性熱加載
@RefreshScope
@RestController
@RequestMapping("/demo/config")
public class DemoController {
    //獲取配置中心的屬性
    @Value("${host.ifs}")
    private String ifsHost;

    @GetMapping("/getHost")
    public String getHost(){
        return this.ifsHost;
    }
}

服務提供方02

除了啓動端口爲2200外,其代碼與服務提供方01的徹底相同

驗證

分別訪問兩個服務提供方暴露出來的接口

http://127.0.0.1:2100/demo/config/getHost

http://127.0.0.1:2200/demo/config/getHost

而後再 curl -X POST http://127.0.0.1:2100/refresh 後發現只有2100端口的服務屬性刷新了,2200的沒變

因此纔有了下面的彩蛋

彩蛋

屬性熱加載前,都要手工調用各個應用的刷新接口,即使使用 Git 倉庫的 Webhooks,維護起來也夠費勁的

解決辦法也有,詳見下一篇文章《SpringCloud系列第09節之消息總線Bus》中經過消息總線的方式,實現集羣的自動更新

相關文章
相關標籤/搜索