暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

解密TenDB(MySQL) Flashback 实现(一):多库多表回档实现

腾讯游戏存储与计算技术 2021-05-19
589

背景

每个业务都或多或少遇到过业务bug、误操作等问题,因此数据恢复一直是一个很普遍、也很紧急的需求。通常MySQL用户习惯使用以下两种方式进行数据恢复:

  • 全备+Binlog方式构造特定时间点的数据;

  • 业务相关同学根据业务日志,构造出更新SQL更新现网

然而这两种方式都非常耗时,且容易出错,造成停机时间过长,对用户体验造成影响。

Flashback开源工具研究

2012年淘宝彭立勋提出了MySQL Flashback特性,并在MySQL-5.5.18的基础上开发实现。MariaDB分支从10.2.4版本开始支持flashback 特性。

除此以外,美团、58到家等也开源了Flashback组件(美团MyFlash https://github.com/Meituan-Dianping/MyFlash、Mariadb flashback https://mariadb.com/kb/en/getting-the-mariadb-source-code/)

带着下面几个问题,我们开始对已有Flashback开源工具进行研究:

  • 社区已开发出很多MySQL flashback工具,能否直接使用,避免重复开发?

  • Flashback只能回档ROW模式下DML语句(insert/delete/update/replace),DDL(create/alter/drop)如何处理?

  • 现网Tendb3(MySQL5.7)、Tendb2(MySQL5.6)、Tendb(MySQL5.5)是主要版本,均有很多实例,如何让三个版本都能使用Flashback?

研究结果如下:

结论: 经研究对比后发现,现有Flashback组件均无法满足我们的需求,因此我们选择自研实现。

Flashback 实现

MySQL binlog格式

binlog简单来说就是记录数据更新操作的二进制日志。binlog两个重要作用:

  • 用于主从之间数据同步,也就是将master上的更新操作在slave上重放;

  • 由于binlog记录了数据更新操作,因此binlog也常用于恢复数据。

binlog_format && binlog_row_image
  • STATEMENT

    : MySQL server上的每一个数据更新操作以SQL文本的形式记录到binlog文件中。

    可以简单理解为,用户传什么SQL给server执行,server就将该SQL原封不动的记录到binlog。不会保存数据修改前与修改后的镜像(image)。 

    此时,所有DDL(alter drop create等) DML(insert update delete)语句在binlog中都会以QUERY_EVENT(关于QUERY_EVENT后面会介绍)保存。

  • ROW
    :
    所有DDL(alter drop create等)依然以SQL文本方式(QUERY_EVENT)保存。

    所有DML(insert update delete)则以二进制形式保存每一行数据的更新操作,每一行数据的更新记录中包含每一列更新前的镜像(Before Image,简写为BI)和更新后的镜像(After Image,简写为AI)

    ROW 模式下,binlog是否记录每一行更新前后镜像还受binlog_row_image参数的影响:

    • binlog_row_image=FULL
      : 无论某列的值是否有更新,都将记录所有列更新前的镜像(Before Image)和更新后的镜像(After Image);

    • binlog_row_image=MINIMAL
      : Before Image只记录唯一识别列(唯一索引列、主键列),After Image只记录修改列;

    • binlog_row_image=NOBLOB
      : 类似FULL格式,记录所有列更新前后的镜像。但对于BLOB或TEXT格式的列,如果不是唯一识别列(唯一索引列、主键列),或者没有修改,则不记录。

  • MIXED
    : STATEMENT
    ROW
    两种格式混用。

    详情请参考:Mixed Binary Logging Format (https://dev.mysql.com/doc/refman/5.7/en/binary-log-mixed.html)

而我们本文讨论的mysql flashback,有一个大前提即:

binlog_format=ROW
binlog_row_image=FULL/NOBLOB

(在MySQL 5.5版本中,指定binlog_format=ROW

即可)

binlog文件内容格式简介

每个binlog文件由一系列event组成,文件最开始的event是FORMAT_DESCRIPTION_EVENT,文件结尾的event是ROTATE_EVENT,中间是其他event的组合。

每种event的含义可参考:Event Meanings;

每种event的组成部分可参考:Event Data for Specific Event Types

几种常见event的介绍:

  • GTID_EVENT
    : MySQL server开启Gtid特性后,server会为每个事务(begin~commit)都是生成一个全局事务号(GTID,global transaction identifier),GTID_EVENT即用于记录该编号。如:

      #191119  9:54:11 server id 135758565  end_log_pos 237 CRC32 0x49f8957c  GTID    last_committed=0        sequence_number=1       rbr_only=yes
      /*!50718 SET TRANSACTION ISOLATION LEVEL READ COMMITTED*//*!*/;
      SET @@SESSION.GTID_NEXT= '29b05b90-ee79-11e9-9fff-fe0727adf2b3:1'/*!*/;
      复制
    • Query_log_event/QUERY_EVENT
      : 用于记录SQL语句。如use database、以及CREATE、ALTER、DROP等DDL(Data Definition Language)操作。DDL不能做flashback。

        # at 538
        #191119 9:54:11 server id 135758565 end_log_pos 625 CRC32 0x87610211 Query thread_id=3 exec_time=0 error_code=0
        SET TIMESTAMP=1574128451/*!*/;
        BEGIN
        /*!*/;
        复制
      • Table_map_log_event/TABLE_MAP_EVENT
        : 用于描述数据变更涉及表的表结构是什么样的。

        在binlog_format=ROW时binlog中数据变更和数据表结构定义是作为两种event存放的。

      • Rows_log_event/ROWS_EVENT
        binlog_format=ROW
        时,ROWS_EVENT
        表示行数据的变更事件。其中包含一行或多行的Before Imag

        • Write_rows_log_event/WRITE_ROWS_EVENT
          :写入数据(即INSERT、REPLACE语句),包含变更后的数据镜像(After Image);

        • Update_rows_log_event/UPDATE_ROWS_EVENT
          :更新数据(即UPDATE语句),包含变更前后的数据镜像(Before Image和After Image);

        • Delete_rows_log_event/DELETE_ROWS_EVENT
          :删除数据(即DELETE语句),包含变更前的数据镜像(Before Image)

      • Xid_log_event/XID_EVENT
        :事务提交时会生成一个XID_EVENT,内容就是COMMIT;

      event格式

      每个event均由event header和event data两部分组成。

      下面我们以UPDATE_ROWS_EVENT
      为例:

      event header:

      event_data:

      flashback实现思路

      首先,需要说明一下WRITE_ROWS_EVENT
      UPDATE_ROWS_EVENT
      DELETE_ROWS_EVENT
      对应的type_code
      :

        WRITE_ROWS_EVENT = 30,
        UPDATE_ROWS_EVENT = 31,
        DELETE_ROWS_EVENT = 32,
        复制

        flashback整体实现上有三层反转:

        • 第1层反转: 单行数据更新event:

          • 如果该event是WRITE_ROWS_EVENT
            ,则直接将event header中的type_code
            从30变成32。此时insert反转成delete;

          • 如果该event是DELETE_ROWS_EVENT
            ,则直接将event header中的type_code
            从32变成30。此时delete反转成insert;

          • 如果该event是UPDATE_ROWS_EVENT
            ,则需要将event data中AI 与 BI交换。AI:After Image,BI:Before Image

        • 第2层反转:反转由一个TABLE_MAP_EVENT
          和多个[INSERT|UPDATE|DELETE] ROWS_EVENT
          组成的最小执行单元((least execution event unit)。最小执行单元概念由美团MyFlash
          提出。如下面insert语句:

            insert into t_char_with_primary(name,grade,birthday,address)
            values
            ('李靖',78.5,'1990-10-01 10:00:00','广东省深圳市南山区'),
            ('金吒',95.5,'2010-10-01 10:00:00','广东省深圳市南山区科兴'),
            ('木吒',96,'2011-12-01 10:00:00','广东省深圳市宝安区西乡'),
            ('哪吒',96,'2013-11-01 10:00:00','中国香港'),
            ('路人甲',78,'2012-01-01 08:00:00','韩国');
            复制

            其最小执行单元如下:

            flashback后的结果:"路人甲"最后一个插入,现在最先删除;"李靖"最先插入,现在最后删除。

          • 第3层反转: 对整个binlog倒序输出所有[INSERT|UPDATE|DELETE] ROWS_EVENT

            一个binlog文件包含18:00到19:00的更新日志,则flashback后,优先输出19:00反转后的结果,最后输出18:00反转后的结果。

          Flashback实现整体流程图

          bug修复+优化

          Flashback自研实现后,在灰度测试过程中完成以下一系列bug修复与优化:

          • bug修复:修复UPDATE T1,T2 INNER JOIN T2 ON T1.C1 = T2.C1 中如果只想恢复T2表的数据的bug;

          • bug修复:  Tendb3(MySQL5.7) mysqlbinlog解析Tendb1(MySQL5.5) binlog文件bug;

          • 重点优化:添加选项–flashback-databases,支持同时flashback多个库;

          • 重点优化:添加选项–flashback-tables,支持同时flashback多个表;

          • 重点优化:简易DDL语句识别并报错,排除校验、监控心跳语句,其余Querey_event
            都报错退出;

          同类工具对比

          Flashback 数据正确性验证方案

          下面"数据正确性验证方案"将帮助我们有效确认Flashback是否正确执行。


          • 数据验证流程: 如上图;

          • 注意:B1 到 B2 这段时间内,a b c d表必须持续更新;

          • B3 备份中a b表insert sql文件和B1备份中a b表insert sql文件 md5值一致,则代表Flashback正确执行;

          • B3 备份中c d表insert sql文件和B2备份中c d表insert sql md5值一致,则代表Flashback未影响非目标表;

          Flashback单据

          Flashback工具完善后,让用户/DBA手动敲命令完成数据回档是不现实的。因此,我们在管理平台中实现了"Flashback单据",用户/DB可以方便的通过提交回档单据进行数据的回档操作。

          下面来看看单据开发过程中遇到的一些挑战:

          • 挑战一: Tendb Cluster模式如何尽肯能保证 要么全部成功,要么全部失败?

            部分成功,部分失败,对集群模式来说与"数据污染无异"。

            方案如下:

            单个MySQL回档分四个步骤完成:前置检查、文件拉取、flashback_binlog、sql导入;

            通过共享Redis保存每个实例当前的状态;

            每个MySQL在执行下一步骤之前,必须确保集群所有MySQL上一步骤状态是成功的。

          • 挑战二: Flashback文件遍历完成前,结果保存在内存中,如何保证不占用大量内存?

            一条命令处理全部binlog文件,会将binlog文件内容全部读取到内存中,此时内存占用过大极可能造成oom。

            经调研发现,现网DB单个binlog文件最大257M且超大事务很少遇到。

            因此我们选择将回档步骤拆解为两步完成:

            • mysqlbinlog --flashabck $binlogFile > $dest.sql
              少量并发将单个binlog文件解析为sql文件;

            • mysql -u$user -p$password ... < $dest.sql
              串行倒序导入sql文件

            Flashback 单据整体实现

            Flashback收益

            整体对比,Flashback方案时间优势特别明显。

            Flashback使用建议

            1. 执行前强烈建议先备份相关库表数据,以便flashback导入.sql文件失败后 不至于数据彻底污染;

            2. flashback执行期间相关库表不能还有写操作(建议停服执行flashback恢复数据)。新的写操作可能直接导致flashback执行失败,甚至造成现网数据污染。

            3. FOREIGN KEY CASCADE DELETE/UPDATE场景中,Child表无法使用flashback恢复数据:

              FOREIGN KEY CASCADE DELETE/UPDATE场景中,Father表delete/update时,binlog中只会记录Father表的delete/update,而Child表delete/update不会记录。

              而flashback回档依赖于binlog文件数据 ,所以此时Child表数据flashback无法完成回档。

            参考文档

          文章转载自腾讯游戏存储与计算技术,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

          评论