在Rainbond中實現資料庫結構自動化升級

rainbond發表於2022-02-10

Rainbond 這款產品一直致力於打通企業應用交付的全流程,這個流程中不可或缺的一環是企業應用的不斷升級、迭代。Rainbond 特有的能力,是可以將囊括多個服務元件的企業應用系統進行打包,並執行一鍵安裝、升級以及回滾的操作。上述的內容僅僅解決了應用程式本身的版本控制問題。企業應用的升級迭代流程想要完全實現自動化,還需要能夠自動處理資料庫表結構(Schema)的版本控制。經過不斷的探索,Rainbond 首先在原始碼構建領域藉助業界領先的 Liquibase 整合了雲原生時代的資料庫 Schema 版本管理的能力。

Schema版本管理難題

資料庫表結構(Schema)定義了資料表(Table)的名字,以及每一個資料表中所包含的資料列(Column)的名字、屬性等資訊。它描述了一個資料庫所擁有的框架,記錄在資料庫中的資料都需要遵循 Schema 裡的定義。

區別於應用程式自身的升級,Schema 版本管理問題,本質上是一種持久化資料的升級,這一特徵伴隨著兩個疑問:

  • 持久化資料如何升級:雲原生時代的交付,已經無法跳脫出容器化、平臺化的特徵。各大雲原生平臺在進行軟體交付過程中,都不會輕易將持久化資料納入版本控制體系中去。原因很簡單,每個交付環境中的資料都是不同的,升級過程中很難抉擇持久化資料的統一版本管理方案。
  • 哪些持久化資料需要升級:既然難以抉擇持久化資料的統一版本管理方案,那麼退而求其次,是否可以優先選擇必要的持久化資料進行版本管理。縮小範圍之後,就突出了資料庫表結構這一特殊持久化資料型別。其版本管理的必要性是顯而易見的,應用程式本身從V1版本升級到了V2版本,那麼對應的資料庫表結構也需要增加必要的新表、新列。

這兩個疑問引出了本文的主旨:在企業級軟體交付領域,如何合理的在每次升級的過程中處理資料庫表結構(Schema)的版本控制?

傳統軟體交付領域,在 Schema 版本管理方面有兩種主流的解決方案:

  • 人工處理:這是最基礎的 Schema 版本管理方式。現場交付人員不僅需要處理應用程式的升級流程,也直接運算元據庫,完成 Schema 的升級。這種方法最直接,但是無法自動化處理的流程都具有一些通病:低效、易錯。
  • 程式碼處理:這是一種進階的方式。通過在應用程式內部引入第三方庫,來進行 Schema 的版本管理。這一操作已經可以免除交付現場的人工處理流程,交付人員只需要將應用程式進行更新,程式本身會連線到資料庫,對 Schema 作出自動化的變更。這種方式的自動化程度已經可以滿足要求,但是也具有引入第三方庫的通病:技術成本提升、侵入性、與語言或框架繫結。

雲原生時代的解決思路

雲原生時代,應用程式的使用者、交付者都希望通過所選用的平臺來賦能自己的應用程式。在本文探討的領域中,這種期待可以具體的描述為:藉助平臺能力,以無侵入的方式,將 Schema 版本管理能力賦予應用,使得應用在進行一鍵升級時, Schema 也自動完成升級。

Rainbond 作為一款雲原生應用管理平臺,也在不斷探索為應用賦能之道。在 Schema 版本管理領域,實現了在原始碼構建過程中整合 Schema 版本管理的能力。應用本身不需要改動任何程式碼,僅僅需要將兩種型別的檔案放進程式碼根目錄下的指定目錄下即可。這兩種檔案分別是:定義了資料庫例項連線地址的配置檔案,升級 Schema 所使用的 Sql 指令碼檔案。

關於原始碼構建

原始碼構建功能,本身就是一種 Rainbond 對應用的賦能。雲原生時代,應用都在向容器化的方向邁進。容器化的過程中看似無法免除 Dockerfile 的編寫,實則不然。原始碼構建功能可以直接對接原始碼,將其編譯成為可執行的容器映象。整個過程不需要開發人員的介入,提供程式碼倉庫地址即可,極大的降低了開發人員的技術負擔。

在原始碼構建的流程中,以無侵入的方式整合了很多能力。比如通過納入 Pinpoint-agent 的方式整合 APM 能力。再比如通過納入 jmx-exporter 的方式整合自定義業務監控能力。今天重點描述的,是通過納入 Liquibase 的方式,整合 Schema 版本控制能力。

關於Liquibase

Liquibase 是一款專門用於資料庫表結構版本控制的 CI/CD 工具。從 2006 年開始,Liquibase 團隊一直致力於讓資料庫變更管理更簡單,尤其是在敏捷軟體開發領域。這一工具基於 Apache 2.0 協議開源。

經過長期的迭代,Liquibase 已經非常成熟可靠,通過 sql、yaml、xml、json 在內的多種檔案格式,開發人員可以快速的定義出符合 Liquibase 風格的資料庫表結構變更檔案,這種檔案被稱之為 changelog。基於 changelog 中的定義,Liquibase 可以非常方便的在多個變更操作版本之間升級與回滾。

Liquibase 提供多種方式供開發人員互動,包括一種通用的命令列操作模式,原始碼構建通過命令列形式整合 Liquibase 的 Schema 版本管理能力。

程式碼定義的Schema版本控制能力

Rainbond 原始碼構建推崇程式碼定義各種能力。對於 Schema 版本控制能力而言,也是通過程式碼倉庫中的指定檔案來定義的,我們可以簡要的稱之為 Schema As Code,這種程式碼定義能力的實踐,要求每一次 CI 工作都由一個程式碼倉庫地址開始,比如 Git。對於每一個資料庫例項來說,通過指定目錄下的配置檔案和 changelog 來定義資料庫表結構版本。預設情況下,是指程式碼根目錄下的 Schema目錄。

下面是一個程式碼結構示例,Rainbond 官方同時提供了一份完整的程式碼示例 java-maven-demo :

.
├── Procfile
├── README.md
├── Schema
│   ├── changelog.sql       # 定義資料庫表結構
│   └── mysql.properties    # 定義資料庫例項連線資訊
├── pom.xml
└── src

Schema 目錄下的 mysql.propertieschanglog.sql檔案定義瞭如何進行 Schema 版本控制。

mysql.properties 定義了資料庫例項的連線方式,以及所引用的 changelog 檔案地址。

driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://${MYSQL_HOST}:${MYSQL_PORT}/${MYSQL_DATABASE}?createDatabaseIfNotExist=true&characterEncoding=utf8
username=${MYSQL_USER}
password=${MYSQL_PASSWORD}
changeLogFile=changelog.sql

最簡化定義項包括:

  • driver:指定使用的 jdbc 驅動,原始碼構建中整合的驅動支援mysql、mariadb、mssql、mongo、postgresql、sqlite等常見型別資料庫。
  • url:定義資料庫連線地址,可以通過 jdbc 的標準寫法來預創資料庫例項。
  • username&password:定義資料庫例項的登入憑據。
  • changeLogFile:定義該資料庫例項表結構變更檔案的路徑。

原始碼構建過程中,會遍歷識別 Schema 目錄下的所有 properties 檔案,並在啟動時處理每一個資料庫例項的 Schema 版本控制流程。通過配置檔案的組合,在以下各種常見場景中都可以很好的工作。

  • 單個資料庫例項
  • 多個相同型別資料庫例項,比如應用同時連線了多個 mysql
  • 多個不同型別資料庫例項,比如應用同時連線了mysql、mongo
  • 同個資料庫中的多個資料庫例項,比如應用同時使用同個 mysql 中的多個庫例項

changlog 的最佳實踐

changelog 檔案,是管理 Schema 的關鍵所在。以下是一個示例:

-- liquibase formatted sql
-- changeset guox.goodrain:1
create table person (
id int primary key,
name varchar(50) not null,
address1 varchar(50),
address2 varchar(50),
city varchar(30)
);
-- rollback drop table person;
-- changeset guox.goodrain:2
create table company (
id int primary key,
name varchar(50) not null,
address1 varchar(50),
address2 varchar(50),
city varchar(30)
);
-- rollback drop table company;

推薦使用 sql 型別的 changelog 檔案來定義 Schema 版本,因為這最符合開發人員的習慣。

changlog 檔案通過註釋來定義一些行為。常見如下:

# 定義 changelog 檔案的格式,這是每一個 changelog 檔案的開頭項
-- liquibase formatted sql 
# 定義變更集,後面跟隨的,是開發人員姓名,以及變更集的序號,這個序號很重要,建議使用有序數字來定義
-- changeset guox.goodrain:1
# 定義回滾操作,每一個變更集都應該定義與之對應的回滾操作,這使得在變更出現問題時,快速回滾到指定版本的變更集
-- rollback drop table staff;

Liquibase 官方提出了一系列的最佳實踐,有一些最佳實踐應該作為開發人員的預設行為。

  • 每個變更集僅包含一個變更,通過細化資料庫表結構的變更版本,這可以防止失敗的自動提交語句使資料庫處於意外狀態。
  • changeset 的 ID,選擇有序且獨一無二的數列,或者對開發者友好的名字。
  • 讓版本永遠可回滾,為每一個 changeset 設定合理的回滾操作。

有關於 mysql.propertieschanglog.sql 檔案的寫法,更多的特性請參考 liquibase 文件 ,這些特性都可以被原始碼構建所繼承。

Schema生命週期流程

1. 構建流程

執行正常的原始碼構建流程時,會自動識別程式碼根目錄下的 Schema 目錄,準備 Schema 版本管理所需要的基礎環境,包括 jre 和 Liquibase 工具包。

構建日誌會有以下提示:

2. 啟動流程

完成構建流程後,服務元件會自動進入啟動過程中, Rainbond 平臺會根據程式碼中定義好的配置檔案,針對每一個資料庫例項,進行自動升級處理。

處理過程中,在服務元件的日誌中的頭部位置,會列印相關的記錄:

上圖中演示了針對同一個 mysql 資料庫中的多個庫例項進行表結構的升級操作。對於空的庫例項而言,這也相當於一次初始化的操作。

在示例中,Rainbond 分別嚮應用所連線的同個 mysql 資料庫中的兩個庫例項(分別名為 Initialize anotherdb)進行了表結構初始化操作,分別建立了表companyperson 以及 another_companyanother_person。在資料庫元件的 Web終端登入後,可以驗證:

3. 釋出到元件庫

Rainbond 特有的釋出機制,可以將業務元件和資料庫元件統一發布為一個應用模版。方便在不同的環境中一鍵安裝交付。通過應用模版交付的應用,依然具有 Schema 版本控制的能力。全新安裝的應用模版,其資料庫也會被初始化為上述狀態。在這裡,我們稱釋出的應用為源應用,由應用模版安裝而來的應用為已交付應用。

4. 程式碼更新

當開發人員持續迭代業務系統的時候,Schema 也隨之改動,假定新版本的業務系統,要求 Initialize 新增表 staff,併為已有的 person 表新增一個新的列 country。那麼開發人員應該為對應的 changelog.sql 檔案新增以下內容,並和新的業務程式碼一併提交,保證業務程式碼和 Schema 保持一致。

-- changeset other.goodrain:3
alter table person add column country varchar(2);
create table staff (
id int primary key,
name varchar(50) not null,
address1 varchar(50),
address2 varchar(50),
city varchar(30)
);
-- rollback drop table staff;
-- rollback alter table person drop column country;

在源應用處點選構建,Rainbond 會拉取最新的程式碼,更新業務應用的同時,為 Schema 進行升級。

構建過程中沒有任何變化,但是在啟動過程中,針對更新的 Initialize 和保持原狀的 anotherdb 庫例項,Rainbond 給出兩種不同的處理:

5. 基於應用模版的升級

源應用有了新的版本,已交付應用也應隨之有變更。首先,應用模版需要有一個更新的版本,重複釋出流程,定義更高的版本號即可。已交付應用可以根據 Rainbond 的提示,一鍵升級到更新後的版本。

6. 驗證

登入已交付應用的資料庫元件中,可以檢視對應的 Schema 變化。

7. 回滾

資料庫表結構的回滾操作是一個很嚴肅的問題。本著資料庫表結構只增不減的原則,已經生效的 Schema 不會隨著已交付應用的一鍵回滾而有任何變動。如果一定要進行回滾,則需要運維人員登入業務元件的 Web終端手動操作。

需要注意的是回滾的順序:資料庫表結構應該先於應用程式回滾。這是由於一旦應用程式回滾完成, changlog 檔案本身也回滾到了上個版本,無法再進行資料庫表結構的回滾。

執行以下命令,可以根據指定的配置檔案,對資料庫表結構進行回滾操作,回滾幅度以 1 個 changeset 為單位。

cd Schema/
liquibase rollbackCount 1 --defaults-file=mysql.properties

鑑於回滾後的業務元件一旦重啟或更新,就會比對 changelog 檔案後重新升級 Schema,所以在執行回滾操作後,務必新增環境變數 ALLOW_SCHEMA_UPDATE=false 來禁用 Schema 版本管理控制功能,直到新版本應用模版的升級。

常見問題

  1. 如何在 *.properties 配置檔案中合理的定義所有資料庫例項的連線地址和憑據?

使用環境變數來代替 *.properties 配置檔案中的資料路例項連線地址和憑據資訊,定義方式詳見文中的示例。Rainbond 原始碼構建過程中,會拾取執行環境中的所有環境變數,對目標配置檔案進行渲染,所以對於環境變數的命名並不重要,只需要保證定義的環境變數會在最終交付環境中生成即可。無論環境變數來自於自定義的環境配置還是 Rainbond 獨有的連線資訊機制。

  1. 執行回滾操作失敗?

回滾如何操作,定義在 changlog 檔案中。務必保證每一個 changeset 都有對應的回滾策略,方可保證每次回滾都得到正確的結果。

  1. 執行 Schema 升級的過程中報錯:!! Failed to check the database status. Check /app/Schema/xxx.properties.log

每一次執行 Schema 變更的過程中,都會先進行檢查,包括資料庫例項地址的連通性、changelog 檔案的可執行性。如果檢查不通過,則不會對資料庫作出任何操作,但是檢查的結果會記錄在日誌檔案中,可以登入 Web 終端,檢視提示中的日誌檔案內容。

  1. 老使用者如何獲取 Schema 版本控制功能?

這一功能和 Rainbond 的版本脫離,所以老使用者可以通過更新原始碼構建相關元件來獲取這一能力。執行以下一組命令即可:

# 以下命令在 Rainbond 叢集內任意節點執行;如果你使用 dind-allinone 版本,則應該在 rainbond-allinone 容器中執行
hubpassword=$(kubectl get rainbondcluster -o yaml -n rbd-system | grep password | awk '{print $2}')
docker login --username=admin --password=${hubpassword} goodrain.me
images=(builder runner)
for image in ${images[@]}
  do
    docker pull registry.cn-hangzhou.aliyuncs.com/goodrain/${image}:v5.5.0-release
    docker tag registry.cn-hangzhou.aliyuncs.com/goodrain/${image}:v5.5.0-release goodrain.me/${image}
    docker push goodrain.me/${image}
  done

Liquibase https://www.liquibase.com

java-maven-demo https://gitee.com/rainbond/java-maven-demo

關於Rainbond

Rainbond 是一個開源的雲原生應用管理平臺,使用簡單,不需要懂容器和Kubernetes,支援管理多個Kubernetes叢集,提供企業級應用的全生命週期管理,功能包括應用開發環境、應用市場、微服務架構、應用持續交付、應用運維、應用級多雲管理等。

? Github:https://github.com/goodrain/rainbond
? 官網:https://www.rainbond.com
? 微信群:關注 Rainbond 公眾號加入技術交流群
? 釘釘群:請搜尋釘釘群號 31096419

相關文章