1. 程式人生 > 其它 >從原始碼分析 XtraBackup 的備份原理

從原始碼分析 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函式。

intmain(intargc,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:datadirmustbespecified.\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("completedOK!\n");

exit(EXIT_SUCCESS);
}

下面重點看看xtrabackup_backup_func函式的處理邏輯。

xtrabackup_backup_func

該函式同樣位於xtrabackup.cc檔案中。

void
xtrabackup_backup_func(void)
{
...
/*startbackgroundthreadtocopynewerlog*/
/*建立redolog拷貝執行緒*/
os_thread_id_tlog_copying_thread_id;
datafiles_iter_t*it;
...
/*getcurrentcheckpoint_lsn*/
/*Lookforthelatestcheckpointfromanyoftheloggroups*/
/*獲取最新的checkpointlsn*/
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);
...
/*copylogfilebycurrentposition*/
/*從最新的checkpointlsn開始拷貝redolog*/
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);

/*Populatefil_systemwithtablespacestocopy*/
/*獲取ibdata1,undotablespaces及所有的ibd檔案*/
err=xb_load_tablespaces();
if(err!=DB_SUCCESS){
msg("xtrabackup:error:xb_load_tablespaces()failedwith"
"errorcode%lu\n",err);
exit(EXIT_FAILURE);
}
...
/*Createdatacopyingthreads*/
/*建立資料拷貝執行緒*/
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);
}

/*迴圈等待,直到拷貝結束*/
/*Waitforthreadstoexit*/
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("Debugsleepfor%useconds\n",
opt_debug_sleep_before_unlock);
os_thread_sleep(opt_debug_sleep_before_unlock*1000000);
}

/*讀取最新的checkpointlsn,用於後續的增量備份*/
/*readthelatestcheckpointlsn*/
latest_cp=0;
{
log_group_t*max_cp_group;
ulintmax_cp_field;
ulinterr;

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);
gotoskip_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:Thelatestcheckpoint(forincremental):"
"'"LSN_PF"'\n",latest_cp);
}
skip_last_cp:
/*停止redolog拷貝執行緒.將備份的元資料資訊記錄在XTRABACKUP_METADATA_FILENAME中,即xtrabackup_checkpoints*/
/*stoplog_copying_thread*/
log_copying=FALSE;
os_event_set(log_copying_stop);
msg("xtrabackup:Stoppinglogcopyingthread.\n");
while(log_copying_running){
msg(".");
os_thread_sleep(200000);/*0.2sec*/
}
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:failedtostreammetadata.\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);
}

//Thereisnoneedtostopslavethreadbeforecopingnon-Innodbdatawhen
//--no-lockoptionisusedbecause--no-lockoptionrequiresthatnoDDLor
//DMLtonon-transactiontablescanoccur.
if(opt_no_lock){
if(opt_safe_slave_backup){
if(!wait_for_safe_slave(mysql_connection)){
return(false);
}
}
}
/*如果設定了--slave-info,會將SHOWSLAVESTATUS的相關資訊,記錄在xtrabackup_slave_info中*/
if(opt_slave_info){
/*如果之前使用了備份鎖,這裡會先鎖定Binlog(LOCKBINLOGFORBACKUP)*/
lock_binlog_maybe(mysql_connection,opt_backup_lock_timeout,
opt_backup_lock_retry_count);

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

/*TheonlyreasonwhyGalera/binloginfoiswrittenbefore
wait_for_ibbackup_log_copy_finish()isthatafterthatcallthextrabackup
binarywillstartstreamigatemporarycopyofREDOlogtostdoutand
thus,anystreamingfrominnobackupexwouldinterfere.Theonlywayto
avoidthatistohaveasingleprocess,i.e.mergeinnobackupexand
xtrabackup.*/
if(opt_galera_info){
if(!write_galera_info(mysql_connection)){
return(false);
}
write_current_binlog_file(mysql_connection);
}

/*如果--binlog-info設定的是ON(預設是AUTO),則會將SHOWMASTERSTATUS的相關資訊,記錄在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("ExecutingFLUSHNO_WRITE_TO_BINLOGENGINELOGS...\n");
xb_mysql_query(mysql_connection,
"FLUSHNO_WRITE_TO_BINLOGENGINELOGS",false);
}

return(true);
}

該函式的處理流程如下:

  1. 呼叫lock_tables_maybe函式加全域性讀鎖。lock_tables_maybe函式的處理邏輯會在下篇文章介紹。

  2. 呼叫backup_files函式備份非ibd檔案。具體來說,會備份以下面這些關鍵字作為字尾的檔案。

    constchar*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()
{
/*releasealllocks*/
/*釋放所有鎖,如果鎖定了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("StartingslaveSQLthread\n");
xb_mysql_query(mysql_connection,
"STARTSLAVESQL_THREAD",false);
}

/*CopybufferpooldumporLRUdump*/
/*拷貝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)){
constchar*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("Backupcreatedindirectory'%s'\n",xtrabackup_target_dir);
if(mysql_binlog_position!=NULL){
msg("MySQLbinlogposition:%s\n",mysql_binlog_position);
}
if(!mysql_slave_position.empty()&&opt_slave_info){
msg("MySQLslavebinlogposition:%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的備份流程如下圖所示。