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)
{
...
/* --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);
}
...
}
該函式的處理流程如下:
- 建立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
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);
}
該函式的處理流程如下:
-
呼叫lock_tables_maybe函式加全域性讀鎖。lock_tables_maybe函式的處理邏輯會在下篇文章介紹。
-
呼叫backup_files函式備份非ibd檔案。具體來說,會備份以下面這些關鍵字作為字尾的檔案。
const char *ext_list[] = {"frm", "isl", "MYD", "MYI", "MAD", "MAI",
"MRG", "TRG", "TRN", "ARM", "ARZ", "CSM", "CSV", "opt", "par",
NULL}; -
如果命令列中指定了 --slave-info ,則會執行 SHOW SLAVE STATUS 獲取複製的相關資訊。
-
如果命令列中指定了 --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);
}
該函式的處理流程如下:
- 釋放全域性讀鎖。
- 拷貝ib_buffer_pool和ib_lru_dump檔案。
- 將備份的相關資訊記錄在xtrabackup_info檔案中。如果設定了--history ,還會將備份資訊記錄在 PERCONA_SCHEMA庫下的xtrabackup_history表中。
總結
綜合上面的分析,XtraBackup的備份流程如下圖所示。