一文搞懂后端面试之不停机数据迁移【中间件 | 数据库 | MySQL | 数据一致性】

数据迁移方面的工作:

  • 重构老系统:使用新的表结构来存储数据
  • 单库拆分分库分表、分库分表扩容
  • 大表修改表结构定义

数据备份工具

MySQL上常用的两款数据备份工具:mysqldump和XtraBackup

mysqldump:一个用于备份和恢复数据库的命令行工具,允许用户导出MySQL数据库的结构、数据及与表之间的关系,以便在数据库发生问题时进行恢复。是一个逻辑备份工具,导出的内容是一条条SQL
XtraBackup:使用了InnoDB存储引擎的数据备份技术,支持增量备份和恢复,并且支持多主机备份和恢复。是一个物理备份工具,相当于直接复制InnoDB的底层存储文件。
在这里插入图片描述
如果你使用的不是 MySQL,可以自己收集一下你使用的数据库的工具。要注意分析这些工具的优缺点,尤其是导入导出速度以及可行的优化手段。

innodb_autoinc_lock_mode

innodb_autoinc_lock_mode是InnoDB引擎里面控制自增主键生成策略的参数,有三个取值:

  • 0:使用表自增键锁,但是锁在INSERT语句结束之后就释放了
  • 1:使用表自增锁,如果是普通的INSERT INTO VALUE 或者 INSERT INTO VALUES 语句,申请了主键就释放锁,而不是整个INSERT语句执行完毕才释放。如果是INSERT SELECT 等语句,因为无法确定究竟要插入多少行,所以都是整个INSERT语句执行完毕才释放。
  • 2:使用表自增锁,所有的语句都是申请了主键就立刻释放

在执行 INSERT INTO VALUES 的时候,不管插入多少行,都只申请一次主键,一次申请够,这些主键必然是连续的。所以你可以从返回的最后一个 ID 推测出全部 ID

面试准备

  • innodb_autoinc_lock_mode的值,这回影响主键生成策略,在数据迁移的时候需要考虑处理主键问题
  • 公司的binlog模式,在后面增量校验和修复数据里面使用的是行模式的binlog
  • 公司是否有统一的数据库规范,比如必须要更新时间戳、不能硬删除,只能软删除
  • 使用的ORM框架怎么实现双写
  • 公司是否做过数据迁移?如果做过,具体的方案是什么

重构系统:

这个系统是我们公司的一个核心系统,但是又有非常悠久的历史。在我刚接手的时候,它已经处于无法维护的边缘了。但是不管是重构这个系统,还是重新写一个类似的系统,已有的数据都是不能丢的。所以我的核心任务就是重新设计表结构,并且完成数据迁移。为此我设计了一个高效、稳定的数据迁移方案。

实际落地了单库拆分 分库分表

进公司的时候,刚好遇上单库拆分分库分表。我主要负责的事情就是设计一个数据迁移方案,把数据从单库迁移到分库分表上。

解决方案

数据迁移方案的基本步骤:

  1. 创建目标表
  2. 用源表数据初始化目标表
  3. 执行一次校验,并修复数据,此时用源表数据修复目标表数据
  4. 业务代码开启双写,读源表,并且先写源表,数据以源表为主
  5. 开启增量校验和数据修复,保持一段时间
  6. 切换双写顺序,此时读目标表,并且先写目标表,数据以目标表为准
  7. 继续保持增量校验和数据修复
  8. 切换为目标表单写,读写都只操作目标表

不考虑数据校验的话,整个数据迁移过程是这样的
在这里插入图片描述
图右边的两步都要考虑校验和修复数据的问题

初始化目标表数据

在创建了一个目标表之后,第一步是先尝试初始化目标数据,问题是你怎么拿到源表数据

基本思路有两个:

  1. 使用源表的历史备份
  2. 源表导出数据

以mysqldump为例 关键词是加快导入和导出速度

我选择了从源表导出数据,使用的是 mysqldump 工具。mysqldump 是一个开源的逻辑备份工具,优点是使用简单,能够直接导出整个数据库。缺点则是导出和导入的速度都比较慢,尤其是在数据量非常大的情况下。
所以我针对 mysqldump 做了一些优化,来提高导出和导入的性能。
加快导出速度能做的事情并不多,主要就是开启 extended-insert 选项,将多行合并为一个 INSERT 语句。
加快导入速度就可以做比较多的事情。

  • 关闭唯一性检查和外键检查,源表已经保证了这两项,所以目标表并不需要检查。
  • 关闭 binlog,毕竟导入数据用不着 binlog。
  • 调整 redo log 的刷盘时机,把 innodb_flush_log_at_trx_commit 设置为 0。

注意在第 2、3 点里面,可以把话题引向的 binlog 和 redolog 内容。反过来,你也可以利用 binlog 和 redolog 把话题引导到这一节课的内容,灵活应对就可以。

第一次校验与修复

在初始化数据之后,可以先尝试立刻校验和修复一下数据,因为如果你前面用的是备份数据,那么备份数据已经落后生产数据了
比如你用的是昨天的备份,那么今天的修改目标表就没有。还有如果你是导出的数据,那么导出数据到你导入数据的这段时间,数据发生了变化,目标表依旧是没有的。
如果你们公司有明确的数据库规范的话,比如说所有的表都需要有update_time这个字段,那么校验和修复的时候就可以采用增量的方案。因为只有update_time晚于你导出数据的时间点,才说明这一行的数据已经发生了变更。在修复的时候就直接用源表的数据覆盖掉目标表的数据。

业务开启双写,以源表为准

首先要解释清楚是怎么做到双写的。
支持双写大体上有两个方向:侵入式和非侵入式两种。
侵入式方案就是直接修改业务代码,要求业务代码在写了源表之后再写目标表。但是侵入式方案是不太可行的,或者说代价很高。因为这意味着所有的业务都要检查一遍,然后修改。既然修改了,自然还要测试。所以,一句话总结就是工作量大还容易出错

非侵入式一般和你使用的数据库中间件有关,比如ORM框架。这一类框架一般会提供两种方式来帮你解决类似的问题。

  • AOP(Aspect Oriented Program 面向切面编程)方案:不同框架有不同叫法,比如说可能叫做interceptor,middleware,hook,handler,filter。这个方案的关键是捕捉到发起的增删改调用,篡改为双写模式。
    在这里插入图片描述
  • 数据库抽象操作:可能叫做Session, Connection, Connection Pool, Executor等,就是将源表的操作修改为双写模式。

不管用哪种方案,都是确保一个东西,就是双写可以在运行期间随时切换状态,单写源表、先写源表、先写目标表、单写目标表都可以。
大多数时候都是利用一个标记位,然后你可以通过配置中心或者接口直接修改它的值。

在这里插入图片描述
以GORM为例

开启双写在 GORM 上面还是比较容易做到的。最开始我觉得可以考虑使用 GORM 的 Hook 机制,用 DELETE 和 SAVE 两个 Hook 就可以了。但是这要求我必须给每一个模型或表都定义类似的 Hook,还是比较麻烦的。后来我仔细翻了 GORM 的文档,确认可以考虑使用 GORM 的 ConnPool 接口。所以我用装饰器模式封装了两个数据源,每次执行语句的时候,都根据标记位来执行双写逻辑。

func (m *DouleWritePool) QueryContext(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error) {if m.mode == '源表优先' {err = m.source.QueryContext(ctx, query, args...)if err == nil {m.target.QueryContext(ctx, query, args...)}} else if {//...}
}

在双写的时候,可以往两个方向进一步刷亮点:数据一致性问题和主键问题

数据一致性问题

正常面试官都可能会问到,如果在双写过程中,写入源表成功了,但是写入目标表失败了,该怎么办?
最基础的回答就是不管

写入源表成功,但是写入目标表失败,这个是可以不管的。因为我后面有数据校验和修复机制,还有增量校验和修复机制,都可以发现这个问题。

然后可以提出一个曾经思考过但是最终没有实施的方案,这能够证明你在数据一致性上有过很深的思考。关键词是难以确定被影响的行

在设计方案的时候,我考虑过在写入目标表失败的时候,发一个消息到消息队列,然后尝试修复数据。但是这个其实很难做到,因为我不知道该修复哪些数据。比如一个UPDATE语句在目标表上执行失败,我没办法根据UPDATE语句推断出源表上哪些行被影响到了。

在这里插入图片描述

主键问题

如果在源表中使用的是自增主键,那么在双写的时候写入目标表要不要写入主键?答案是要的。
需要在写入源表的时候拿到自增主键,然后在写入目标表的时候设置好主键。因为其实你并不能确保目标表自增的主键和源表自增的主键是同一个值。比如在并发场景下两次插入。
在这里插入图片描述
因此你可以介绍你是如何处理这个问题的

在双写的时候比较难以处理的问题是自增主键问题。为了保持源表和目标表的数据完全一致,需要在源表插入的时候拿到自增主键的值,然后用这个值作为目标表插入的主键

在这里插入图片描述
此外还可以进一步展示一个更加高级的亮点,也就是我在前置知识里说到的innodb_autoinc_lock_mode 取值会影响到自增主键的连续性,抓住关键词 自增主键的连续性

在处理批量插入的时候更要小心一些。正常来说,批量插入如果使用的是VALUES语法,那么生成的主键是连续的,可以从返回的最后一个主键推测出前面其他行的主键。即使innodb_autoinc_lock_mode 的取值是2也能保证这一点。但是如果是多个INSERT INTO VALUE语句,或者INSERT SELECT语句,这些语句生成的主键就可能不连续。在双写之前,都要先改造这一类的业务

增量校验和数据修复

增量校验基本就是一边保持双写,一边校验最新修改的数据,如果不一致,就要进行修复。这里有两个方案。第一个方案是利用更新时间戳,比如说update_time这种列;第二个方案是利用binlog。相比之下binlog更加高级,在面试的时候应该优先考虑这个方案。

利用更新时间戳

利用更新时间戳的思路很简单,就是定时查询每一张表,然后根据更新时间戳来判断某一行数据有没有发生变化。

for {// 执行查询// SELECT * FROM xx WHERE update_time >= last_timerows := findUpdatedRows()for row in rows {// 找到目标行,要用主键来找,用唯一索引也可以,看你支持到什么程度tgtRow = findTgt(row.id)if row != tgtRow {// 修复数据fix()}}// 用这一批数据里面最大的更新时间戳作为下一次的起始时间戳last_time = maxUpdateTime(row)// 睡眠一下sleep(1s)
}

所以可以介绍基本的策略,关键词是更新时间戳

我们采用的方案是利用更新时间戳找出最近更新过的记录,然后再去目标表里面找到对应的数据,如果两者不相等,就用源表的数据去修复目标表的数据。这个方案有两个条件:所有的表都是更新时间戳的,并且删除是软删除的

在这里插入图片描述
这里提到的第二个条件就是准备展示的第一个亮点,你可以等面试官追问为什么必须是软删除,也可以自己接着回答。

如果不是软删除,那么源表删掉数据之后,如果目标表没删除,在我们的匹配逻辑里是找不到的。在这种场景下,还有一个补救措施,就是反向全量校验。也就是说从目标表里再次查询全量数据,再去源表里找对应的数据,如果源表里没找到,就说明源表已经删了数据,那么目标表就是可以删除数据了

在这里插入图片描述
接下来可以进一步展示第二个亮点,主从同步延迟引发的问题。假设你在校验和修复的时候,读的是从库,那么会遇到两种异常情况,一种是目标表主从延迟,另一种是源表主从延迟。
在这里插入图片描述
那么怎么解决呢?简单粗暴的方法就是全部读主库,校验和修复都以主库数据为准。缺点是对主库的压力比较大

更加高级的方案:双重校验

校验和修复的时候要小心主从同步的问题,如果校验和修复都是用从库的话,那么就会出现校验出错,或者修复出错的情况。按照道理来说,强制走主库就可以解决问题,但是这样对主库的压力比较大
所以我采用的是双重校验方案,第一次校验的时候读从库,如果发现数据不一致,再读主库,用主库的数据再校验一次。修复的时候就只能以主库数据为准。这种方案的基本前提是,主从延迟和数据不一致的情况是小概率的,所以最终会走到主库也是小概率的。

在这个回答里面你没有提到任何异常情况的具体场景,如果面试官问到了,就回答上面图里面的两种情况。这个亮点能够凸显你在主从延迟方面的积累,也会把话题引到主从模式和主从同步上,所以要做好准备。

利用binlog

binlog是一个更加高级的方案,它还有一些变种,所以理解和记忆的难度也更高一些。这里的binlog是基于行的binlog模式。
最简单的形态就是把binlog当作一个触发器,回答的关键词就是binlog触发

在校验和修复数据的时候,采用的是监听binlog的方案。binlog只用于触发校验和修复这个动作,当我收到binlog之后,会用binlog中的主键,去查询源表和目标表,再比较两者的数据。如果不一致,就用源表的数据去修复目标表。

在这里插入图片描述
如果更进一步,会觉得binlog里面本来就有数据,那么干嘛不直接用binlog里的数据?所以就有了第二个形态:binlog的数据被看作源表数据。

拿到binlog之后,我用主键去目标表查询数据,然后把binlog里面的内容和目标表的数据进行比较。如果数据不一致,再用binlog里的主键去源表里查询到数据,直接覆盖目标表的数据。

在这里插入图片描述
这里有一点不同,就是发现不一样之后,需要查询源表,再用查询到的数据去覆盖目标表的数据。因为你要防止binlog是很古老的数据,而目标表是更加新的数据这种情况

紧接着,会发现目标表也有binlog,所以干嘛不直接比较源表的binlog和目标表的binlog?如果binlog不一样,那不就说明是目标表那边出了问题吗?
这种方案理论上是可行的,但是它有两个非常棘手的问题。

  1. 一次双写,可能立刻就收到了源表的 binlog,但是你过了好久才收到目标表的 binlog。反过来,先收到目标表的 binlog,隔了很久才收到源表的 binlog 也一样。所以你需要缓存住一端的 binlog,再等待另外一端的 binlog
  2. 顺序问题,如果有两次双写操作的是同一行,那么你可能先收到源表第一次的 binlog,再收到目标表第二次双写的 binlog,你怎么解决这个问题呢?你只能考虑利用消息队列之类的东西给 binlog 排个序,确保 binlog 收到的顺序和产生的顺序一致

虽然能够进一步减轻数据库查询的压力,但是实在过于复杂,得不偿失。

切换双写顺序

这一步本身就是一个亮点。因为很多人都是在双写的时候直接切换到目标表单写。但是这样直接切换的风险太大了,万一出了问题都没法回滚。
在这里插入图片描述
所以中间要引入一个双写的时候先写目标表,且业务读目标表的步骤,这样万一切换到写目标表出了问题还可以回滚。

介绍这一步的时候补充说明一下

引入这一步,是为了能够在切换到以目标表为准之前有一个过渡阶段。也就是说,通过先写目标表,再写源表这种方式,万一发现数据迁移出现了问题,还可以回滚为先写源表,再写目标表,确保业务没有问题。

在这里插入图片描述

保持增量校验和修复

在切换了双写顺序之后,保持增量校验和修复是顺理成章的,方案和步骤5一样,不过步骤5的校验和修复都是以源表为准,那么在这一步,就是以目标表为准

  • 不管什么先后顺序、什么并发问题,在修复的时候你永远用主表的最新数据去修复,绝对不会出问题
  • 如果源表或者目标表本身也是分库分表的,那么无非就是查询、修复数据的时候使用对应的分库分表规则而已
  • 整个方案在第八步之前,都是可以回滚的。一旦切换到第八步,就不可能回滚了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/397204.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Redis中的set类型

set的含义 集合设置(和get相对应) 集合就是把一些有关联的数据放到一起 集合中的元素是无序的(和list的有序是对应的-顺序很重要,这里的无序就是顺序不重要);在list中[]1,2,3],[1,3,2],是两个…

Java开发工具IDEA

IDEA概述 Intellij IDEA IDEA全称Intellij IDEA,是用于Java语言开发的集成环境,它是业界公认的目前用于Java程序开发最好的工具。 集成环境 把代码编写,编译,执行,调试等多种功能综合到一起的开发工具。 IDEA下载和安…

PDF在线预览实现:如何使用vue-pdf-embed实现前端PDF在线阅读

目录 PDF在线预览实现:如何使用vue-pdf-embed实现前端PDF在线阅读 一、前言 二、vue-pdf-embed是什么 1、作用与场景 2、vue-pdf-embed的优点 三、项目初始化与依赖安装 1、初始化Vue项目 2、安装依赖 3、集成vue-pdf-embed插件 四、一个实际的应用demo …

Java面试题精选:消息队列(一)

1、为什么使用消息队列 问题用意: 其实就是想问一下消息队列有哪些使用场景,你项目中什么业务场景用到了消息队列,有什么技术挑战。使用MQ后给你带来了什么好处 规范回答: 消息队列的常见使用场景很多,但比较核心的…

【漏洞修复】Tomcat中间件漏洞

1.CVE-2017-12615 抓包上传一句话木马 密码passwd 2.后台弱口令部署war包 先用弱口令登录网站后台 制作war包 将172.jsp压缩成.zip文件,修改后缀为.war 上传 蚁剑链接 3.CVE-2020-1938 Python2 CVE-2020-1938.py IP -p 端口 -f 要读取的文件 漏洞修复&#xf…

超越自我——带你学haproxy算法一遍过!!!

文章目录 前言介绍 静态算法static-rrfirst 动态算法roundrobinleastconn 其他算法source算法map-base 取模法一致性hashuriurI_param 取模法hdr 总结本文相关连接如下: 前言 本文相关连接如下: 如果想更多了解haproxy的相关知识,请点击&am…

HTTP协议和运行原理

HTTP 是一个在计算机世界里专门在两点之间传输文字、图片、音频、视频等超文本数据的约定和规范。不仅仅适用于[服务器<–>客户端]也是适用于[服务器<–>服务器] HTTP 状态码 1xx 1xx 类状态码属于提示信息&#xff0c;是协议处理中的一种中间状态&#xff0c;实际…

操作系统 IO 相关知识

操作系统 IO 相关知识 阻塞与非阻塞同步与异步IO 和系统调用传统的 IODMAmmap 内存映射sendfilesplice 常用的 IO 模型BIO&#xff1a;同步阻塞 IONIO&#xff1a;同步非阻塞 IOIO 多路复用信号驱动 IOAIO&#xff1a;异步 IO 模型 IO 就是计算机内部与外部进行数据传输的过程&…

加密案例分享:电子设备制造行业

企业核心诉求选择 1.某企业规模庞大&#xff0c;分支众多&#xff0c;数据安全管理方面极为复杂&#xff1b; 2.企业结构复杂&#xff0c;包括研发、销售、财务、总部、分部、办事处、销售等单位连结成为一个庞大的企业组织&#xff0c;数据产生、存储、流转、使用、销毁变化…

NIO线程模型

NIO线程模型主要涉及以下几个方面&#xff1a; 一、基本概念 NIO&#xff08;New Input/Output&#xff09;是Java的一种新的输入输出模型&#xff0c;也被称为非阻塞IO。其核心特点是数据读写操作均是非阻塞的&#xff0c;即在进行读写操作时&#xff0c;若数据未准备好&…

第129天:内网安全-横向移动WmiSmbCrackMapExecProxyChainsImpacket

这里这个环境继续上一篇文章搭建的环境 案例一&#xff1a; 域横向移动-WMI-自带&命令&套件&插件 首先上线win2008 首先提权到system权限 wmic是windows自带的命令&#xff0c;可以通过135端口进行连接利用&#xff0c;只支持明文方式&#xff0c;优点是不用上传别…

【区块链+医疗健康】医链 - 区块链医疗信息管理系统 | FISCO BCOS应用案例

根据《“十四五”规划和 2035 远景目标纲要》&#xff0c;我国在“十四五”时期将全面推进医疗信息化建设。工信部等部 门联合发布《关于加快推动区块链技术应用和产业发展的指导意见》&#xff0c;促进区块链在医疗健康等公共服务领域开 展应用&#xff0c;促进业务协同办理。…

ES6 export import

1.Export 模块是独立的文件&#xff0c;该文件内部的所有的变量外部都无法获取。如果希望获取某个变量&#xff0c;必须通过export输出&#xff0c; 声明的同时导出变量、函数以及类 // profile.js export var firstName Michael; export var lastName Jackson; export var…

【JUC】Java对象内存布局和对象头

文章目录 面试题Object object new Object() 谈谈你对这句话的理解&#xff1f; 对象在堆内存中存储布局权威定义&#xff08;周志明老师JVM第三版&#xff09;对象在堆内存中的存储布局详解对象头的MarkWord源码对象标记源码 对象内存布局&#xff08;使用JOL证明&#xff09…

猫头虎 分享已解决Bug || java.lang.NullPointerException 解决方案

&#x1f42f; 猫头虎 分享已解决Bug || java.lang.NullPointerException 解决方案 在 后端开发过程中&#xff0c;我们经常会遇到各种各样的Bug。而其中最常见之一就是 java.lang.NullPointerException。很多小伙伴在遇到这个问题时&#xff0c;往往会感到非常头痛&#xff0…

设计模式-单一职责模式

DecoratorBridge Decorator 动机 在某些情况下我们可能会 “过度地使用继承来扩展对象的功能”&#xff0c;由于继承为类型引入的静态特质&#xff0c;使得这种扩展方式缺乏灵活性&#xff1b;并且随着子类的增多&#xff08;扩展功能的增多&#xff09;&#xff0c;各种子类的…

yolov8断点续训

1. 前言 我们在使用yolov8进行训练的时候往往会因为各种各样的原因中断训练&#xff0c;如&#xff1a; 开了太多程序导致崩溃突然断电其他原因 这时候如果下次能继续上次训练的结果继续训练会节省很多时间 2.复现与解决办法 2.1 正常启动训练 yolov8环境搭建与简单配置请…

给 Python 的第三方模块安装工具 pip 换源

sudo mkdir ~/.pipcd .pipsudo nano pip.conf[global] timeout 10 index-url http://mirrors.aliyun.com/pypi/simple/ extra-index-url http://pypi.douban.com/simple/ [install] trusted-hostmirrors.aliyun.compypi.douban.com喜欢或对你有帮助&#xff0c;点个赞吧&…

PMBOK® 第六版 规划范围管理

目录 读后感—PMBOK第六版 目录 规划范围管理就像是为项目划定清晰的界限&#xff0c;通过书面形式明确项目团队必须完成的工作&#xff0c;这有助于有效控制和交付目标产品&#xff0c;防止范围蔓延&#xff0c;合理分配资源&#xff0c;从而提高项目成功率和客户满意度。 一…

Tomcat搭建JSPServlet

一、Tomcat环境搭建 1. 将项目变为Web项目 选中项目&#xff0c;点击Help中的Find Action 搜索Add Framework Support 勾选Web Application 出现这些文件就是成功了 2. 配置Tomcat 点击Edit Configurations 点击加号&#xff0c;选择Tomcat Server Local Deployment栏下点击…