Table of contents
1 Spring Cloud Config 簡介
1.1 JDBC backend
今次呢篇係關於利用 JDBC backend,我地會用 RDBMS 傳統 DB 黎儲存所有配置資料。
2 建立 RDBMS
今次測試會用到 H2 in-memory DB 以及 MySQL,而 H2 我地只需要喺 code 度初始化就可以。
2.1 建立測試用 MySQL 連線
啟動左 Docker Desktop 之後,執行以下 command,喺 MySQL 度建立一個叫 scc
既 database:
docker container run -d --rm -p 3306:3306 -e MYSQL_ROOT_PASSWORD=root -e MYSQL_USER=mysql -e MYSQL_PASSWORD=mysql -e MYSQL_DATABASE=scc -v "C:/docker-data/mysql:/var/lib/mysql" --name mysql mysql:latest
2.2 建立 table
先進入 Docker container 並且登入 MySQL 既 CLI,存取 scc
database:
docker container exec -it mysql mysql -u"root" -p"root" scc
Spring Cloud Config 默認會用 properties
table,但我地最好自定義 column 名,避免默認既 columns 帶有 reserved keywords,例如 key
、value
。
SQL Server 既例子:
1CREATE TABLE properties (
2 config_client_app_name VARCHAR(200) NOT NULL,
3 config_profile VARCHAR(200) NOT NULL,
4 config_label VARCHAR(200) NOT NULL,
5 config_key VARCHAR(200) NOT NULL,
6 config_value NVARCHAR(MAX) NULL
7);
MySQL 既例子:
1CREATE TABLE properties (
2 config_client_app_name VARCHAR(200) NOT NULL,
3 config_profile VARCHAR(200) NOT NULL,
4 config_label VARCHAR(200) NOT NULL,
5 config_key VARCHAR(200) NOT NULL,
6 config_value VARCHAR(10000) NULL
7) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 DEFAULT COLLATE=utf8mb4_0900_ai_ci;
註:如果想用 properties
以外既 table 名係可以既,稍後可以喺 config server 既配置度改條 SELECT SQL。
2.3 新增紀錄
INSERT INTO properties (config_client_app_name, config_profile, config_label, config_key, config_value)
VALUES ('spring-cloud-config-client-demo', 'dev', 'master', 'my.prop', 'Hello MySQL!');
3 動手寫
3.1 Config server
Project structure:
src/main/java
src/main/resources
application.yml
bootstrap.yml
data-h2.sql
schema-h2.sql
3.1.1 Maven dependencies
1<dependencyManagement>
2 <dependencies>
3 <dependency>
4 <groupId>org.springframework.boot</groupId>
5 <artifactId>spring-boot-dependencies</artifactId>
6 <version>2.7.11</version>
7 <type>pom</type>
8 <scope>import</scope>
9 </dependency>
10 <dependency>
11 <groupId>org.springframework.cloud</groupId>
12 <artifactId>spring-cloud-dependencies</artifactId>
13 <version>2021.0.7</version>
14 <type>pom</type>
15 <scope>import</scope>
16 </dependency>
17 </dependencies>
18</dependencyManagement>
19
20<dependencies>
21 <dependency>
22 <groupId>org.springframework.cloud</groupId>
23 <artifactId>spring-cloud-config-server</artifactId>
24 </dependency>
25 <dependency>
26 <groupId>org.springframework.boot</groupId>
27 <artifactId>spring-boot-starter-data-jdbc</artifactId>
28 </dependency>
29
30 <!-- MySQL -->
31 <dependency>
32 <groupId>com.mysql</groupId>
33 <artifactId>mysql-connector-j</artifactId>
34 </dependency>
35
36 <!-- SQL Server -->
37 <dependency>
38 <groupId>com.microsoft.sqlserver</groupId>
39 <artifactId>mssql-jdbc</artifactId>
40 <version>12.2.0.jre11</version>
41 </dependency>
42
43 <!-- H2 -->
44 <dependency>
45 <groupId>com.h2database</groupId>
46 <artifactId>h2</artifactId>
47 </dependency>
48</dependencies>
3.1.2 Java code
MainConfigServerApp.java
:
1@EnableConfigServer
2@SpringBootApplication
3public class MainConfigServerApp {
4
5 public static void main(String[] args) {
6 SpringApplication.run(MainConfigServerApp.class, args);
7 }
8}
3.1.3 Bootstrap 配置
bootstrap.yml
:
1encrypt:
2 key: michael ## or use the ENCRYPT_KEY environment variable
3
4spring:
5 profiles:
6 active: jdbc ## should be externalized
7 cloud:
8 config:
9 server:
10 encrypt:
11 enabled: true
註:一定要用 jdbc
既 Spring profile 先可以啟用到 JDBC backend。
3.1.4 Application 配置
我地喺原先既 Git backend 配置度再加上 JDBC backend 既配置。
application.yml
:
1spring:
2 datasource:
3# MySQL
4# url: jdbc:mysql://localhost:3306/scc
5# username: mysql
6# password: mysql
7# H2
8 url: jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;TRACE_LEVEL_FILE=4
9 username: sa
10 password:
11 h2:
12 console:
13 enabled: true
14 path: /h2-console
15 sql:
16 init:
17 mode: always
18 platform: h2
19 cloud:
20 config:
21 server:
22 git:
23 clone-on-start: true
24 refresh-rate: 10
25 ignore-local-ssh-settings: true
26 uri: git@github.com:blackr1234/spring-cloud-config-demo.git
27 default-label: master
28 search-paths:
29 - "{application}"
30 host-key: 'AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBEmKSENjQEezOmxkZMy7opKgwFB9nkt5YRrYMjNuG5N87uRgg6CLrbo5wAdT/y6v0mKV0U2w0WZ2YB/++Tpockg='
31 host-key-algorithm: ecdsa-sha2-nistp256
32 private-key: |
33 -----BEGIN EC PRIVATE KEY-----
34 MHcCAQEEIPnupl8oxl0Wj6xfOd/PobBG48m3pVkmubPem1XSyexEoAoGCCqGSM49
35 AwEHoUQDQgAEUGO+DDpbpsgp3C+H68iTTaklmcnk2MSbbh4bwQVnMws09eqFqvA4
36 RzcTRtAXt2IWkq4JHUg6rtjDnc/0zwSQyQ==
37 -----END EC PRIVATE KEY-----
38 jdbc:
39 enabled: true
40 sql: "SELECT config_key, config_value FROM properties WHERE config_client_app_name = ? AND config_profile = ? AND config_label = ?"
41 order: 1
註:
spring.cloud.config.server.jdbc.sql
- 條 SQL prepared statement 唔預你大改,因為啲 params(
?
)會塞既 values 係固定位置既。
- 佢主要係畀你加減下啲 quotes,去令到條 SQL 可以適用喺我地用既 DB 度,例如
key
同 value
喺某啲版本既某啲 DB 可以係 reserved words,咁既話就一定要加 quotes。
- 如果想用默認既
properties
以外既 table 名,可以喺度改。
- 除非用最新版 Spring Cloud Config
4.0.1
,否則 JDBC backend 係唔支援 default label,即係同 Git backend 唔同。
3.1.5 初始化 H2 in-memory DB 既配置檔
src/main/resources/schema-h2.sql
:
1CREATE TABLE properties (
2 config_client_app_name VARCHAR(200) NOT NULL,
3 config_profile VARCHAR(200) NOT NULL,
4 config_label VARCHAR(200) NOT NULL,
5 config_key VARCHAR(200) NOT NULL,
6 config_value CLOB(10K) NULL
7);
src/main/resources/data-h2.sql
:
INSERT INTO properties (config_client_app_name, config_profile, config_label, config_key, config_value)
VALUES ('spring-cloud-config-client-demo', 'dev', 'master', 'my.prop', 'Hello H2!');
3.2 Config client
4 簡單測試
4.1 步驟
- 啟動 config server。
- 啟動 config client。
- 檢查
my.prop
既 value,應該係而家 DB 裡面既 value。
- 更改 DB 裡面既
my.prop
既 value。
- Call config client 既
/actuator/refresh
API。
- Response body 裡面會有一個 array,其中一個 string 就係
my.prop
,意味住 my.prop
既 value 改變左。
- 檢查
my.prop
既 value,應該係我地改成既新 value。
4.2 檢查 property value
針對 my.prop
,我地有以下既方法:
- Call config client 既
/actuator/env
API,response body 會有 my.prop
既 value,呢個方法只需要配置好 Actuator 就可以用。
- 因為我地有特登寫 code 去做 logging,所以我地可以留意 config client 既 log 裡面
my.prop
既 value。
5 配置紀錄既應用次序
5.1 自己發現到既 bug
我喺 2023-01-07 發現到 spring-cloud-config-server
版本 4.0.0
或以前既 JDBC backend 對於配置紀錄既應用次序同 Git backend 有唔同,之後 Spring Cloud 項目既維護人員確認左個 bug,然後喺新版本 4.0.1
度 fix。
5.1.1 問題核心
Git backend 既實現畀到出黎既次序係:
application
{spring.application.name}
application-{profile}
{spring.application.name}-{profile}
但當我地使用 JDBC backend,個次序就會變左:
application
application-{profile}
{spring.application.name}
{spring.application.name}-{profile}
呢個 bug 一直存在喺 JDBC backend 既 code,但唔影響 Git backend。
5.1.2 即時解決方法
確定適用既 Spring Boot 版本:2.7.1
如果等唔切 spring-cloud-config-server
推出新版本,或者因為某啲原因而唔想用新版本,咁喺使用舊版本既情況下,我地可以覆蓋 Spring 有問題既 Java class。
以下係改好左既 org.springframework.cloud.config.server.environment.JdbcEnvironmentRepository
,我地只要將佢放喺我地既 config server 既 src/main/java
既對應 package 度,個 Java app 運行既時候就會用我地果個版本既 Java class。
唯一既改動就係將 findOne
method 既 2 個 for loops 調轉。
1package org.springframework.cloud.config.server.environment;
2
3457891011
12public class JdbcEnvironmentRepository implements EnvironmentRepository, Ordered {
13
14 // ...
15
16 @Override
17 public Environment findOne(String application, String profile, String label) {
18
19 // ...
20
21 // XXX fixed the order
22 for (String env : envs) {
23 for (String app : applications) {
24 try {
25 Map<String, Object> next = this.jdbc.query(this.sql, this.extractor, app, env, label);
26 if (next != null && !next.isEmpty()) {
27 environment.add(new PropertySource(app + "-" + env, next));
28 }
29 }
30 catch (DataAccessException e) {
31 if (!failOnError) {
32 if (logger.isDebugEnabled()) {
33 logger.debug("Failed to retrieve configuration from JDBC Repository", e);
34 }
35 }
36 else {
37 throw e;
38 }
39 }
40 }
41 }
42 return environment;
43 }
44}
註:
- 呢個方法都係試出黎。
- Spring 既 class loading 複雜,而根據經驗,新舊版本既實現都可以好唔同。
- 並唔能夠保證舊版或者新版既 Spring Boot 項目甚或冇用 Spring 既 Maven 項目都一樣可以咁做黎覆蓋第三方 library 既 source code。
- 但至少試過喺呢個 Spring Boot 版本下打包成 JAR 之後都可以成功覆蓋到。
6 連線容錯測試
根據測試,JDBC backend 並唔容許 DB 連線失敗,因為佢冇用 cache,所以當個 config DB 有 downtime,config client 係有可能啟動唔到。
6.1 使用 JDBC backend 而 DB 連線失敗
模擬連線失敗場景:
- 啟動 MySQL Docker container。
- 啟動 config server。
- 啟動 config client。
- 呢個時候,我地可以見到 config server 既 log 度有 SELECT SQL 既 logs。
- 暫停 MySQL Docker container。
- 重新啟動 config client,可以見到 config client 啟動既時候停左喺度一陣之後,就因為喺 config server 度拎唔到配置而繼續執行,而視乎個 JAR 檔裡面既配置能唔能夠滿足到啲 Spring beans 既需要,個 config client 有可能會啟動失敗。咁都係因為 config server 冇任何 cache,所以佢每次都會問 DB 拎配置紀錄。
7 用返 Git backend
當我地已經用緊 JDBC backend,但係有需要用返 Git 既話,就要重新執行過個 config server。
我地需加上以下既配置去 disable datasource 既 auto-configuration:
spring.autoconfigure.exclude: org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration,org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration
另外,我地亦都要:
- 唔用
jdbc
Spring profile。
- 移除
spring.datasource
既相關配置。
- 將
spring.cloud.config.server.jdbc.enabled
設置成 false
。