使用MTS加快mysql奔溃后binlog恢复

mysql 专栏收录该内容
42 篇文章 0 订阅

场景是这样的:有一个mysql库,5.7.29版本的,被任务操作误删除了,需要恢复。但是日常的备份操作因为没有安装xtrabackup热备工具,导致日常的全备曾备没有正常进行,好在binlog基本都有备份(缺了中间一个binlog文件,这个在后面细说)。也就是说,我们只能通过重放binlog进行恢复。WTF!全部的binlog是108个文件,每个512M大小。需要尽可能快的恢复。

对于上面的场景,个人认为有以下几种方案:
1,使用mysqlbinlog结合mysql进行重放。具体是将binlog文件列表列出在mysqlbinlog后,通过mysqlbinlog读取binlog内容,再通过管道传递给mysql进行执行。例如:
                    mysqlbinlog  binlog.000001  binlog.000002 binlog.000003 ..... | mysql -uuser -p -P3306 -f
这里加了-f选项,是因为中间缺了一个binlog文件,在执行位于缺失binlog文件之后的binlog内容时,可能会出现各种duplicate key或者key not found错误。绝对不能依靠人工来实时盯着实时处理,会累死。
这个方案的缺点在于,mysql是一个单客户端线程,意味着所有的binlog 事务都是一个接一个串行执行的,事务的重放需要花较长时间。优点是简单。但是需要注意一点,线程需要放在后台执行,以免terminal超时断掉导致重放中断。例如:
cat > recover.sh <<EOF
#!/bin/bash
mysqlbinlog  binlog.000001  binlog.000002 binlog.000003 |  mysql -uuser -pxxxx -P3306 -f
EOF
nohup sh recover.sh > recover.log 2>&1 & ;disown

2,将所有的binlog文件作为relay log文件,通过mysql的MTS机制,进行多线程的binlog 日志应用。
这种方案的优点就是利用mysql现有的MTS机制,多线程的事务重放,花费较短的时间。重放的过程基本不需要人为介入。机制比较成熟,可以利用现有的对主从复制的监控机制进行过程监控。
缺点在于前期准备较为复杂。并且,因为中间缺失了一个binlog文件,对于GTID事务,就会造成GTID的不连续。因此在恢复完成之后,需要通过:A) mysql5.7版本,reset master后,set @@global.gtid_purge;B)mysql8.0版本,可以使用全量的gtid_set设置gtid_purged或者通过set @@global.gtid_purged='+gtid_set'的方式,补上这一段缺失的binlog。
主要通过以下步骤:


a,确保数据库处于shutdown状态。确保处于关闭状态,是因为我们后面要手动修改relay相关文件,包括复制binlog文件。为了确保我们的变更能被mysql观察到。linux系统下,虽然文件名路径都一致,但是id不同,那也不是同一个文件。要避免这个问题,以及由此产生的奇奇怪怪的问题。后面会详细说。

b,将需要恢复的binlog文件copy到一个固定目录;例如copy到/database/my3308/log/relay/中。同时修改my.cnf配置文件中的relay_log配置项,指向这个路径:relay_log=/database/my3308/log/relay/binlog

c,修改relay_log.index文件(默认channel,如果准备使用其他channel,文件名变为relay_log_channel.index),将需要重放的binlog路径按照顺序添加进来:
find . -name 'binlog*'| sed 's=\./=/database/my3308/log/relay/=g'  > relay_log.index

d,修改权限,确保mysql能够访问这些文件:
chown -R mysql:mysql  /database/my3308/log

e,修改my.cnf配置文件,修改如下配置:
slave_parallel_type=LOGICAL_CLOCK      #这个是为了确保启用并行复制
slave_parallel_workers=32                          #MTS线程数
slave_exec_mode=IDEMPOTENT              #这个在本场景是比较重要的,前面说了,缺了一个中间的binlog文件,所以必然会有大量duplicate key、key not found错误,这个配置用于在复制时忽略这些错误
innodb_flush_log_at_trx_commit=0             #不用过多sync磁盘,
relay_log_recovery=off                                #这个尤其重要,如果不关闭的话,mysqld一启动,所有copy过来的binlog文件全给你删掉,那没得玩了
skip_slave_start                                           #复制线程不要随进程启动
relay_log=/database/my3308/log/relay/binlog      #这里主要是  /database/my3308/log/relay/这个路径要与我们copy的路径相同,至于最后的binlog不重要,可以不同。
relay_log_index=/database/my3308/log/relay/relay_log.index   #这里要指向我们上面修改的文件
server_id=2                                                  #这个需要设置一个与恢复之前的数据库不同的值,当然也可以不修改,通过--replicate-same-server-id进行管理。具体看下边。

f,以--skip-slave-start   --replicate-same-server-id选项起库。--replicate-same-server-id是比较重要的,因为我们是从binlog恢复,如果my.cnf中配置的server_id与binlog中的server_id一致的话,binlog事务会被跳过,一旦跳过,就会看到Retrieved_Gtid_Set已经读取了所有的gtid,但是Executed_Gtid_Set并没有增长,并且Slave_SQL_Running_State处于Slave has read all relay log; waiting for more updates状态,同时,copy过来的binlog文件一个一个的被mysql删除掉。这就是因为复制协调器读取了全部的gtid,但是发现binglog中的server_id与本机server_id一致,于是将之丢弃掉了。

g,配置复制通道指向我们要重放的binlog:
change master to master_host='localhost',master_port=3300,MASTER_AUTO_POSITION=0,RELAY_LOG_FILE='binlog.000001',RELAY_LOG_POS=4;
这里最少需要指定master_host和RELAY_LOG_FILE两项配置。master_host在这里虽然没什么用,但是mysql的代码中校验了,如果没有设置master_host,在5.7.29版本上会报错,但是在8.0.23版本上没有发现报错。

h,启动sql_thread线程进行事务重放:
start slave sql_thread;

i,修补由于缺失binlog导致的gtid gap。

j,恢复结束后,进行备份。并且修改my.cnf配置文件恢复到原样。

有几点需要说明:
1,需要在停库的状态下copy binlog并且修改binlog index文件;

可能会出现的问题:

1,change master to MASTER_AUTO_POSITION=0,RELAY_LOG_FILE='binlog.000052',RELAY_LOG_POS=21079342;
ERROR 1380 (HY000): Failed initializing relay log position: Could not find target log file mentioned in relay log info in the index file '/home/wf/ZNV_DEMO_0.1_MySQL_5.7.29_2020-12-16-15-09-52/log/relay_log.index' during relay log initialization
这是因为mysql会从@@global.relay_log取文件夹路径,然后与这里RELAY_LOG_FILE配置的值取文件名进行组合后,再去@@global.relay_log_index中取查找,如果找不到的话或者文件权限配置不对mysql不能访问,就会报这个错。抽象出来就是  “ 取目录名称(@@global.relay_log) + 取文件名称(RELAY_LOG_FILE)    存在于   @@global.relay_log_index指向文件的内容“。
这个问题的代码在rpl_rli.cc文件中:
 

bool Relay_log_info::is_group_relay_log_name_invalid(const char **errmsg) {
  DBUG_TRACE;
  const char *errmsg_fmt = nullptr;
  static char errmsg_buff[MYSQL_ERRMSG_SIZE + FN_REFLEN];
  LOG_INFO linfo;

  *errmsg = nullptr;
  if (relay_log.find_log_pos(&linfo, group_relay_log_name, true)) {
    errmsg_fmt =
        "Could not find target log file mentioned in "
        "relay log info in the index file '%s' during "
        "relay log initialization";
    sprintf(errmsg_buff, errmsg_fmt, relay_log.get_index_fname());
    *errmsg = errmsg_buff;
    return true;
  }
  return false;
}

2,协调器读取了全部的事务,但是没有在数据库上执行
这个问题是数据库的server_id于binlog中的server_id相同了,mysql认为这个binlog是自己产生的,为了避免环形复制导致的无限复制问题,因此将事务跳过了。可以通过设置不同的server_id或者使用--replicate-same-server-id进行起库。
这个问题具体的代码在log_event.cc、log_event.h两个文件中enum_skip_reason do_shall_skip(Relay_log_info *) 方法,这个方法有多个重载版本。感兴趣可以查看。这里列一个出来:
 

#if defined(MYSQL_SERVER)
  int do_update_pos(Relay_log_info *rli) override;
  enum_skip_reason do_shall_skip(Relay_log_info *) override {
    /*
      Events from ourself should be skipped, but they should not
      decrease the slave skip counter.
     */
    if (this->server_id == ::server_id)
      return Log_event::EVENT_SKIP_IGNORE;
    else
      return Log_event::EVENT_SKIP_NOT;
  }
#endif
};

3,上面说了,中间缺失了一个binlog文件,那么,在日志应用过程中就可能会因为各种各样的错误而中断。如果使用MTS的话,通过slave_exec_mode=IDEMPOTENT配置可以忽略掉重复key或者key不存在错误。对于其它错误,仍然需要手动处理。

  • 1
    点赞
  • 0
    评论
  • 1
    收藏
  • 打赏
    打赏
  • 扫一扫,分享海报

参与评论 您还未登录,请先 登录 后发表或查看评论
©️2022 CSDN 皮肤主题:大白 设计师:CSDN官方博客 返回首页

打赏作者

技术菜逼

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值