微服務專題07 - Spring Cloud 配置管理

前言

前面的章節我們講了微服務專題06-雲原生應用(Cloud Native Applications)。本節,繼續微服務專題的內容分享,共計16小節,分別是:

本節內容重點爲:

  • Environment 端點:介紹/env 端點的使用場景,並且解讀其源碼,瞭解其中奧祕
  • 基本使用:介紹@EnableConfigServerEnvironment 倉儲
  • 分佈式配置官方實現:介紹 Spring 官方標準分佈式配置實現方式:JDBC 實現
  • 動態配置屬性 Bean:介紹@RefreshScope基本用法和使用場景,並且說明其中的侷限性
  • 健康指標:介紹 Spring Boot 標準端口(/health)以及 健康指標(Health Indicator)
  • 健康指標自定義實現:實現分佈式配置的健康指標自定義實現

開源項目

做過 SpringCloud 配置管理的同學一定會接觸一些企業級的配置管理框架,這裏給出參考。

國內知名開源項目

百度 Disconf

攜程 Apollo

阿里 Nacos

國外知名開源項目

Spring Cloud Config

Netfix Archaius

Apache Zookeeper

客戶端

Spring Cloud Config 的前世今生

傳統的配置管理是基於 Spring Stack 來實現的,所以 Client 與 Server 是通過 Spring 進行關聯的:
在這裏插入圖片描述
Q:Spring Cloud 的分佈式配置如何設計的呢?

A:Spring Cloud 的配置讀取是在客戶端啓動時就加載配置服務器。而通常分佈在不同地域,不同機器上的客戶端配置是不一樣的,比如中國的 Client 的 QPS 是1000,而美國的 Client 的 QPS 是 500,則可通過服務熔斷的機制 ${app.qps} 去設計。並且 SpringCloud 最新版本服務器端支持加載 Github/SVN、數據庫以及配置文件這幾種方式。
在這裏插入圖片描述

Java Client 自行讀取 HttpClient

在傳統的項目配置管理,Java Client 自行讀取HttpClient,通常的流程是這樣的:

  1. 首先獲取配置文件文本信息
  2. 將文本信息轉化爲 Properties 對象
  3. 根據配置文件(yml/properties)的 key 讀取到相應的值
  4. 業務邏輯代碼加載 value 值(通過 Spring 注入配置)
  5. 新的配置就對於 APP 生效了

Q:傳統意義上的配置客戶端通過Http 拉取配置服務器上的配置有什麼弊端?

A: 通過http拉取的過程中,Http1.1 版本的協議是無狀態的,即短連接。這就意味着,客戶端每次在更新的時候就必須採取輪詢策略,而長期運作的情況顯然不是我們願意看到的結果。

配置三方庫

面對 JavaClient 採用 Http 短連接的弊病,我們通常可以採用第三方庫來對配置文件進行管理。

開源項目 配置源順序(配置優先級覆蓋) 配置源(存儲媒介) 轉換類型(編程便利性)
Apache Commons Configuration Configuration 豐富,基本支持所有類型
Spring FrameWork addFirst()優先覆蓋 PropertySource

Apache Commons Configuration

pom座標如下:

<dependency>
	<groupId>commons-configuration</groupId>
	<artifactId>commons-configuration</artifactId>
	<version>1.9</version>
</dependency>

展開源碼,發現 commons-configuration 包裏面的核心類 Configuration 提供大多數常見類型的 Value 轉換。
在這裏插入圖片描述

原因在於 Properties 集成了Hashtable 的 key 和 value 都是 Object 類型:
在這裏插入圖片描述
接下來看一下 Configuration 的實現類:
在這裏插入圖片描述
實現類有很多種,這裏舉例幾個常見的子類實現用以說明:

  • PropertiesConfiguration: 將 Properties 作爲 Configuration 配置

  • MapConfiguration: 以 Map 形式存儲 配置信息

    • EnvironmentConfiguration : OS 環境變量
    • SystemConfiguration : Java 系統屬性
  • CompositeConfiguration:將配置組合起來,存儲方式爲List < Configuration >

不論Spring原生的配置讀取,還是第三方庫的配置讀取,最核心概念在於:配置源、以及它們優先次序配置轉換能力

Q:看到這裏,我們不禁思考,HTTP 資源算不算一個配置?

A:通常我們所說的配置源指的是文件、HTTP 資源、數據源、 Git 等,但是殊途同歸,都是以URL形式存在,所以HTTP 資源算一個配置。

  • 文件對應的URL路徑 file:///
  • http資源對應的URL路徑 http://
  • 數據源對應的URL路徑jdbc://
  • git 對應的URL路徑 git://

Spring FrameWork

前面提到的 commons-configuration 方式使通過 Apache Commons Configuration 來實現的,那麼在Spring FrameWork 則通過Environment作爲媒介進行配置管理。

在這裏插入圖片描述

在其子實現類 ConfigurableEnvironment 有這樣的方法:

MutablePropertySources getPropertySources();

而MutablePropertySources 則是用來存放配置源的集合的:
在這裏插入圖片描述

關於 PropertySource 配置源 ,對比 Apache 的 PropertiesConfiguration ,同樣在 PropertySource 存在諸多實現類:

  • MapPropertySource
    • PropertiesPropertySource
  • CompositePropertySource : 將配置組合起來,存儲方式爲LinkedHashSet<PropertySource> ,特點就是有序並且可以去重。
    在這裏插入圖片描述

Tips: LinkedHashSet 與 LinkedHashMap 有什麼區別呢?

  • SystemEnvironmentPropertySource 環境變量配置

Spring Cloud 客戶端配置定位擴展 : PropertySourceLocator

Spring Cloud 客戶端配置管理

現在我們整理一下客戶端與服務端的配置流程:

在這裏插入圖片描述

  1. 首先要明確一點,項目的外部化配置是有很多種形式的,比如命令行參數、Java System 屬性、application properties 配置文件等。
  2. 但是現在想要通過一個服務器作爲配置管理,就應該將配置中心的加載順序放到首位。我們知道在 getProperties 的過程中,會採取 addFirst 的形式。
  3. 通過上一節的介紹了上下文層次的加載順序問題,不就是正好可以解決這一個問題麼?Bootstrap上下文是所有上下文的parent ,所以我們可以將配置中心置於Bootstrap ApplicationContext(Bootstrap父上下文)。將其他形式的配置設置爲 Service ApplicationContext(服務級上下文)。
  4. 這樣通過客戶端啓動時加載時就會優先加載來自於 Bootstrap 父上下文中的配置中心的內容。

服務端

基於 Git 實現

實現的效果是,通過配置 Spring Cloud Config 的服務端,將 Git 倉庫中的配置加載出來:

我這裏將git版本庫放在了/resources/configs目錄下,並用以不同的profile加以區分。

在這裏插入圖片描述

  1. 配置服務端依賴,篇幅有限,完整依賴請移步至文末 Github 代碼地址。
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.1.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
   
    <properties>
        <spring-cloud.version>Hoxton.SR6</spring-cloud.version>
    </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</artifactId>
        </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>
  1. 通過 @EnableConfigServer 聲明當前的服務是 Spring Cloud Config 的服務端。
@SpringBootApplication
@EnableConfigServer
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }

}
  1. 配置文件相關

版本化配置

## 配置服務器應用名字
spring.application.name = config-server

## 設置服務端口號
server.port = 10086

## 配置服務器git本地文件系統路徑
spring.cloud.config.server.git.uri = \
${user.dir}/src/main/resources/configs/

版本文件

config.properties文件:

name = jack

config-test.properties文件:

name = tom
  1. 服務訪問

當我們訪問:http://localhost:10086/config/default 實際上讀取的是configs目錄下的config.properties配置文件。

在這裏插入圖片描述

當我們訪問:http://localhost:10086/config/test 實際上讀取的是configs目錄下的config-test.properties配置文件。

在這裏插入圖片描述
注意git的版本號:

在這裏插入圖片描述

Spring Cloud Config 實現一套完整的配置管理 API 設計。在配置的使用常採用三段式風格設置路徑,即 /應用名/profile/ $ {label},$ {label} 代表分支。如果不聲明分支則默認加載master主分支。如果profile環境也不聲明就等同於 /應用名.properties。

以上演示的DEMO我們也要知道是有很多問題的:

  • 複雜的版本更新機制( Git 倉庫)
    • 版本
    • 分支
    • 提交
    • 配置
  • 憋腳的內容更新(實時性不高)
    • 客戶端第一次啓動拉取
    • 需要整合 BUS 做更新通知

設計原理

前文 demo 中所演示的 Config Server 中涉及到 @EnableConfigServer 這個註解,現在我們就分析一下:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(ConfigServerConfiguration.class)
public @interface EnableConfigServer {

}

注意使用了 @Import 說明實際配置類爲 ConfigServerConfiguration

@Configuration
public class ConfigServerConfiguration {
	class Marker {}

	@Bean
	public Marker enableConfigServerMarker() {
		return new Marker();
	}
}

我們發現,在 ConfigServerAutoConfiguration 裏實際應用了 ConfigServerConfiguration 這個類:

@Configuration
@ConditionalOnBean(ConfigServerConfiguration.Marker.class)
@EnableConfigurationProperties(ConfigServerProperties.class)
@Import({ EnvironmentRepositoryConfiguration.class, CompositeConfiguration.class, ResourceRepositoryConfiguration.class,
		ConfigServerEncryptionConfiguration.class, ConfigServerMvcConfiguration.class })
public class ConfigServerAutoConfiguration {

}

從這裏我們發現:當應用配置類標註了 @EnableConfigSever , 導入 ConfigServerConfiguration,並註冊 Marker Bean,而 Marker Bean 也是作爲 ConfigServerAutoConfiguration 條件之一。

案例分析 JDBC 實現

  • JdbcTemplate Bean 來源

    • JdbcTemplateAutoConfiguration
  • SQL 來源

    • JdbcEnvironmentProperties ,內容爲:spring.cloud.config.server.jdbc.sql ,如果不配置,默認使用DEFAULT_SQL,即:
            SELECT KEY, VALUE from PROPERTIES where APPLICATION=? and PROFILE=? and LABEL=?
    

回顧上面的demo,結合源碼,我們也會得出以下結論:

KEY VALUE APPLICATION PROFILE LABEL
name zhangsan config default master
name lisi config test master

本質說明:

  • JDBC :連接技術

  • DB : 存儲介質

  • 核心接口: EnvironmentRepository

Q:是否可以自定義 EnvironmentRepository 實現?

A:前提:如何激活自定義的 EnvironmentRepository 實現,首先找到爲什麼默認是 Git 作爲配置倉庫的原因:

@Configuration
@ConditionalOnMissingBean(value = EnvironmentRepository.class, search = SearchStrategy.CURRENT)
class DefaultRepositoryConfiguration {
	...
	@Bean
	public MultipleJGitEnvironmentRepository defaultEnvironmentRepository(
	        MultipleJGitEnvironmentRepositoryFactory gitEnvironmentRepositoryFactory,
			MultipleJGitEnvironmentProperties environmentProperties) throws Exception {
		return gitEnvironmentRepositoryFactory.build(environmentProperties);
	}
}

當 Spring 應用上下文沒有出現 EnvironmentRepository Bean 的時候,那麼,默認激活 DefaultRepositoryConfiguration (Git 實現),否則採用自定義實現。

自定義實現

自定義 EnvironmentRepository Bean

    @Bean
    public EnvironmentRepository environmentRepository() {
        return (String application, String profile, String label) -> {
            Environment environment = new Environment("default", profile);
            List<PropertySource> propertySources = environment.getPropertySources();
            Map<String, Object> source = new HashMap<>();
            source.put("name", "test");
            PropertySource propertySource = new PropertySource("map", source);
            // 追加 PropertySource
            propertySources.add(propertySource);
            return environment;
        };
    }

以上實現將失效 DefaultRepositoryConfiguration 裝配。

比較 Spring Cloud 內建配置倉儲的實現

  • Git 方式:早放棄

  • JDBC 方式:太簡單

  • Zookeeper 方式: 比較適合做分佈式配置

  • 自定義方式:是高端玩家

後記

本節代碼地址:https://github.com/harrypottry/spring-cloud-config-server

更多架構知識,歡迎關注本套Java系列文章Java架構師成長之路

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章