baby sword‘s blog baby sword‘s blog
首页
  • java基础
  • java进阶
大数据
  • mysql

    • mysql索引
    • mysql日志
  • redis

    • 单机下的redis
    • 集群下的redis
  • Spring
  • springboot
  • RPC
  • netty
  • mybatis
  • maven
  • 消息队列
  • kafka
  • zookeeper
  • rocketmq
  • 七大设计原则
  • 创建型模式
  • 结构型模式
  • 行为型模式
  • SpringCloud

    • eureka
  • SpringCloud Alibaba

    • nacos
  • 计算机网络
  • 操作系统
  • 算法
  • 个人项目
  • 个人面试面经
  • 八股记忆
  • 工作积累
  • 逻辑题
  • 面试

    • 百度后端实习二面
GitHub (opens new window)

zhengjian

不敢承担失去的风险,是不可能抓住梦想的
首页
  • java基础
  • java进阶
大数据
  • mysql

    • mysql索引
    • mysql日志
  • redis

    • 单机下的redis
    • 集群下的redis
  • Spring
  • springboot
  • RPC
  • netty
  • mybatis
  • maven
  • 消息队列
  • kafka
  • zookeeper
  • rocketmq
  • 七大设计原则
  • 创建型模式
  • 结构型模式
  • 行为型模式
  • SpringCloud

    • eureka
  • SpringCloud Alibaba

    • nacos
  • 计算机网络
  • 操作系统
  • 算法
  • 个人项目
  • 个人面试面经
  • 八股记忆
  • 工作积累
  • 逻辑题
  • 面试

    • 百度后端实习二面
GitHub (opens new window)
  • 华仔聊技术

  • 业务设计

    • 微信红包实现方案
    • 分布式id
    • 数据库迁移设计
      • 背景
      • 确定要迁移的数据库的数据涉及到的服务有哪些?
      • 确定迁移原因
      • 确定迁移预期目标
      • 确定方式
      • 通过上面的方式的思考,我们就可以形成一个大致的实现方案
      • 细节思考
        • 先考虑双写的具体实现
        • 数据量思考
        • 开关具体实现思考
        • 平滑卸掉mongodb思考
        • 关注读写界限思考
    • 单点登录相关知识
    • 风控平台累计量设置
    • 红包雨设计
  • 场景设计

  • 运维

  • 安全

  • 面试

  • mac相关工具推荐

  • 开发工具

  • 人工智能

  • 推荐

  • 阅读

  • 工具

  • 计划

  • 产品

  • 云原生

  • go

  • QVM

  • 软件设计师

  • 极客时间

  • 单元测试

  • 其他
  • 业务设计
xugaoyi
2023-02-07
目录

数据库迁移设计

# 背景

在公司大部分数据库都需要从mongodb迁移到mysql的前提下。现在对某些数据库中的表进行数据迁移。

# 确定要迁移的数据库的数据涉及到的服务有哪些?

首先即要确定我们修改代码的范围,如果所有关于数据库的数据的流量全部来自于同一个服务,那就没有问题,如果数据库的数据访问层可能来自于多个服务,那么问题可能会更复杂一些。以下是关于mongodb数据访问全部来自于一个account服务的解决方案。

# 确定迁移原因

  • mongodb在数据量大的情况下,效率较慢。
  • mongodb维护人员较少,当mongodb发生故障时,很难维护

# 确定迁移预期目标

  • mongodb数据全量迁移至mysql数据库
  • 迁移过程中不停机,对用户无感
  • 迁移过程中出现问题可以进行降级或回滚。

# 确定方式

①首先需要考虑mongodb和mysql在数据结构上面的差异,mongodb中的数据结构和mysql差距很多,mongodb中可能会有JSON的格式,需要考虑迁移到mysql中对应的数据结构该怎么设计、索引应该怎么创建。

②考虑中途不宕机,怎实现呢?

思考:该目标是不是可以抽象为两个服务节点之间的数据传输,之前我们学习学习当中的节点间的大量数据传输有哪些呢?比如说redis、mysql,他们在集群下总是会有主副本的区分,主节点要将数据传输到副节点,是不是都实现过一套传输的方案,其方案基本上都是将数据的传输分为存量数据和增量数据的传递。首先我们要确定存量数据如何传输,在存量数据的传递过程中,我们也要进行增量数据的传输,在mysql和redis中,基本的实现方案都是在生成存量传输数据的过程中,接受客户端的请求,然后将增量数据写在一个缓存中,当存量数据传输完成,再将缓存中的数据进行传输。

上面的思考,其实就是在不宕机的情况下,完成数据的传输,其重点就是将数据分为存量数据和增量数据,这时关键,对于存量数据和增量数据如何进行传输,我们可以根据业务具体来实现。

存量数据:在数据开始迁移的那一刻,原数据库所拥有的数据。

增量数据:在数据开始迁移的那一刻,原数据库因为接受到请求新增的数据。

③如何保证出错了过后可以降级?

数据库迁移过程中,想要保证出错可以降级,其本质就是保证mongodb的数据业务上不丢失,与mongodb相关的代码不修改。所以这并不是说在代码版本上的降级,我们应该尽全力保证在数据迁移期间,mongodb的服务(增量数据)依然在进行当中。

如何实现呢?我们可以引入数据双写和开关的概念。

数据双写:在迁移过程中,mysql和mongodb都在进行数据的写,这样就算需要降级,mongodb的数据依然存在

开关:如果客户端发送过来读请求,为了保证的数据的一致性,我们必须保证业务只从一个数据库当中读。那么我们数据迁移过程中,读哪一个呢?

只读mysql:有些数据还在mongodb呢,可能因为读不到而出现错误,所以不能使用mysql

只读mongodb:mongo写同时进行着,那么读肯定是没有问题的。

所以在迁移过程中,我们可以只读mongodb,但是我们不能总读mongodb吧,因为我们的目标就是将mongodb下线,后面总是要将读流量切换到mysql的,而且还要保证不宕机。

这样就引入了开关的概念。

为了我们可以在迁移后平滑地将流量从mongodb切换到mysql,我们在项目中引入开关的概念。并配置在apollo中,通过修改apollo中的开关,动态修改在代码中的实现。

mongodb写开关:为true时,表示开启对mongdb的写

mysql写开关:为true时,表示开启对mysql的写

读开关:为true时,表示开启对mongodb读,否则对mysql读(mongodb读和mysql读是互斥的,所以这里只需要设置这一个互斥开关就可以了)

# 通过上面的方式的思考,我们就可以形成一个大致的实现方案

对于存量的数据,我们直接迁移(使用公司内部的工具:可以将mongodb对应的字段与目标表对应的字段设置相关的映射关系,然后进行数据的迁移)

对于增量数据,采用双写模式写到mysql。

①根据mongodb建立相应的mysql表结构,根据mongodb中repository层编写mysql repository层相关的代码。

②双写上线(读mongo,写mongo、mysql)

③开启dts存量同步(中途开始显现出bug,需要修改)

④存量同步完成后,逐步将读流量切换到Mysql,验证mysql业务逻辑是否有错(bug高峰期)

⑤读流量全量迁移到MySQL并验证完毕后,停止写MongoDB,迁移完成

上线阶段 数据库读写状态 流程
MongoDB MySQL
一 读/写 写 双写上线,DTS同步存量数据
二 写 读/写 读流量迁移至MySQL
三 读/写 停写MongoDB

# 细节思考

# 先考虑双写的具体实现

读:读我们的第一阶段都是先读mongodb,所以这一点都是毋庸置疑的。

增:增加,表示两个数据库都不存在该数据,所以新增是没有问题的,当做增量数据进行处理。

删:当要删除的数据未同步到mysql中时,mongodb中的数据可以成功删除,mysql因为数据不存在,执行了后语义正确,且后续不会出现该数据;当要删除的数据已经同步到mysql中时,mongodb中的数据可以删除,mysql中的数据也可以删除成功,语义正确

**改:**当要更改的数据(想不清楚了?)

工具思考:应该保证什么功能

# 数据量思考

在进行数据的迁移时,我们必须考虑数据量的大小,是否需要进行分表。因为每个数据库系统,一张表存储的数据量可能不一样,所以我们需要考虑是否要进行分表,如果要进行分表,由要怎么来使用方案,都是要考虑的方案。

# 开关具体实现思考

我们的重点就是在不宕机的请况下,切换开关。首先我们现在已知的是有3种开关。

读切换开关、mongodb写开关、mysql写开关。

既然要实现机器不宕机,我们就需要对开关实现动态开关。所以我们要借助注册中心,实现对配置文件的动态修改。因为公司对于配置中心使用的也是apollo,所以我们配置中心使用apollo其实也是听不懂的,这样我们在apollo上修改了配置就可以在java代码中及时得到更新。那么我们在springboot中怎么读取这个配置呢?

    @Value("${mongo2mysqlMigration.reader.mongo}")
    private Boolean mongoReaderEnabled;

    @Value("${mongo2mysqlMigration.writer.mysql}")
    private Boolean mysqlWriterEnabled;

    @Value("${mongo2mysqlMigration.writer.mongo}")
    private Boolean mongoWriterEnabled;
1
2
3
4
5
6
7
8

如图所示,我们使用@Value注解读取yaml文件,当apollo重新更新了配置过后,这里就可以将对应的数据重新注入到成员变量中。

至于@Value注解的原理,可以看看这篇文章https://blog.csdn.net/qq_29717181/article/details/99872792

在代码中,我们就根据开关用if else来对流量进行切换。

# 平滑卸掉mongodb思考

未迁移前我们的代码处于以下这个层次

service层依赖于repository层

service层主要是一些复杂的业务,而repository层主要是直接与数据库交互的层。我们的目的是并不想修改service层的东西,做到对servcie无感。

虽然这个时候就会疑惑了

service中的依赖注解(@Resource(name="UserRepositoryImpl")) 在切换的时候我们不修改这个么?

其实确实不用,我们把mysql的repository 名字改成UserRepositoryImpl,mongodb 的Repository改成其他的就可以了。

这样就可以做到对service层无感知。

这个时候问题又来了,我们有了mysql的repository和mongodb的repository,这个时候怎么做流量的转发呢?这个时候我们要引入另外一个代理层proxy,专门来做我们的代理转发的业务,根据开关配置的真与否来进行流量的分发操作,这个时候我们的框架其实就基本出来了。

img

这时,我们只需要将相关的数据层依赖换为proxy,而proxy又依赖mysql和mongodb就可以实现流量的分发。

以下使我们的各个组件的名字,可以做到对service无感。

img

在proxy中根据开关的真假,就可以完成流量的切换了。

# 关注读写界限思考

mongodb中一些userRepository可能只是纯粹的读或者写,这种对我们是非常友好的,开关切换时就直接切换就好。但是在方法中可能有些不纯粹的操作,比如一个方法中可能既有查询方法,又有修改方法,就感觉向一个service业务方法似得,这种就需要我们手动在proxy中进行拆解,将其中的写方法和读方法正确的进行拆分,这样才不会出错(对于一个不纯粹的方法,如果我们将其定义写操作,那么当我们开启mysql的读时,会执行该方法,那么其中的读mysql操作也会同时执行,这是不符合我们含义的。所以这里我们要划分好读写的界限,要让方法更加纯粹)

编辑 (opens new window)
#{ "author": null, "name": "xugaoyi", "link": "https://github.com/xugaoyi" }
上次更新: 2024/02/22, 14:03:19
分布式id
单点登录相关知识

← 分布式id 单点登录相关知识→

最近更新
01
spark基础
02-22
02
mysql读写分离和分库分表
02-22
03
数据库迁移
02-22
更多文章>
Theme by Vdoing | Copyright © 2019-2024 Evan Xu | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式