目錄導航
前言
前面的章節我們講了微服務專題06-雲原生應用(Cloud Native Applications)。本節,繼續微服務專題的內容分享,共計16小節,分別是:
- 微服務專題01-Spring Application
- 微服務專題02-Spring Web MVC 視圖技術
- 微服務專題03-REST
- 微服務專題04-Spring WebFlux 原理
- 微服務專題05-Spring WebFlux 運用
- 微服務專題06-雲原生應用(Cloud Native Applications)
- 微服務專題07-Spring Cloud 配置管理
- 微服務專題08-Spring Cloud 服務發現
- 微服務專題09-Spring Cloud 負載均衡
- 微服務專題10-Spring Cloud 服務熔斷
- 微服務專題11-Spring Cloud 服務調用
- 微服務專題12-Spring Cloud Gateway
- 微服務專題13-Spring Cloud Stream (上)
- 微服務專題14-Spring Cloud Bus
- 微服務專題15-Spring Cloud Stream 實現
- 微服務專題16-Spring Cloud 整體回顧
本節內容重點爲:
- Environment 端點:介紹
/env
端點的使用場景,並且解讀其源碼,瞭解其中奧祕 - 基本使用:介紹
@EnableConfigServer
、Environment
倉儲 - 分佈式配置官方實現:介紹 Spring 官方標準分佈式配置實現方式:JDBC 實現
- 動態配置屬性 Bean:介紹
@RefreshScope
基本用法和使用場景,並且說明其中的侷限性 - 健康指標:介紹 Spring Boot 標準端口(
/health
)以及 健康指標(Health Indicator) - 健康指標自定義實現:實現分佈式配置的健康指標自定義實現
開源項目
做過 SpringCloud 配置管理的同學一定會接觸一些企業級的配置管理框架,這裏給出參考。
國內知名開源項目
國外知名開源項目
客戶端
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,通常的流程是這樣的:
- 首先獲取配置文件文本信息
- 將文本信息轉化爲 Properties 對象
- 根據配置文件(yml/properties)的 key 讀取到相應的值
- 業務邏輯代碼加載 value 值(通過 Spring 注入配置)
- 新的配置就對於 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>
,特點就是有序並且可以去重。
SystemEnvironmentPropertySource
環境變量配置
Spring Cloud 客戶端配置定位擴展 : PropertySourceLocator
Spring Cloud 客戶端配置管理
現在我們整理一下客戶端與服務端的配置流程:
- 首先要明確一點,項目的外部化配置是有很多種形式的,比如命令行參數、Java System 屬性、application properties 配置文件等。
- 但是現在想要通過一個服務器作爲配置管理,就應該將配置中心的加載順序放到首位。我們知道在 getProperties 的過程中,會採取 addFirst 的形式。
- 通過上一節的介紹了上下文層次的加載順序問題,不就是正好可以解決這一個問題麼?Bootstrap上下文是所有上下文的parent ,所以我們可以將配置中心置於Bootstrap ApplicationContext(Bootstrap父上下文)。將其他形式的配置設置爲 Service ApplicationContext(服務級上下文)。
- 這樣通過客戶端啓動時加載時就會優先加載來自於 Bootstrap 父上下文中的配置中心的內容。
服務端
基於 Git 實現
實現的效果是,通過配置 Spring Cloud Config 的服務端,將 Git 倉庫中的配置加載出來:
我這裏將git版本庫放在了/resources/configs目錄下,並用以不同的profile加以區分。
- 配置服務端依賴,篇幅有限,完整依賴請移步至文末 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>
- 通過
@EnableConfigServer
聲明當前的服務是 Spring Cloud Config 的服務端。
@SpringBootApplication
@EnableConfigServer
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
- 配置文件相關
版本化配置
## 配置服務器應用名字
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
- 服務訪問
當我們訪問: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架構師成長之路