mysql 数据闪回
背景
数据闪回属于数据恢复的一个范畴, 常见的数据恢复的方法有很多, 但都大同小异。
主要有以下两种方案:
- 进行变更之前把变更前的数据存储下来用于恢复
- 基于全量备份+ binlog增量备份的方式
这两种方案,私下里操作最多的往往是第一种, 因为第一种比较小作坊, 容易操作, 但操作也相对零碎, 人工化, 容易产生失误。第二种是业界许多解决方案的标准, 例如RDS的备份恢复克隆等功能就是按照第二种方案去做的, 但第二种有三个问题, 第一,备份数据比较庞大, 比较多,恢复时间非常久, 第二, 需要有一整套的体系支持(包括不仅限于服务器、运维、等)第三,第二种需要比较大的开发量。需要对binlog 进行订阅解析以及合理的安排, 对备份策略进行合理的程序化定制。
很多企业在发展到一定阶段之后必定会对线上数据变更进行相应的管控, 建立好相应的变更管控的流程。 但这个流程也非万能的, 数据被误操作的概率还是存在, 当数据被误操作后,如何快速恢复到操作前的一个状态, 成了本文主要关注的一个话题。
闪回工具现状
mysqlbinlog工具配合sed、awk
该方式先将binlog解析成类SQL的文本,然后使用sed、awk把类SQL文本转换成真正的SQL。
- 优点:当SQL中字段类型比较简单时,可以快速生成需要的SQL,且编程门槛也比较低。
- 缺点:当SQL中字段类型比较复杂时,尤其是字段中的文本包含HTML代码,用awk、sed等工具时,就需要考虑极其复杂的转义等情况,出错概率很大。
给数据库源码打patch。该方式扩展了mysqlbinlog的功能,增加Flashback选项。
- 优点:复用了MySQL Server层中binlog解析等代码,一旦稳定之后,无须关心复杂的字段类型,且效率较高。
- 缺点:在修改前,需要对MySQL的复制代码结构和细节需要较深的了解。版本比较敏感,在MySQL 5.6上做的patch,基本不能用于MySQL 5.7的回滚操作。升级困难,因为patch的代码是分布在MySQL的各个文件和函数中,一旦MySQL代码改变,特别是复制层的重构,升级的难度不亚于完全重新写一个。
使用业界提供的解析binlog的库,然后进行SQL构造,其优秀代表是binlog2sql。
- 优点:使用业界成熟的库,因此稳定性较好,且上手难度较低。
- 缺点:效率往往较低,且实现上受制于binlog库提供的功能。
上述几种实现方式,主要是提供的过滤选项较少,比如不能提供基于SQL类型的过滤,需要回滚一个delete语句,导致在回滚时,需要结合awk、sed等工具进行筛选。
开源方案
总体来讲如下的两种方案其实是一个原理,其中一个程序化了, 产品化了而已。
美团的 MyFlash
MyFlash 是由美团点评公司技术工程部开发维护的一个回滚DML操作的工具。该工具通过解析v4版本的binlog,完成回滚操作。相对已有的回滚工具,其增加了更多的过滤选项,让回滚更加容易。
- 无需把binlog解析成文本,再进行转换。
- 提供原生的基于库、表、SQL类型、位置、时间等多种过滤方式。
- 支持MySQL多个版本。
- 对于数据库的代码重构不敏感,利于升级。
- 自主掌控binlog解析,提供尽可能灵活的方式。
闪回工具分析
以上业界的一些闪回方案,基本上都是基于Binlog 去倒腾的, 有一些明显的限制
- 不能针对某条语句(tansaction)进行回滚操作
- 需要维护binlog, 建立binlog 相关的逻辑体系
- 人工变更与应用变更不能真正分开(闪回是针对某个表, 某个类型,在某一段时间内的全部数据)
- 使用起来不方便, 对于普通研发来说成本还是蛮高的
- …
闪回工具的方案
为了解决以上所有问题, 主要考虑以下两种方案(两种都是原创)
基于SQL重写与备份的方案
这种方案的整个大的流程如下:
- 变更前备份: 在实际执行语句前,通过对SQL语句的重写,来达到保存变更前状态的一个目的。
- 变更执行: 进行实际的变更操作
- 备份恢复: 利用备份的数据进行恢复(一般是自动生成rollback的SQL语句进行执行)
上述环节中, 最重的一部分工作就是变更前的备份,其中工作量最大的一部分就是进行SQL的重写,这有可能涉及到AST语法树的部分, 当然对于比较简单的SQL语句, 也用不着AST语法树。
这里面最大的工作量就是SQL语句的重写, 利用重写的SQL进行原数据查询, 再将变更前的数据备份下来。
整体的一个流程如下:
存在的问题: 备份与实际写入之间是存在时间间隔的, 如果这个时间间隔内存在其他变数,则也只能恢复到备份的时候的状态。
基于binlog 映射SQL语句的方案
对于每一个DML操作, binlog 总是忠实的记录了DML操作的详情,我们可以利用这个详情用于事后恢复数据。
但binlog 并未将语句与binlog一一对应起来, 如果要做,可能需要花费的代价比较大,浪费很多的存储空间。
由于我们的需求是仅对我们的变更进行回滚,不对其他同类型同时间的变更进行回滚, 所以,我们需要有能力将binlog与我们的变更进行一一对应。
整体的操作流程如下:
从图中可以看到, 变更的执行跟数据的备份恢复其实是分开的, 变更执行只是变更执行,不用去卡很多时间点相关的概念, 也不同担心备份的跟实际变更的数据会不一致。
避免了上面方案的一个时间差的问题,而且不需要进行SQL语句的重写与反查数据库。
为了能做变更与binlog 的唯一对应, 我们需要对变更执行这个步骤做一些手脚, 加入一个唯一对应的标志。
由于binlog 是会忠实记录变更的, 因此我们可以给每个变更生成一个唯一标志, 并把这个唯一标志体现在binlog 里面, 这样我们就实现了变更与binlog的对应。
有了这层对应关系我们就可以轻松的对这个变更进行数据的恢复, 从而不影响其他同时间同类型的变更。
数据备份模块也是需要投入开发的一个模块他主要涉及到以下工作:
0. binlog 名称与位点的确定
- slave 伪装
- binlog 接收与解析
- 定位变更的binlog
- 由对应的binlog生成相应的备份
总结
就开发量上来讲, 两种方案都是差不多的, 整个周期至少都需要2-3周左右的开发量,
我们显然应该采取第二种方案, 因为第二种方案与业界许多方案有比较多的共通之处, 也是比较通用靠谱的一种方案, 而且避免了查询间隔带来的数据不一致问题。
而且它与现有生态融合比较好。