茶艺师学微服务(实操篇3-数据迁移怎么做? 上)
前言
当完成了一个模块的微服务化,接着会考虑这么一个问题,“这个模块所依赖的数据库要独立出来吗?”
然而既然都在搞微服务了,那么把这模块所依赖的数据库独立出来,即所谓的“数据迁移”,就有必要了。
至少有以下理由:
- 数据隔离和解耦:通过拆分微服务,可以将不同的业务功能和数据存储分离开来。数据迁移可以确保每个微服务只关注自己所需的数据,实现数据的隔离和解耦。这样可以提高系统的灵活性和可维护性。
- 性能和扩展性:拆分微服务通常是为了提高系统的性能和可扩展性。通过数据迁移,可以将数据分散到不同的存储系统中,从而实现更好的负载均衡和水平扩展。每个微服务可以根据自身的需求和负载进行优化,提高整体系统的性能。
- 数据一致性和完整性:在微服务架构中,不同的微服务可能需要访问和修改相同的数据。通过数据迁移,可以确保数据在不同的微服务之间保持一致和完整。这可以避免数据冗余、数据不一致和数据丢失等问题。
- 遵循微服务原则:微服务架构的核心原则之一是单一职责原则。通过数据迁移,可以将数据按照业务功能进行划分,使每个微服务只关注自己所需的数据,实现高内聚和低耦合的设计。
- 支持团队自治:微服务架构鼓励团队自治和独立开发。通过数据迁移,可以将数据的所有权和管理责任分配给相应的团队。这样可以提高团队的独立性和效率,减少团队之间的依赖和冲突。
接下来我们来看看数据迁移怎么做?
迁移方案准备
停机迁移?不停机迁移?
第一时间,也许会想到,我把机器都停下来,然后把原数据库里的数据复制进新的数据库不就好了吗?
就像搬仓库,我肯定选不会有进货和出货的日子去搬啊。
如果说,是个小应用,停一停机没所谓,那就停机迁移。
又如果是游戏公司,直接发个公告,“我们 XXXX - XXXX 停机维护”。
然而在现实中更多的是不能停机的应用。
对于它们,只能选择不停机维护。
数据迁移过程中的难点
与停机迁移比起来,不停机迁移最大的难点就是不断有新的数据产生,以及有旧的数据被更新了。
不管如何迁移,目标库总会落后于原库。
而当原数据库与目标数据库不一致时(数据库的类型不一样,如从 MySQL 转到 MongoDB;原表与目标表不一致,如目标表就只有原表的几列...),数据迁移又有了以下难度:
- 数据转换 打个比方,就是 MySQL 的 string 转成 MongoDB 的 string
- 完整性校验 就像是数据中间的关系,要怎么检验?
不停机迁移的阶段与步骤
由于不停机迁移有着以上难点,那么我们不妨抱着“能稳则稳,保证有补救手段”的思路来设计整个迁移方案。 先给整个过程划分阶段:
接着就是具体步骤:
模拟迁移
数据准备
在 mysql 里从零开始准备大量数据,有两个思路。
思路一
准备一个 sql 文件,把表的初始化内容以及要写入的数据(你要模拟多少就要写多少行)都写进里面,交给 docker 在启动 MySQL 时导入。
当然模拟数据可以交给程序生成,只要把表的初始化内容以及数据的公共部分写好,再由程序“拼接”出来就好。
- 初始化表的写法示例
// 假设要初始化一个数据库,名字叫 testBase
// 该库里有一个表,叫 testTables ,该表有四个字段 id, A, B, C
// 而且将里面的字段 id 与 A 联合成一个主键(联合主键,又名为唯一性约束),叫 test_id
create database if not exists testBase;
create table if not exists testBase.testTables(
id bigint auto_increment primary key,
A bigint null,
B bigint null,
C bigint null,
constraint test_id unique (id, A)
);
- 写入数据写法示例
接着就是生成模拟数据的 SQL 语句,其核心写法就是:
// 在 testTables 里写数据
INSERT INTO testTables (id, A, B, C)
VALUES (value1, value2, value3, value4),
(value5, value6, value7, value8),
(value9, value10, value11, value12),
...
接着就是如何打开一个文件并在里面写内容:
// 生成的文件名为 testData.sql
// os.OpenFile(文件名,操作参数,操作权限设置)
// os.O_RDWR 以读写模式打开文件
// os.O_APPEND 在文件后面追加数据
// os.O_TRUNC 如果文件存在,将其截断为空文件
// os.O_CREATE 如果文件不存在,新建文件
// 0666 文件的权限模式,表示文件的所有者、所属组和其他用户都具有读写权限
file, err := os.OpenFile("testData.sql",
os.O_RDWR|os.O_APPEND|os.O_TRUNC|os.O_CREATE, 0666)
require.NoError(t, err)
defer file.Close()
// 写入内容
file.Write()
file.WriteString()
在使用写程序时,就是想办法凑成上面句子就好,相信大家都有自己的办法
提示:写好的 sql 文件,除了可以交给 docker 导入外(注意导入位置),还可以直接用 IDE 连接 MySQL 进行操作。
思路二
启动一个 gorm.DB ,把要写入的数据使用程序生成好,使用 transcation 把这些数据写入 MySQL 里。 写法也很简单,关键的写入语句示例:
// data 就是你模拟好的数据
err := db.Transaction(func(tx *gorm.DB) error {
err := tx.Create(data).Error
require.NoError(t, err)
return err
})
数据导出工具 mysqldump
这是 MySQL 自带的数据导出工具,使用方法是在 MySQL 的容器里:
mysqldump -h <Ip> --port <端口> -u <用户名> -p <数据库名> <表> ... > <导出数据.sql>
// -h 机器的IP地址 用云服务器的就用该云服务器的公网IP
// --port MySQL 所使用的端口,默认是13316
// --result-file 能指定导出文件的位置,没有这句,生成文件就在容器里
导入数据
在创建新的数据库后,在 MySQL 里进入该数据库, source 一下导出文件就可以了。
source 导出文件.sql
异构数据初始化
上面的示例,展示了同构数据的迁移方法。
但是数据库不同,表不同,即称之为异构数据,在这场合,不能这样直接导入。
基本思路还是和上面示例一样:
- (注意新旧转换)写成 SQL ,导入
- (注意新旧转换)直接在代码中用 ORM 插入
可以使用 goroutine 来并发完成,如:
- 在同一表内,每个 goroutine 完成不同部分的数据
- 每个 goroutine 负责不同的表
这两个 goroutine 思路可以混合使用。
为了不影响线上使用,要注意 goroutine 的数量。
结语
这里大致讲了一下应用在微服务化中,不可避免的数据迁移的操作思路与步骤,并且深入探讨了一些数据导出与导入的细节。
在下一篇,我们开始看看数据校验如何做。