在Rainbond中實現數據庫結構自動化升級
- 2022 年 2 月 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.properties
和 changlog.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.properties
和 changlog.sql
文件的寫法,更多的特性請參考 liquibase 文檔 ,這些特性都可以被源碼構建所繼承。
Schema生命周期流程
1. 構建流程
執行正常的源碼構建流程時,會自動識別代碼根目錄下的 Schema
目錄,準備 Schema 版本管理所需要的基礎環境,包括 jre 和 Liquibase 工具包。
構建日誌會有以下提示:
2. 啟動流程
完成構建流程後,服務組件會自動進入啟動過程中, Rainbond 平台會根據代碼中定義好的配置文件,針對每一個數據庫實例,進行自動升級處理。
處理過程中,在服務組件的日誌中的頭部位置,會打印相關的記錄:
上圖中演示了針對同一個 mysql 數據庫中的多個庫實例進行表結構的升級操作。對於空的庫實例而言,這也相當於一次初始化的操作。
在示例中,Rainbond 分別嚮應用所連接的同個 mysql 數據庫中的兩個庫實例(分別名為 Initialize
anotherdb
)進行了表結構初始化操作,分別創建了表company
、person
以及 another_company
、another_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 版本管理控制功能,直到新版本應用模版的升級。
常見問題
- 如何在
*.properties
配置文件中合理的定義所有數據庫實例的連接地址和憑據?
使用環境變量來代替
*.properties
配置文件中的數據路實例連接地址和憑據信息,定義方式詳見文中的示例。Rainbond 源碼構建過程中,會拾取運行環境中的所有環境變量,對目標配置文件進行渲染,所以對於環境變量的命名並不重要,只需要保證定義的環境變量會在最終交付環境中生成即可。無論環境變量來自於自定義的環境配置還是 Rainbond 獨有的連接信息機制。
- 執行回滾操作失敗?
回滾如何操作,定義在 changlog 文件中。務必保證每一個 changeset 都有對應的回滾策略,方可保證每次回滾都得到正確的結果。
- 執行 Schema 升級的過程中報錯:
!! Failed to check the database status. Check /app/Schema/xxx.properties.log
每一次執行 Schema 變更的過程中,都會先進行檢查,包括數據庫實例地址的連通性、changelog 文件的可執行性。如果檢查不通過,則不會對數據庫作出任何操作,但是檢查的結果會記錄在日誌文件中,可以登錄 Web 終端,查看提示中的日誌文件內容。
- 老用戶如何獲取 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
References Link
Liquibase //www.liquibase.com
java-maven-demo //gitee.com/rainbond/java-maven-demo
關於Rainbond
Rainbond 是一個開源的雲原生應用管理平台,使用簡單,不需要懂容器和Kubernetes,支持管理多個Kubernetes集群,提供企業級應用的全生命周期管理,功能包括應用開發環境、應用市場、微服務架構、應用持續交付、應用運維、應用級多雲管理等。
🌟 Github://github.com/goodrain/rainbond
😊 官網://www.rainbond.com
😉 微信群:關注 Rainbond 公眾號加入技術交流群
🤔 釘釘群:請搜索釘釘群號 31096419