• 从源码分析 XtraBackup 的备份原理


    MySQL物理备份工具,常用的有两个:MySQL Enterprise Backup 和 XtraBackup。

    前者常用于MySQL企业版,后者常用于MySQL社区版、Percona Server for MySQL 和 MariaDB。

    所以,如果我们使用的是后三者,在实例较大的情况下,一般都会选择XtraBackup作为备份恢复工具。

    熟悉一个工具,不仅仅是要了解它的用法,更重要的是掌握用法背后的原理。毕竟,用法只是“术”,原理才是“道”。所谓,明道才能优术。

    了解XtraBackup的原理,比较经典的一篇文章是淘宝数据库内核日报的《Percona XtraBackup 备份原理

    但看文章始终有隔靴搔痒之感,而且很多细节性的东西文章也不会提到,譬如我们比较关心的全局读锁。

    下面我们就从源码的角度看看XtraBackup的备份原理,主要包括两部分:

    1. XtraBackup的备份流程。
    2. XtraBackup中全局读锁的加锁逻辑。因篇幅较长,这一部分会放到下篇文章介绍。

    分析版本:XtraBackup 2.4.24

     

    XtraBackup的备份流程

    XtraBackup的main函数定义在 storage/innobase/xtrabackup/src/xtrabackup.cc 文件中。

    可以看到,对于--backup选项,会调用xtrabackup_backup_func函数。

    int main(int argc, char **argv)
    {
        ...
     /* --backup */
     if (xtrabackup_backup) {
      xtrabackup_backup_func();
     }

     /* --stats */
     if (xtrabackup_stats) {
      xtrabackup_stats_func(server_argc, server_defaults);
     }

     /* --prepare */
     if (xtrabackup_prepare) {
      xtrabackup_prepare_func(server_argc, server_defaults);
     }

     if (xtrabackup_copy_back || xtrabackup_move_back) {
      if (!check_if_param_set("datadir")) {
       msg("Error: datadir must be specified.\n");
       exit(EXIT_FAILURE);
      }
      mysql_mutex_init(key_LOCK_keyring_operations,
         &LOCK_keyring_operations, MY_MUTEX_INIT_FAST);
      if (!copy_back(server_argc, server_defaults)) {
       exit(EXIT_FAILURE);
      }
      mysql_mutex_destroy(&LOCK_keyring_operations);
     }
        ...
     msg_ts("completed OK!\n");

     exit(EXIT_SUCCESS);
    }

    下面重点看看xtrabackup_backup_func函数的处理逻辑。

    xtrabackup_backup_func

    该函数同样位于xtrabackup.cc文件中。

    void
    xtrabackup_backup_func(void)
    {
        ...
     /* start back ground thread to copy newer log */
     /* 创建redo log拷贝线程 */
     os_thread_id_t log_copying_thread_id;
     datafiles_iter_t *it;
        ...
     /* get current checkpoint_lsn */
     /* Look for the latest checkpoint from any of the log groups */
     /* 获取最新的checkpoint lsn */
     mutex_enter(&log_sys->mutex);

     err = recv_find_max_checkpoint(&max_cp_group, &max_cp_field);

     if (err != DB_SUCCESS) {

      ut_free(log_hdr_buf_);
      exit(EXIT_FAILURE);
     }

     log_group_header_read(max_cp_group, max_cp_field);
     buf = log_sys->checkpoint_buf;

     checkpoint_lsn_start = mach_read_from_8(buf + LOG_CHECKPOINT_LSN);
     checkpoint_no_start = mach_read_from_8(buf + LOG_CHECKPOINT_NO);
        ...  
     /* copy log file by current position */
     /* 从最新的checkpoint lsn开始拷贝redo log */
     if(xtrabackup_copy_logfile(checkpoint_lsn_start, FALSE))
      exit(EXIT_FAILURE);

     mdl_taken = true;

     log_copying_stop = os_event_create("log_copying_stop");
     debug_sync_point("xtrabackup_pause_after_redo_catchup");
     os_thread_create(log_copying_thread, NULL, &log_copying_thread_id);

     /* Populate fil_system with tablespaces to copy */
     /* 获取ibdata1,undo tablespaces及所有的ibd文件 */
     err = xb_load_tablespaces();
     if (err != DB_SUCCESS) {
      msg("xtrabackup: error: xb_load_tablespaces() failed with"
          "error code %lu\n", err);
      exit(EXIT_FAILURE);
     }
        ...
     /* Create data copying threads */
     /* 创建数据拷贝线程 */
     data_threads = (data_thread_ctxt_t *)
      ut_malloc_nokey(sizeof(data_thread_ctxt_t) *
                                    xtrabackup_parallel);
     count = xtrabackup_parallel;
     mutex_create(LATCH_ID_XTRA_COUNT_MUTEX, &count_mutex);
     
     /* 拷贝物理文件,其中,xtrabackup_parallel是拷贝并发线程数,由--parallel参数指定 */
     for (i = 0; i < (uint) xtrabackup_parallel; i++) {
      data_threads[i].it = it;
      data_threads[i].num = i+1;
      data_threads[i].count = &count;
      data_threads[i].count_mutex = &count_mutex;
      data_threads[i].error = &data_copying_error;
      os_thread_create(data_copy_thread_func, data_threads + i,
         &data_threads[i].id);
     }
        
     /* 循环等待,直到拷贝结束 */
     /* Wait for threads to exit */
     while (1) {
      os_thread_sleep(1000000);
      mutex_enter(&count_mutex);
      if (count == 0) {
       mutex_exit(&count_mutex);
       break;
      }
      mutex_exit(&count_mutex);
     }

     mutex_free(&count_mutex);
     ut_free(data_threads);
     datafiles_iter_free(it);

     if (data_copying_error) {
      exit(EXIT_FAILURE);
     }

     if (changed_page_bitmap) {
      xb_page_bitmap_deinit(changed_page_bitmap);
     }
     }
     
     /* 调用backup_start函数,这个函数会加全局读锁,拷贝非ibd文件 */
     if (!backup_start()) {
      exit(EXIT_FAILURE);
     }
     if(opt_lock_ddl_per_table && opt_debug_sleep_before_unlock){
      msg_ts("Debug sleep for %u seconds\n",
             opt_debug_sleep_before_unlock);
      os_thread_sleep(opt_debug_sleep_before_unlock * 1000000);
     }
     
     /* 读取最新的checkpoint lsn,用于后续的增量备份 */
     /* read the latest checkpoint lsn */
     latest_cp = 0;
     {
      log_group_t* max_cp_group;
      ulint max_cp_field;
      ulint err;

      mutex_enter(&log_sys->mutex);

      err = recv_find_max_checkpoint(&max_cp_group, &max_cp_field);

      if (err != DB_SUCCESS) {
       msg("xtrabackup: Error: recv_find_max_checkpoint() failed.\n");
       mutex_exit(&log_sys->mutex);
       goto skip_last_cp;
      }

      log_group_header_read(max_cp_group, max_cp_field);

      xtrabackup_choose_lsn_offset(checkpoint_lsn_start);

      latest_cp = mach_read_from_8(log_sys->checkpoint_buf +
              LOG_CHECKPOINT_LSN);

      mutex_exit(&log_sys->mutex);

      msg("xtrabackup: The latest check point (for incremental): "
          "'" LSN_PF "'\n", latest_cp);
     }
    skip_last_cp:
     /* 停止redo log拷贝线程. 将备份的元数据信息记录在XTRABACKUP_METADATA_FILENAME中,即xtrabackup_checkpoints */
     /* stop log_copying_thread */
     log_copying = FALSE;
     os_event_set(log_copying_stop);
     msg("xtrabackup: Stopping log copying thread.\n");
     while (log_copying_running) {
      msg(".");
      os_thread_sleep(200000); /*0.2 sec*/
     }
     msg("\n");

     os_event_destroy(log_copying_stop);
     if (ds_close(dst_log_file)) {
      exit(EXIT_FAILURE);
     }

     if (!validate_missing_encryption_tablespaces()) {
      exit(EXIT_FAILURE);
     }


     if(!xtrabackup_incremental) {
      strcpy(metadata_type, "full-backuped");
      metadata_from_lsn = 0;
     } else {
      strcpy(metadata_type, "incremental");
      metadata_from_lsn = incremental_lsn;
     }
     metadata_to_lsn = latest_cp;
     metadata_last_lsn = log_copy_scanned_lsn;

     if (!xtrabackup_stream_metadata(ds_meta)) {
      msg("xtrabackup: Error: failed to stream metadata.\n");
      exit(EXIT_FAILURE);
     }
     
     /* 调用backup_finish函数,这个函数会释放全局读锁 */
     if (!backup_finish()) {
      exit(EXIT_FAILURE);
     }
        ...
    }

    该函数的处理流程如下:

    1. 创建redo log拷贝线程,从最近的checkpoint lsn开始拷贝redo log。
    2. 创建数据文件拷贝线程,拷贝ibdata1,undo tablespaces及所有的ibd文件。这里可通过设置--parallel进行多线程备份,提高物理文件的拷贝效率。不设置则默认为1。
    3. ibd文件拷贝完成后,调用backup_start函数。
    4. 停止redo log拷贝线程。
    5. 调用backup_finish函数。

    接下来重点看看backup_start和backup_finish这两个函数的实现逻辑。

    backup_start

    该函数位于backup_copy.cc文件中。

    bool
    backup_start()
    {
     /* opt_no_lock指的是--no-lock参数 */
     if (!opt_no_lock) {
     /* 如果指定了--safe-slave-backup,会关闭SQL线程,等待Slave_open_temp_tables变量为0。
        如果使用的是statement格式,且使用了临时表,建议设置--safe-slave-backup。
        对于row格式,无需指定该选项 */
      if (opt_safe_slave_backup) {
       if (!wait_for_safe_slave(mysql_connection)) {
        return(false);
       }
      }
      /* 调用backup_files函数备份非ibd文件,加了全局读锁还会调用一次。
         这一次,实际上针对的是--rsync方式 */
      if (!backup_files(fil_path_to_mysql_datadir, true)) {
       return(false);
      }

      history_lock_time = time(NULL);
      /* 加全局读锁,如果支持备份锁,且没有设置--no-backup-locks,会优先使用备份锁 */
      if (!lock_tables_maybe(mysql_connection,
               opt_backup_lock_timeout,
               opt_backup_lock_retry_count)) {
       return(false);
      }
     }
     /* 备份非ibd文件 */
     if (!backup_files(fil_path_to_mysql_datadir, false)) {
      return(false);
     }

     // There is no need to stop slave thread before coping non-Innodb data when
     // --no-lock option is used because --no-lock option requires that no DDL or
     // DML to non-transaction tables can occur.
     if (opt_no_lock) {
      if (opt_safe_slave_backup) {
       if (!wait_for_safe_slave(mysql_connection)) {
        return(false);
       }
      }
     }
     /* 如果设置了--slave-info,会将SHOW SLAVE STATUS的相关信息,记录在xtrabackup_slave_info中 */
     if (opt_slave_info) {
      /* 如果之前使用了备份锁,这里会先锁定Binlog(LOCK BINLOG FOR BACKUP)*/
      lock_binlog_maybe(mysql_connection, opt_backup_lock_timeout,
          opt_backup_lock_retry_count);

      if (!write_slave_info(mysql_connection)) {
       return(false);
      }
     }

     /* The only reason why Galera/binlog info is written before
     wait_for_ibbackup_log_copy_finish() is that after that call the xtrabackup
     binary will start streamig a temporary copy of REDO log to stdout and
     thus, any streaming from innobackupex would interfere. The only way to
     avoid that is to have a single process, i.e. merge innobackupex and
     xtrabackup. */
     if (opt_galera_info) {
      if (!write_galera_info(mysql_connection)) {
       return(false);
      }
      write_current_binlog_file(mysql_connection);
     }
        
     /* 如果--binlog-info设置的是ON(默认是AUTO),则会将SHOW MASTER STATUS的相关信息,记录在xtrabackup_binlog_info中 */
     if (opt_binlog_info == BINLOG_INFO_ON) {
      lock_binlog_maybe(mysql_connection, opt_backup_lock_timeout,
          opt_backup_lock_retry_count);
      write_binlog_info(mysql_connection);
     }

     if (have_flush_engine_logs) {
      msg_ts("Executing FLUSH NO_WRITE_TO_BINLOG ENGINE LOGS...\n");
      xb_mysql_query(mysql_connection,
       "FLUSH NO_WRITE_TO_BINLOG ENGINE LOGS", false);
     }

     return(true);
    }

    该函数的处理流程如下:

    1. 调用lock_tables_maybe函数加全局读锁。lock_tables_maybe函数的处理逻辑会在下篇文章介绍。

    2. 调用backup_files函数备份非ibd文件。具体来说,会备份以下面这些关键字作为后缀的文件。

      const char *ext_list[] = {"frm", "isl", "MYD", "MYI", "MAD", "MAI",
                      "MRG", "TRG", "TRN", "ARM", "ARZ", "CSM", "CSV", "opt", "par",
                      NULL};
    3. 如果命令行中指定了 --slave-info ,则会执行 SHOW SLAVE STATUS 获取复制的相关信息。

    4. 如果命令行中指定了 --binlog-info ,则会执行 SHOW MASTER STATU 获取 Binlog 的位置点信息。binlog-info无需显式指定,因为它的默认值为AUTO,如果开启了Binlog,则为ON。

    backup_finish

    该函数位于backup_copy.cc文件中。

    bool
    backup_finish()
    {
     /* release all locks */
     /* 释放所有锁,如果锁定了Binlog,还会解锁Binlog */
     if (!opt_no_lock) {
      unlock_all(mysql_connection);
      history_lock_time = time(NULL) - history_lock_time;
     } else {
      history_lock_time = 0;
     }
      /* 如果设置了--safe-slave-backup,且SQL线程停止了,会开启SQL线程 */
     if (opt_safe_slave_backup && sql_thread_started) {
      msg("Starting slave SQL thread\n");
      xb_mysql_query(mysql_connection,
        "START SLAVE SQL_THREAD", false);
     }

     /* Copy buffer pool dump or LRU dump */
     /* 拷贝ib_buffer_pool和ib_lru_dump文件 */
     if (!opt_rsync) {
      if (opt_dump_innodb_buffer_pool) {
       check_dump_innodb_buffer_pool(mysql_connection);
      }

      if (buffer_pool_filename && file_exists(buffer_pool_filename)) {
       const char *dst_name;

       dst_name = trim_dotslash(buffer_pool_filename);
       copy_file(ds_data, buffer_pool_filename, dst_name, 0);
      }
      if (file_exists("ib_lru_dump")) {
       copy_file(ds_data, "ib_lru_dump", "ib_lru_dump", 0);
      }
      if (file_exists("ddl_log.log")) {
       copy_file(ds_data, "ddl_log.log", "ddl_log.log", 0);
      }
     }

     msg_ts("Backup created in directory '%s'\n", xtrabackup_target_dir);
     if (mysql_binlog_position != NULL) {
      msg("MySQL binlog position: %s\n", mysql_binlog_position);
     }
     if (!mysql_slave_position.empty() && opt_slave_info) {
      msg("MySQL slave binlog position: %s\n",
       mysql_slave_position.c_str());
     }
    /* 生成配置文件,backup-my.cnf */
     if (!write_backup_config_file()) {
      return(false);
     }
     
    /* 将备份的相关信息记录在xtrabackup_info文件中 */
     if (!write_xtrabackup_info(mysql_connection)) {
      return(false);
     }

     return(true);
    }

    该函数的处理流程如下:

    1. 释放全局读锁。
    2. 拷贝ib_buffer_pool和ib_lru_dump文件。
    3. 将备份的相关信息记录在xtrabackup_info文件中。如果设置了--history ,还会将备份信息记录在 PERCONA_SCHEMA库下的xtrabackup_history表中。

    总结

    综合上面的分析,XtraBackup的备份流程如下图所示。

     

  • 相关阅读:
    LeetCode 152. 乘积最大子数组
    LeetCode 148. 排序链表
    LeetCode 143. 重排链表
    LeetCode 142. 环形链表 II
    LeetCode 137. 只出现一次的数字 II
    LeetCode 127. 单词接龙
    LeetCode 120. 三角形最小路径和
    spring boot redis 数据库缓存用法
    堪称神器的Chrome插件
    rocketMQ安装中遇到的坑
  • 原文地址:https://www.cnblogs.com/ivictor/p/15547387.html
Copyright © 2020-2023  润新知