掘金 后端 ( ) • 2024-04-14 09:53

茶艺师学微服务(实操篇3-数据迁移怎么做? 上)

前言

当完成了一个模块的微服务化,接着会考虑这么一个问题,“这个模块所依赖的数据库要独立出来吗?”
然而既然都在搞微服务了,那么把这模块所依赖的数据库独立出来,即所谓的“数据迁移”,就有必要了。
至少有以下理由:

  1. 数据隔离和解耦:通过拆分微服务,可以将不同的业务功能和数据存储分离开来。数据迁移可以确保每个微服务只关注自己所需的数据,实现数据的隔离和解耦。这样可以提高系统的灵活性和可维护性。
  2. 性能和扩展性:拆分微服务通常是为了提高系统的性能和可扩展性。通过数据迁移,可以将数据分散到不同的存储系统中,从而实现更好的负载均衡和水平扩展。每个微服务可以根据自身的需求和负载进行优化,提高整体系统的性能。
  3. 数据一致性和完整性:在微服务架构中,不同的微服务可能需要访问和修改相同的数据。通过数据迁移,可以确保数据在不同的微服务之间保持一致和完整。这可以避免数据冗余、数据不一致和数据丢失等问题。
  4. 遵循微服务原则:微服务架构的核心原则之一是单一职责原则。通过数据迁移,可以将数据按照业务功能进行划分,使每个微服务只关注自己所需的数据,实现高内聚和低耦合的设计。
  5. 支持团队自治:微服务架构鼓励团队自治和独立开发。通过数据迁移,可以将数据的所有权和管理责任分配给相应的团队。这样可以提高团队的独立性和效率,减少团队之间的依赖和冲突。

接下来我们来看看数据迁移怎么做?

迁移方案准备

停机迁移?不停机迁移?

第一时间,也许会想到,我把机器都停下来,然后把原数据库里的数据复制进新的数据库不就好了吗?
就像搬仓库,我肯定选不会有进货和出货的日子去搬啊。
如果说,是个小应用,停一停机没所谓,那就停机迁移。
又如果是游戏公司,直接发个公告,“我们 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 文件

【示例】实际效果

提示:写好的 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 的数量。

结语

这里大致讲了一下应用在微服务化中,不可避免的数据迁移的操作思路与步骤,并且深入探讨了一些数据导出与导入的细节。
在下一篇,我们开始看看数据校验如何做。