MyFlash——美团点评的开源MySQL闪回工具

文章内容

由于运维、DBA的误操作或是业务bug,我们在操作中时不时会出现误删除数据情况。早期要想恢复数据,只能让业务人员根据线上操作日志,构造误删除的数据,或者DBA使用binlog和备份的方式恢复数据,不管那种,都非常费时费力,而且容易出错。直到彭立勋首次在MySQL社区为mysqlbinlog扩展了闪回功能。

在美团点评,我们也遇到过研发人员误删主站的配置信息,从而导致主站长达2个小时不可用的情况。DBA同学当时使用了技术团队自研的binlog2sql完成了数据恢复,并多次挽救了线上误删数据导致的严重故障。不过,binlog2sql在恢复速度上不尽如人意,因此我们开发了一个新的工具——MyFlash,它很好地解决了上述痛点,能够方便并且高效地进行数据恢复。

现在该工具正式开源,开源地址为:https://github.com/Meituan-Dianping/MyFlash

闪回工具现状

先来看下目前市面上已有的恢复工具,我们从实现角度把它们划分成如下几类。

① 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等工具进行筛选。

总结了上述几种工具的优缺点,我认为理想的闪回工具需要有以下特性。

a. 无需把binlog解析成文本,再进行转换。
b. 提供原生的基于库、表、SQL类型、位置、时间等多种过滤方式。
c. 支持MySQL多个版本。
d. 对于数据库的代码重构不敏感,利于升级。
e. 自主掌控binlog解析,提供尽可能灵活的方式。

在这些特性中,binlog的解析是一切工作的基础。接下来我会介绍binlog的基本结构。

binlog格式初探

binlog格式概览

一个完整的binlog文件是由一个format description event开头,一个rotate event结尾,中间由多个其他event组合而成。

binlog文件实例:

每个event都是由event header 和event data组成。下面简单介绍下几种常见的binlog event。

① formart description event

表达的含义是:

170905 01:59:33 server id 10 end_log_pos 123 CRC32 0xed1ec563
Start: binlog v 4, server v 5.7.18-log created 170905 01:59:33

② table map event

表达的含义是:

170905  01:59:33 server id 10  end_log_pos 339 CRC32 0x3de40c0d     
Table_map: `test`.`test4` mapped to number 238

③ update row event

表达的含义是:

170905  01:59:33 server id 10  end_log_pos 385 CRC32 0x179ef6dd     
Update_rows: table id 238 flags: STMT_END_F 
UPDATE `test`.`test4` WHERE @1=3 SET @1=13;

binlog event回滚

根据上面的binlog介绍,可以看到每个binlog event中event header有个type_code,其中insert为30,update为31,delete为32。对于insert和delete两个相反的操作,只需把type_code互换,则在binlog event级别完成回滚。

而对于update操作,其格式如下。

其中,BI是指before image,AI是指after image。

我们只需依次遍历修改前的数据和修改后的数据,并一一互换即可。因此整个回滚操作的难点在于回滚update语句,而update语句回滚的核心在于计算出每个AI、BI的长度。下面介绍下长度以及部分字段的计算方法。

镜像长度计算

镜像是由一个个字段组成的,根据字段类型的不同,其计算长度的方法也不一样。

只与字段类型相关。比如int占用4个字节,bingint占用8个字节。其中类型信息可以从table map event中获取。

与字段类型及其参数相关。比如decimal(18,9),占用9个字节,参数信息在table map event中。

与字段类型、参数以及实际存储的值相关。比如varchar(10),有1个字节表示长度,之后的字节才表示真正的数据。比如varchar(280),有2个字节表示长度。实际的长度和数据在一起。

解析binlog中的若干个关键点

① length encoded integer

binlog中一个或者多个字节组合,分别表示了不同的含义。比如,timestamp是由固定的4个字节组成,event类型由一个字节表示;数据库名和表名最长为64个字符,即使每个字符占用3个字节,那么占用的字节数为192<255。因此最多使用一个字节,就可以完成实际长度表示。

然而列的实际数量,可能需要超过1个字节、2个字节、3个字节甚至8个字节去表示。如果我们使用最大的8个字节去表示,那么在绝大多数情况下都是浪费存储空间的。针对这种情况,length encoded integer应运而生。

比如在获取一个varchar类型的长度时,首先读取第一个字节,如果值小于251,那么varchar的长度就是第一个字节表示的长度。如果第一个字节的值为0xFC,那么varchar的长度是由该字节之后的后两个字节组成,以此类推。

② decimal类型

decimal是由整数部分和小数部分组成。无论是整数还是小数,每9个数字,需要4个字节。如果不是9的倍数,剩余的小数位,需要的字节数如下,为方便描述,将该关系定义为函数Fnum。

举例,对于 decimal(18,10):

整数部分可展示的为8,用int,即4个字节。

小数部分,需要的字节数为 (10 /9)*4+Fnum(10%9)=5。

那么总共加起来需要4+5=9个字节。

闪回工具架构

在上面的章节中,介绍了单个binlog event的反转方法。在实践中,我们往往需要把某个binlog,按照指定的条件,过滤出需要的binlog,并进行反转。那么MyFlash是如何完成这些目标的呢?

解析binlog

首先把binlog文件,解析成多个event,放入到相关队列中。在实现上,为了尽可能加快解析速度,可以让用户指定解析的开始与结束位置。把binlog文件解析成binlog event后,再判断下是否符合指定的时间条件,若不符合,则丢弃该event。

注意:用户可以不指定位置和时间,则解析整个文件。如果只指定时间,那么也需要从文件开始处解析,取出时间信息,再进行判断。因此,当需要回滚的binlog只占整个binlog的一小部分时,推荐使用指定位置。

重组event

把binlog event组成最小执行单元。在常见的binlog event中table_map event包含了所要了表名、库名等元数据信息,而row_event(包含write_event、delete_event、update_event)包含了真正的数据。因此在设计中使用了一个最小执行单元概念。所谓的最小执行单元,即least execution event unit,通常包含一个table_map event和若干个row_event。

比如在binlog格式概览一节中,介绍了table_map_event和update_row_event。如果只有update_row_event,那么我们无法知道这个event对应的行记录变更对应的表。因此一个完整的最小执行单元最少包含一个table_map_event和write_row_event、update_row_even、delete_row_event中的一个。

为什么我们需要使用最小执行单元?因为我们在闪回操作时,不能简单的把每个event反转之后,然后再将所有event的顺序反转过来。如果这样的话,就会出现table_map event在row event之后,这显然是违反binlog执行逻辑的。

有了最小执行单元之后,只需两步,即可完成反转。

a. 反转最小执行单元中的row event。
b. 逆序最小执行单元队列,即可。

当然在反转前,也可以增加过滤操作。比如过滤库名、表名和SQL类型等。

生成binlog文件

有了逆序的最小执行单元队列后,只需把每个最小执行单元依次输入到文件即可。不过不要忘了修改每个binlog event里的next_position,用来表示下一个binlog的位置。

性能对比

测试场景

使用testFlashback2,插入100万条数据:

CREATE TABLE testFlashback2 (
id int(11) NOT NULL AUTO_INCREMENT,
nameShort varchar(20) DEFAULT NULL,
nameLong varchar(260) DEFAULT NULL,
amount decimal(19,9) DEFAULT NULL,
amountFloat float DEFAULT NULL,
amountDouble double DEFAULT NULL,
createDatetime6 datetime(6) DEFAULT NULL,
createDatetime datetime DEFAULT NULL,
createTimestamp timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
nameText text,
nameBlob blob,
nameMedium mediumtext,
PRIMARY KEY (id)
) ENGINE=InnoDB

mysql> select count() from testFlashback2;
±---------+
| count(
) |
±---------+
| 1048576 |
±---------+
1 row in set (0.16 sec)

delete from testFlashback2;

测试结果

从上述图表中可以看出,MyFlash的速度最快。

原文链接

https://mp.weixin.qq.com/s?__biz=MjM5NjQ5MTI5OA%3D%3D&mid=2651747096&idx=1&sn=e561ce9254f69c3fcd04c73b44e56e69&chksm=bd12aa558a65234332298e1d611da408ec45afb2d08ecd22078a600379525e75b2fa13149fb0&mpshare=1&scene=23&srcid=1116o1gBWoNf47Rv1E1ngYir#rd

服务推荐

猜你喜欢

转载自blog.csdn.net/gt9000/article/details/87896889