作者:楚乔
路特斯(Lotus)运维开发工程师
在国际化多机房部署的场景下,国内生产部署的是最新版本,国外部署的是落后 1 个甚至 N 个迭代的版本。当业务需要在国外升级到指定中间版本时,当前只能靠运维根据现有版本和指定中间版本之间的 N 次发布文档,挨个汇总 SQL 变更内容,非常费时费力,且容易出错。
因为想要自动化整个数据变更流程,所以选用社区比较有名的 go-migrate 和比较主流的数据库变更工具 flyway 做相应的尝试,并最终对 go-migrate 做二次开发来实现上述能力。
下面分享我们对这两个方案的验证过程及具体实践,以供社区同学参考。
方案描述
以数据库为单位,建立单独 GitLab 项目管理数据库所有的 SQL 变更,变更范围以版本为单位。
工具选型
工具 |
go-migrate [1] |
flyway [2] |
Github stars |
9.6K |
6.7K |
功能 |
类似 python flask框架 migrate 功能 |
undo 等重要功能需要买企业版 版本对比 [3] |
复杂度 |
适中 |
较高 |
源码 |
完全开源 |
有开源版和企业版 |
多个微服务共用一个数据库 |
不支持,可简单二开支持 |
不支持,二开复杂度较高 |
下面主要介绍
这两个工具的最小 demo 供大家参考。
flyway
前置准备
-
安装 flyway
从官网下载 flyway cli 工具 [4] 并安装,选择适合自己电脑操作系统和芯片架构的二进制文件。
-
准备数据库
使用 Datagrip 工具创建测试数据库用于 demo。
1 create database flyway_test default character set utf8mb4 collate utf8mb4_unicode_ci;
-
准备项目
创建 spring boot 测试项目 mysql-test ,pom.xml 文件中增加 flyway 依赖。
1<dependencies>
2 <dependency>
3 <groupId>org.springframework.boot</groupId>
4 <artifactId>spring-boot-starter-data-jpa</artifactId>
5 </dependency>
6 <dependency>
7 <groupId>org.flywaydb</groupId>
8 <artifactId>flyway-core</artifactId>
9 <version>6.0.8</version>
10 </dependency>
11 <dependency>
12 <groupId>mysql</groupId>
13 <artifactId>mysql-connector-java</artifactId>
14 <scope>runtime</scope>
15 </dependency>
16</dependencies>
1718<build>
19 <plugins>
20 <!--flyway-->
21 <plugin>
22 <groupId>org.flywaydb</groupId>
23 <artifactId>flyway-maven-plugin</artifactId>
24 <version>6.0.8</version>
25 <!--配置属性-->
26 <configuration>
27 <user>root</user>
28 <password>root</password>
29 <url>jdbc:mysql://127.0.0.1:3306/flyway_test</url>
30 </configuration>
31 </plugin>
32 </plugins>
33</build>
application.yml 文件中增加数据源配置
1spring:
2 datasource:
3 driverClassName: com.mysql.cj.jdbc.Driver
4 url: jdbc:mysql://localhost:3306/flyway_test?serverTimezone=UTC&allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8&useSSL=false
5 username: root
6 password: root
7 # flyway
8 flyway:
9 baseline-on-migrate: true
10 locations: classpath:/db/migration
11 check-location: true
12 enabled: true
13 #jpa
14 jpa:
15 database: MYSQL
16 show-sql: true
17 hibernate:
18 ddl-auto: none
增加 SQL 变更文件,注意目录为 src/main/resources/db/migration
sql 文件命名规则
-
首位大写字母V ,固定格式;
-
后面跟上版本号,版本号为数字、点、或者下划线组成;
-
版本号后跟上__(注意:这里是两位下划线),固定格式;
-
__后跟上文件描述,无限制,最好做到见文知意,如:V2_creatTableStudent.sql
本地验证
本地执行以下命令
1 # 本机 mac 执行
2 mvn clean package -U -Dmaven.test.skip=true -e
3 ./lotus.sh
45 2022-08-30 10:15:25.347 INFO TID: N/A 98445 --- [ main][] o.f.core.internal.command.DbMigrate : Current version of schema `flyway_test`: << Empty Schema >>
6 2022-08-30 10:15:25.358 INFO TID: N/A 98445 --- [ main][] o.f.core.internal.command.DbMigrate : Migrating schema `flyway_test` to version 1 - init
7 2022-08-30 10:15:25.567 INFO TID: N/A 98445 --- [ main][] o.f.core.internal.command.DbMigrate : Migrating schema `flyway_test` to version 2 - create route
8 2022-08-30 10:15:25.703 INFO TID: N/A 98445 --- [ main][] o.f.core.internal.command.DbMigrate : Successfully applied 2 migrations to schema `flyway_test` (execution time 00:00.373s)
9 2022-08-30 10:15:25.799 INFO TID: N/A 98445 --- [ main][] o.hibernate.jpa.internal.util.LogHelper : HHH000204: Processing PersistenceUnitInfo [name: default]
10 2022-08-30 10:15:25.851 INFO TID: N/A 98445 --- [ main][] org.hibernate.Version : HHH000412: Hibernate ORM core version 5.6.7.Final
11 2022-08-30 10:15:25.980 INFO TID: N/A 98445 --- [ main][] o.hibernate.annotations.common.Version : HCANN000001: Hibernate Commons Annotations {5.1.2.Final}
12 2022-08-30 10:15:26.173 INFO TID: N/A 98445 --- [ main][] org.hibernate.dialect.Dialect : HHH000400: Using dialect: org.hibernate.dialect.MySQL57Dialect
13 2022-08-30 10:15:26.311 INFO TID: N/A 98445 --- [ main][] o.h.e.t.j.p.i.JtaPlatformInitiator : HHH000490: Using JtaPlatform implementation: [org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform]
14 2022-08-30 10:15:26.319 INFO TID: N/A 98445 --- [ main][] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'default'
执行后数据库 flyway_schema_history 表新增 SQL 变更记录
版本降级
1 mvn flyway:undo
2 [ERROR] Failed to execute goal org.flywaydb:flyway-maven-plugin:6.0.8:undo (default-cli) on project redis-test: org.flywaydb.core.internal.license.FlywayProUpgradeRequiredException: Flyway Pro Edition or Flyway Enterprise Edition upgrade required: undo is not supported by Flyway Community Edition.
* 很遗憾,flyway 社区版不支持 undo 功能。
在 Zadig 上实践 flyway
参考「Zadig + Flyway 工作流统一数据和代码变更,研发更丝滑」[5]
go-migrate
前置准备
安装 migrate
从官方下载 migrate 工具 [6]并安装 ,选择适合自己电脑操作系统和芯片架构的二进制文件。
准备数据库
使用 Datagrip 工具创建测试数据库用于 demo
1 create database redis_test default character set utf8mb4 collate utf8mb4_unicode_ci;
准备项目
准备 spring boot 测试项目 migration-test,编写 migrate sql
1 # 1_initialize_schema.up.sql
2 # 如果是业务表,可以考虑create table if not exists
3 create table alert
4 ...
5 charset = utf8mb4;
67 create index IX_alert_select_1
8 ...
1 # 1_initialize_schema.down.sql
2 # 如果是业务表,里面有业务数据,建议版本回退的时候不做drop操作
3 drop table if exists alert;
将 SQL 变更记录(各版本增量更新的 up、down 语句)保存在项目指定目录下,发布时,执行 migrate up 命令。示例如下:
文件命名规则:
1 {version}_{title}.up.{extension}
2 {version}_{title}.down.{extension}
参数说明:
-
{version} —— 64 位无符号整数,该值需要随着版本趋势增长并保持唯一,比如1,2,3,4...
-
{title} —— 本次 SQL 变更的简单英文标识
-
{extension} —— SQL 变更文件后缀,比如.sql
本地验证
migrate 组件目前对支持 多个微服务共用一个数据库,且都需要改变表结构 的场景还不完善,可以考虑在原生功能的基础上进行二开,下述内容为二开后的实践。
1. 执行 migrate up 命令,自动执行所有 up.sql,更新到最新版本
1 shell➜ migrate-test git:(master) ✗ migrate -appname "migrate-test" -source "file://migrations" -database "mysql://root:root@tcp(localhost:3306)/migrate_test" up
2 1/u initialize_schema (180.104ms)
3 2/u create_route (276.570667ms)
4 3/u create_notify (380.573625ms)
2. 执行 migrate down 1 命令,回退版本3
1 shell➜ migrate-test git:(master) ✗ migrate -appname "migrate-test" -source "file://migrations" -database "mysql://root:root@tcp(localhost:3306)/migrate_test" down 1
2 3/d create_notify (39.362042ms)
3. 执行 migrate down 命令,回退到初始状态
1 shell➜ migrate-test git:(master) ✗ migrate -appname "migrate-test" -source "file://migrations" -database "mysql://root:root@tcp(localhost:3306)/migrate_test" down
2 Are you sure you want to apply all down migrations? [y/N]
3 y
4 Applying all down migrations
5 2/d create_route (73.9035ms)
6 1/d initialize_schema (116.148333ms)
4. 执行 migrate goto 2 命令,更新到指定中间版本2
1 shell➜ migrate-test git:(master) ✗ migrate -appname "migrate-test" -source "file://migrations" -database "mysql://root:root@tcp(localhost:3306)/migrate_test" goto 2
2 1/u initialize_schema (178.743125ms)
3 2/u create_route (280.408375ms)
5. 执行 migrate down 1 命令,回退版本2
1 shell➜ migrate-test git:(master) ✗ migrate -appname "migrate-test" -source "file://migrations" -database "mysql://root:root@tcp(localhost:3306)/migrate_test" down 1
2 2/d create_route (52.728416ms)
6. 执行 migrate down 1 命令,回退版本1
1 shell➜ migrate-test git:(master) ✗ migrate -appname "migrate-test" -source "file://migrations" -database "mysql://root:root@tcp(localhost:3306)/migrate_test" down 1
2 1/d initialize_schema (56.16175ms)
在 Zadig 上实践 go-migrate
1. 将服务添加到 Zadig 中并创建构建,在构建脚本中添加 migrate 命令及相关参数,将代码中的 .sql 文件应用到数据库
2. 运行工作流,验证代码中的 SQL 和数据库存在差异时的效果(初次运行将变更应用到数据库,显示变更对应的文件及执行耗时)
再次运行工作流,验证代码中的 SQL 和数据库无差异时的效果(第二次运行,无 SQL 变更,显示 no change )
当数据库中的 SQL 版本低于代码中时, migrate up 命令会将数据库中的 SQL 更新到和代码一致;当数据库中的 SQL 与代码中一致时, migrate up 命令不会执行任何操作。
参考链接
[1] https://github.com/golang-migrate/migrate
[2] https://github.com/flyway/flyway
[3] https://flywaydb.org/download
[4] https://flywaydb.org/download/community
[5] https://mp.weixin.qq.com/s/KFyKkYTQp58BpNn9HGA7AQ
[6] https://github.com/golang-migrate/migrate/releases
Zadig,让工程师更专注创造!