MySQL物理备份工具,常用的有两个:MySQL Enterprise Backup 和 XtraBackup。
前者常用于MySQL企业版,后者常用于MySQL社区版、Percona Server for MySQL 和 MariaDB。
所以,如果我们使用的是后三者,在实例较大的情况下,一般都会选择XtraBackup作为备份恢复工具。
熟悉一个工具,不仅仅是要了解它的用法,更重要的是掌握用法背后的原理。毕竟,用法只是”术”,原理才是”道”。所谓,明道才能优术。
了解XtraBackup的原理,比较经典的一篇文章是淘宝数据库内核日报的《Percona XtraBackup 备份原理》
但看文章始终有隔靴搔痒之感,而且很多细节性的东西文章也不会提到,譬如我们比较关心的全局读锁。
下面我们就从源码的角度看看XtraBackup的备份原理,主要包括两部分:
- XtraBackup的备份流程。
- XtraBackup中全局读锁的加锁逻辑。因篇幅较长,这一部分会放到下篇文章介绍。
分析版本:XtraBackup 2.4.24
XtraBackup的备份流程
XtraBackup的main函数定义在 storage/innobase/xtrabackup/src/xtrabackup.cc 文件中。
可以看到,对于–backup选项,会调用xtrabackup_backup_func函数。
int main(int argc, char **argv)<br>{<br>    ...<br> /* --backup */<br> if (xtrabackup_backup) {<br>  xtrabackup_backup_func();<br> }<br><br> /* --stats */<br> if (xtrabackup_stats) {<br>  xtrabackup_stats_func(server_argc, server_defaults);<br> }<br><br> /* --prepare */<br> if (xtrabackup_prepare) {<br>  xtrabackup_prepare_func(server_argc, server_defaults);<br> }<br><br> if (xtrabackup_copy_back || xtrabackup_move_back) {<br>  if (!check_if_param_set("datadir")) {<br>   msg("Error: datadir must be specified.\n");<br>   exit(EXIT_FAILURE);<br>  }<br>  mysql_mutex_init(key_LOCK_keyring_operations,<br>     &LOCK_keyring_operations, MY_MUTEX_INIT_FAST);<br>  if (!copy_back(server_argc, server_defaults)) {<br>   exit(EXIT_FAILURE);<br>  }<br>  mysql_mutex_destroy(&LOCK_keyring_operations);<br> }<br>    ...<br> msg_ts("completed OK!\n");<br><br> exit(EXIT_SUCCESS);<br>}<br>
下面重点看看xtrabackup_backup_func函数的处理逻辑。
xtrabackup_backup_func
该函数同样位于xtrabackup.cc文件中。
void<br>xtrabackup_backup_func(void)<br>{<br>    ...<br> /* start back ground thread to copy newer log */<br> /* 创建redo log拷贝线程 */<br> os_thread_id_t log_copying_thread_id;<br> datafiles_iter_t *it;<br>    ...<br> /* get current checkpoint_lsn */<br> /* Look for the latest checkpoint from any of the log groups */<br> /* 获取最新的checkpoint lsn */<br> mutex_enter(&log_sys->mutex);<br><br> err = recv_find_max_checkpoint(&max_cp_group, &max_cp_field);<br><br> if (err != DB_SUCCESS) {<br><br>  ut_free(log_hdr_buf_);<br>  exit(EXIT_FAILURE);<br> }<br><br> log_group_header_read(max_cp_group, max_cp_field);<br> buf = log_sys->checkpoint_buf;<br><br> checkpoint_lsn_start = mach_read_from_8(buf + LOG_CHECKPOINT_LSN);<br> checkpoint_no_start = mach_read_from_8(buf + LOG_CHECKPOINT_NO);<br>    ...  <br> /* copy log file by current position */<br> /* 从最新的checkpoint lsn开始拷贝redo log */<br> if(xtrabackup_copy_logfile(checkpoint_lsn_start, FALSE))<br>  exit(EXIT_FAILURE);<br><br> mdl_taken = true;<br><br> log_copying_stop = os_event_create("log_copying_stop");<br> debug_sync_point("xtrabackup_pause_after_redo_catchup");<br> os_thread_create(log_copying_thread, NULL, &log_copying_thread_id);<br><br> /* Populate fil_system with tablespaces to copy */<br> /* 获取ibdata1,undo tablespaces及所有的ibd文件 */<br> err = xb_load_tablespaces();<br> if (err != DB_SUCCESS) {<br>  msg("xtrabackup: error: xb_load_tablespaces() failed with"<br>      "error code %lu\n", err);<br>  exit(EXIT_FAILURE);<br> }<br>    ...<br> /* Create data copying threads */<br> /* 创建数据拷贝线程 */<br> data_threads = (data_thread_ctxt_t *)<br>  ut_malloc_nokey(sizeof(data_thread_ctxt_t) *<br>                                xtrabackup_parallel);<br> count = xtrabackup_parallel;<br> mutex_create(LATCH_ID_XTRA_COUNT_MUTEX, &count_mutex);<br> <br> /* 拷贝物理文件,其中,xtrabackup_parallel是拷贝并发线程数,由--parallel参数指定 */<br> for (i = 0; i < (uint) xtrabackup_parallel; i++) {<br>  data_threads[i].it = it;<br>  data_threads[i].num = i+1;<br>  data_threads[i].count = &count;<br>  data_threads[i].count_mutex = &count_mutex;<br>  data_threads[i].error = &data_copying_error;<br>  os_thread_create(data_copy_thread_func, data_threads + i,<br>     &data_threads[i].id);<br> }<br>    <br> /* 循环等待,直到拷贝结束 */<br> /* Wait for threads to exit */<br> while (1) {<br>  os_thread_sleep(1000000);<br>  mutex_enter(&count_mutex);<br>  if (count == 0) {<br>   mutex_exit(&count_mutex);<br>   break;<br>  }<br>  mutex_exit(&count_mutex);<br> }<br><br> mutex_free(&count_mutex);<br> ut_free(data_threads);<br> datafiles_iter_free(it);<br><br> if (data_copying_error) {<br>  exit(EXIT_FAILURE);<br> }<br><br> if (changed_page_bitmap) {<br>  xb_page_bitmap_deinit(changed_page_bitmap);<br> }<br> }<br> <br> /* 调用backup_start函数,这个函数会加全局读锁,拷贝非ibd文件 */<br> if (!backup_start()) {<br>  exit(EXIT_FAILURE);<br> }<br> if(opt_lock_ddl_per_table && opt_debug_sleep_before_unlock){<br>  msg_ts("Debug sleep for %u seconds\n",<br>         opt_debug_sleep_before_unlock);<br>  os_thread_sleep(opt_debug_sleep_before_unlock * 1000000);<br> }<br> <br> /* 读取最新的checkpoint lsn,用于后续的增量备份 */<br> /* read the latest checkpoint lsn */<br> latest_cp = 0;<br> {<br>  log_group_t* max_cp_group;<br>  ulint max_cp_field;<br>  ulint err;<br><br>  mutex_enter(&log_sys->mutex);<br><br>  err = recv_find_max_checkpoint(&max_cp_group, &max_cp_field);<br><br>  if (err != DB_SUCCESS) {<br>   msg("xtrabackup: Error: recv_find_max_checkpoint() failed.\n");<br>   mutex_exit(&log_sys->mutex);<br>   goto skip_last_cp;<br>  }<br><br>  log_group_header_read(max_cp_group, max_cp_field);<br><br>  xtrabackup_choose_lsn_offset(checkpoint_lsn_start);<br><br>  latest_cp = mach_read_from_8(log_sys->checkpoint_buf +<br>          LOG_CHECKPOINT_LSN);<br><br>  mutex_exit(&log_sys->mutex);<br><br>  msg("xtrabackup: The latest check point (for incremental): "<br>      "'" LSN_PF "'\n", latest_cp);<br> }<br>skip_last_cp:<br> /* 停止redo log拷贝线程. 将备份的元数据信息记录在XTRABACKUP_METADATA_FILENAME中,即xtrabackup_checkpoints */<br> /* stop log_copying_thread */<br> log_copying = FALSE;<br> os_event_set(log_copying_stop);<br> msg("xtrabackup: Stopping log copying thread.\n");<br> while (log_copying_running) {<br>  msg(".");<br>  os_thread_sleep(200000); /*0.2 sec*/<br> }<br> msg("\n");<br><br> os_event_destroy(log_copying_stop);<br> if (ds_close(dst_log_file)) {<br>  exit(EXIT_FAILURE);<br> }<br><br> if (!validate_missing_encryption_tablespaces()) {<br>  exit(EXIT_FAILURE);<br> }<br><br><br> if(!xtrabackup_incremental) {<br>  strcpy(metadata_type, "full-backuped");<br>  metadata_from_lsn = 0;<br> } else {<br>  strcpy(metadata_type, "incremental");<br>  metadata_from_lsn = incremental_lsn;<br> }<br> metadata_to_lsn = latest_cp;<br> metadata_last_lsn = log_copy_scanned_lsn;<br><br> if (!xtrabackup_stream_metadata(ds_meta)) {<br>  msg("xtrabackup: Error: failed to stream metadata.\n");<br>  exit(EXIT_FAILURE);<br> }<br> <br> /* 调用backup_finish函数,这个函数会释放全局读锁 */<br> if (!backup_finish()) {<br>  exit(EXIT_FAILURE);<br> }<br>    ...<br>}<br></ (uint) xtrabackup_parallel; i++) {
该函数的处理流程如下:
- 创建redo log拷贝线程,从最近的checkpoint lsn开始拷贝redo log。
- 创建数据文件拷贝线程,拷贝ibdata1,undo tablespaces及所有的ibd文件。这里可通过设置–parallel进行多线程备份,提高物理文件的拷贝效率。不设置则默认为1。
- ibd文件拷贝完成后,调用backup_start函数。
- 停止redo log拷贝线程。
- 调用backup_finish函数。
接下来重点看看backup_start和backup_finish这两个函数的实现逻辑。
backup_start
该函数位于backup_copy.cc文件中。
bool<br>backup_start()<br>{<br> /* opt_no_lock指的是--no-lock参数 */<br> if (!opt_no_lock) {<br> /* 如果指定了--safe-slave-backup,会关闭SQL线程,等待Slave_open_temp_tables变量为0。<br>    如果使用的是statement格式,且使用了临时表,建议设置--safe-slave-backup。<br>    对于row格式,无需指定该选项 */<br>  if (opt_safe_slave_backup) {<br>   if (!wait_for_safe_slave(mysql_connection)) {<br>    return(false);<br>   }<br>  }<br>  /* 调用backup_files函数备份非ibd文件,加了全局读锁还会调用一次。<br>     这一次,实际上针对的是--rsync方式 */<br>  if (!backup_files(fil_path_to_mysql_datadir, true)) {<br>   return(false);<br>  }<br><br>  history_lock_time = time(NULL);<br>  /* 加全局读锁,如果支持备份锁,且没有设置--no-backup-locks,会优先使用备份锁 */<br>  if (!lock_tables_maybe(mysql_connection,<br>           opt_backup_lock_timeout,<br>           opt_backup_lock_retry_count)) {<br>   return(false);<br>  }<br> }<br> /* 备份非ibd文件 */<br> if (!backup_files(fil_path_to_mysql_datadir, false)) {<br>  return(false);<br> }<br><br> // There is no need to stop slave thread before coping non-Innodb data when<br> // --no-lock option is used because --no-lock option requires that no DDL or<br> // DML to non-transaction tables can occur.<br> if (opt_no_lock) {<br>  if (opt_safe_slave_backup) {<br>   if (!wait_for_safe_slave(mysql_connection)) {<br>    return(false);<br>   }<br>  }<br> }<br> /* 如果设置了--slave-info,会将SHOW SLAVE STATUS的相关信息,记录在xtrabackup_slave_info中 */<br> if (opt_slave_info) {<br>  /* 如果之前使用了备份锁,这里会先锁定Binlog(LOCK BINLOG FOR BACKUP)*/<br>  lock_binlog_maybe(mysql_connection, opt_backup_lock_timeout,<br>      opt_backup_lock_retry_count);<br><br>  if (!write_slave_info(mysql_connection)) {<br>   return(false);<br>  }<br> }<br><br> /* The only reason why Galera/binlog info is written before<br> wait_for_ibbackup_log_copy_finish() is that after that call the xtrabackup<br> binary will start streamig a temporary copy of REDO log to stdout and<br> thus, any streaming from innobackupex would interfere. The only way to<br> avoid that is to have a single process, i.e. merge innobackupex and<br> xtrabackup. */<br> if (opt_galera_info) {<br>  if (!write_galera_info(mysql_connection)) {<br>   return(false);<br>  }<br>  write_current_binlog_file(mysql_connection);<br> }<br>    <br> /* 如果--binlog-info设置的是ON(默认是AUTO),则会将SHOW MASTER STATUS的相关信息,记录在xtrabackup_binlog_info中 */<br> if (opt_binlog_info == BINLOG_INFO_ON) {<br>  lock_binlog_maybe(mysql_connection, opt_backup_lock_timeout,<br>      opt_backup_lock_retry_count);<br>  write_binlog_info(mysql_connection);<br> }<br><br> if (have_flush_engine_logs) {<br>  msg_ts("Executing FLUSH NO_WRITE_TO_BINLOG ENGINE LOGS...\n");<br>  xb_mysql_query(mysql_connection,<br>   "FLUSH NO_WRITE_TO_BINLOG ENGINE LOGS", false);<br> }<br><br> return(true);<br>}
该函数的处理流程如下:
- 调用lock_tables_maybe函数加全局读锁。lock_tables_maybe函数的处理逻辑会在下篇文章介绍。
- 调用backup_files函数备份非ibd文件。具体来说,会备份以下面这些关键字作为后缀的文件。
const char *ext_list[] = {"frm", "isl", "MYD", "MYI", "MAD", "MAI",<br>                "MRG", "TRG", "TRN", "ARM", "ARZ", "CSM", "CSV", "opt", "par",<br>                NULL};
- 如果命令行中指定了 –slave-info ,则会执行 SHOW SLAVE STATUS 获取复制的相关信息。
- 如果命令行中指定了 –binlog-info ,则会执行 SHOW MASTER STATU 获取 Binlog 的位置点信息。binlog-info无需显式指定,因为它的默认值为AUTO,如果开启了Binlog,则为ON。
backup_finish
该函数位于backup_copy.cc文件中。
bool<br>backup_finish()<br>{<br> /* release all locks */<br> /* 释放所有锁,如果锁定了Binlog,还会解锁Binlog */<br> if (!opt_no_lock) {<br>  unlock_all(mysql_connection);<br>  history_lock_time = time(NULL) - history_lock_time;<br> } else {<br>  history_lock_time = 0;<br> }<br>  /* 如果设置了--safe-slave-backup,且SQL线程停止了,会开启SQL线程 */<br> if (opt_safe_slave_backup && sql_thread_started) {<br>  msg("Starting slave SQL thread\n");<br>  xb_mysql_query(mysql_connection,<br>    "START SLAVE SQL_THREAD", false);<br> }<br><br> /* Copy buffer pool dump or LRU dump */<br> /* 拷贝ib_buffer_pool和ib_lru_dump文件 */<br> if (!opt_rsync) {<br>  if (opt_dump_innodb_buffer_pool) {<br>   check_dump_innodb_buffer_pool(mysql_connection);<br>  }<br><br>  if (buffer_pool_filename && file_exists(buffer_pool_filename)) {<br>   const char *dst_name;<br><br>   dst_name = trim_dotslash(buffer_pool_filename);<br>   copy_file(ds_data, buffer_pool_filename, dst_name, 0);<br>  }<br>  if (file_exists("ib_lru_dump")) {<br>   copy_file(ds_data, "ib_lru_dump", "ib_lru_dump", 0);<br>  }<br>  if (file_exists("ddl_log.log")) {<br>   copy_file(ds_data, "ddl_log.log", "ddl_log.log", 0);<br>  }<br> }<br><br> msg_ts("Backup created in directory '%s'\n", xtrabackup_target_dir);<br> if (mysql_binlog_position != NULL) {<br>  msg("MySQL binlog position: %s\n", mysql_binlog_position);<br> }<br> if (!mysql_slave_position.empty() && opt_slave_info) {<br>  msg("MySQL slave binlog position: %s\n",<br>   mysql_slave_position.c_str());<br> }<br>/* 生成配置文件,backup-my.cnf */<br> if (!write_backup_config_file()) {<br>  return(false);<br> }<br> <br>/* 将备份的相关信息记录在xtrabackup_info文件中 */<br> if (!write_xtrabackup_info(mysql_connection)) {<br>  return(false);<br> }<br><br> return(true);<br>}
该函数的处理流程如下:
- 释放全局读锁。
- 拷贝ib_buffer_pool和ib_lru_dump文件。
- 将备份的相关信息记录在xtrabackup_info文件中。如果设置了–history ,还会将备份信息记录在 PERCONA_SCHEMA库下的xtrabackup_history表中。
总结
综合上面的分析,XtraBackup的备份流程如下图所示。
Original: https://www.cnblogs.com/ivictor/p/15547387.html
Author: iVictor
Title: 从源码分析 XtraBackup 的备份原理
原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/599399/
转载文章受原作者版权保护。转载请注明原作者出处!