從源碼分析 XtraBackup 的備份流程
MySQL物理備份工具,常用的有兩個:MySQL Enterprise Backup 和 XtraBackup。
前者常用于MySQL企業(yè)版,后者常用于MySQL社區(qū)版、Percona Server for MySQL 和 MariaDB。
所以,如果我們使用的是后三者,在實例較大的情況下,一般都會選擇XtraBackup作為備份恢復(fù)工具。
熟悉一個工具,不僅僅是要了解它的用法,更重要的是掌握用法背后的原理。畢竟,用法只是“術(shù)”,原理才是“道”。所謂,明道才能優(yōu)術(shù)。
了解XtraBackup的原理,比較經(jīng)典的一篇文章是淘寶數(shù)據(jù)庫內(nèi)核日報的《Percona XtraBackup 備份原理》http://mysql.taobao.org/monthly/2016/03/07/
但看文章始終有隔靴搔癢之感,而且很多細(xì)節(jié)性的東西文章也不會提到,譬如我們比較關(guān)心的全局讀鎖。
下面我們就從源碼的角度看看XtraBackup的備份原理,主要包括兩部分:
XtraBackup的備份流程。 XtraBackup中全局讀鎖的加鎖邏輯。因篇幅較長,這一部分會放到下篇文章介紹。
分析版本:XtraBackup 2.4.24
XtraBackup的備份流程
XtraBackup的main函數(shù)定義在 storage/innobase/xtrabackup/src/xtrabackup.cc 文件中。
可以看到,對于--backup選項,會調(diào)用xtrabackup_backup_func函數(shù)。
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函數(shù)的處理邏輯。
xtrabackup_backup_func
該函數(shù)同樣位于xtrabackup.cc文件中。
void
xtrabackup_backup_func(void)
{
????...
?/*?start?back?ground?thread?to?copy?newer?log?*/
?/*?創(chuàng)建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?*/
?/*?創(chuàng)建數(shù)據(jù)拷貝線程?*/
?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是拷貝并發(fā)線程數(shù),由--parallel參數(shù)指定?*/
?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);
?}
????
?/*?循環(huán)等待,直到拷貝結(jié)束?*/
?/*?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);
?}
?}
?
?/*?調(diào)用backup_start函數(shù),這個函數(shù)會加全局讀鎖,拷貝非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,用于后續(xù)的增量備份?*/
?/*?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拷貝線程.?將備份的元數(shù)據(jù)信息記錄在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);
?}
?
?/*?調(diào)用backup_finish函數(shù),這個函數(shù)會釋放全局讀鎖?*/
?if?(!backup_finish())?{
??exit(EXIT_FAILURE);
?}
????...
}
該函數(shù)的處理流程如下:
創(chuàng)建redo log拷貝線程,從最近的checkpoint lsn開始拷貝redo log。 創(chuàng)建數(shù)據(jù)文件拷貝線程,拷貝ibdata1,undo tablespaces及所有的ibd文件。 這里可通過設(shè)置--parallel進(jìn)行多線程備份,提高物理文件的拷貝效率。不設(shè)置則默認(rèn)為1。 ibd文件拷貝完成后,調(diào)用backup_start函數(shù)。 停止redo log拷貝線程。 調(diào)用backup_finish函數(shù)。
接下來重點看看backup_start和backup_finish這兩個函數(shù)的實現(xiàn)邏輯。
backup_start
該函數(shù)位于backup_copy.cc文件中。
bool
backup_start()
{
?/*?opt_no_lock指的是--no-lock參數(shù)?*/
?if?(!opt_no_lock)?{
?/*?如果指定了--safe-slave-backup,會關(guān)閉SQL線程,等待Slave_open_temp_tables變量為0。
????如果使用的是statement格式,且使用了臨時表,建議設(shè)置--safe-slave-backup。
????對于row格式,無需指定該選項?*/
??if?(opt_safe_slave_backup)?{
???if?(!wait_for_safe_slave(mysql_connection))?{
????return(false);
???}
??}
??/*?調(diào)用backup_files函數(shù)備份非ibd文件,加了全局讀鎖還會調(diào)用一次。
?????這一次,實際上針對的是--rsync方式?*/
??if?(!backup_files(fil_path_to_mysql_datadir,?true))?{
???return(false);
??}
??history_lock_time?=?time(NULL);
??/*?加全局讀鎖,如果支持備份鎖,且沒有設(shè)置--no-backup-locks,會優(yōu)先使用備份鎖?*/
??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);
???}
??}
?}
?/*?如果設(shè)置了--slave-info,會將SHOW?SLAVE?STATUS的相關(guān)信息,記錄在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設(shè)置的是ON(默認(rèn)是AUTO),則會將SHOW?MASTER?STATUS的相關(guān)信息,記錄在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);
}
該函數(shù)的處理流程如下:
調(diào)用lock_tables_maybe函數(shù)加全局讀鎖。lock_tables_maybe函數(shù)的處理邏輯會在下篇文章介紹。
調(diào)用backup_files函數(shù)備份非ibd文件。具體來說,會備份以下面這些關(guān)鍵字作為后綴的文件。
const?char?*ext_list[]?=?{"frm",?"isl",?"MYD",?"MYI",?"MAD",?"MAI",
????????????????"MRG",?"TRG",?"TRN",?"ARM",?"ARZ",?"CSM",?"CSV",?"opt",?"par",
????????????????NULL};如果命令行中指定了 --slave-info ,則會執(zhí)行 SHOW SLAVE STATUS 獲取復(fù)制的相關(guān)信息。
如果命令行中指定了 --binlog-info ,則會執(zhí)行 SHOW MASTER STATU 獲取 Binlog 的位置點信息。binlog-info無需顯式指定,因為它的默認(rèn)值為AUTO,如果開啟了Binlog,則為ON。
backup_finish
該函數(shù)位于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;
?}
??/*?如果設(shè)置了--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);
?}
?
/*?將備份的相關(guān)信息記錄在xtrabackup_info文件中?*/
?if?(!write_xtrabackup_info(mysql_connection))?{
??return(false);
?}
?return(true);
}
該函數(shù)的處理流程如下:
釋放全局讀鎖。 拷貝ib_buffer_pool和ib_lru_dump文件。 將備份的相關(guān)信息記錄在xtrabackup_info文件中。 如果設(shè)置了--history ,還會將備份信息記錄在 PERCONA_SCHEMA庫下的xtrabackup_history表中。
總結(jié)
綜合上面的分析,XtraBackup的備份流程如下圖所示。

