關於數據庫的各種備份與還原姿勢詳解

數據庫的冷備份與熱備份

數據導出不完全等於數據備份:

  • 數據導出是指將數據庫中的數據逆向成SQL語句進行導出,所以導出的是SQL文件。通常用作把數據從一個系統遷移到另一個系統,目的是屏蔽系統之間的差異性
  • 數據備份是指將數據庫中數據存儲的相關文件進行拷貝,用於保存一個數據庫的全部物理數據,所以備份後的數據與原本數據在細節及狀態上都是完全一致的。不會像SQL那樣在使用了一些函數的情況下,可能會在不同的時間點或不同的系統上產生不一樣的結果

冷備份與熱備份:

  • 冷備份:在數據庫已經關閉的情況下,對數據的備份稱作冷備份
  • 熱備份:與冷備份相反,在數據庫節點不停機的狀態下進行的備份被稱作熱備份

冷備份的限制:

  • 數據庫必須停機備份,這對一些線上數據庫是無法接受的
  • 備份的數據文件非常佔用存儲空間,並且不支持增量備份
  • 冷備份是備份所有的數據文件和日誌文件,所以無法單獨備份某個邏輯庫和數據表

聯機冷備份:

單節點的數據庫在冷備份時需要停機,這就會對業務系統產生影響。爲了解決這個問題,我們可以組建集羣然後挑選集羣中的一個節點進行停機冷備份。由於集羣中還有其他節點在運行,所以不必擔心影響正在運行的系統。等備份結束之後再啓動該節點,這樣就能解決停機備份帶來的影響

熱備份的限制:

  • 數據庫在熱備份的時候會全局加讀鎖,備份期間節點只能讀取數據不能寫入數據

聯機熱備份:

同樣的方式,爲了避免全局加鎖,我們可以挑選集羣中的一個節點與其他節點解除數據同步關係後進行熱備份。等待備份完成後,再去恢復與集羣中其他節點的數據同步關係。這樣在備份過程中就只有該節點會加讀鎖,其他節點不會受到影響

聯機熱備份與聯機冷備份該如何選擇:

建議選擇聯機熱備份,因爲熱備份可以選擇全量備份或增量備份。而冷備份只能選擇全量備份,當後期數據量很大的時候,冷備份需要耗費很多的時間,並且由於是全量備份也需要佔用更多的存儲空間。熱備份只有在第一次備份的時候需要選擇全量備份,後續備份只需要增量備份新數據即可。 因此,熱備份在存儲空間的佔用及備份耗費的時間上都優於冷備份


實踐聯機冷備份

認識MySQL中與數據相關的文件

上一小節提到了數據備份是指將數據庫中數據存儲的相關文件進行拷貝,而這些文件有很多,所以讓我們來簡單認識下MySQL中與數據相關的文件。

首先是組成邏輯庫的文件,在MySQL中一個邏輯庫實際由多個文件組成,其結構如下:
關於數據庫的各種備份與還原姿勢詳解

  • OPT文件:定義字符集和字符集的排序規則,該文件是一個文本文件
  • FRM文件:這是數據表的定義文件,包含了數據表的結構信息。無論數據表使用的什麼存儲引擎,每一個數據表的定義文件一定是FRM文件
  • ISL文件:該文件只有創建了表分區纔會出現,它存儲着表分區數據文件所在的路徑
  • MyISAM:
    • MYD文件:MyISAM的數據文件
    • MYI文件:MyISAM的索引文件
  • InnoDB:
    • IBD文件:InnoDB的索引及數據文件,該文件是一個二進制文件

FRM文件在很多情況下都會被用到,例如數據庫在執行SQL語句之前,先會審查SQL語句中用到的一些字段是否在FRM文件中定義了。或者數據庫在優化SQL語句時,通過FRM文件判斷where子句中的字段是否是主鍵列或索引列等。因爲FRM文件經常會被使用,所以該文件是一個二進制文件

MySQL中的其他文件:

  • auto.cnf文件:該文件存儲MySQL實例的UUID,即server-uuid,在集羣中可以作爲節點的唯一標識
  • grastate.dat文件:該文件保存的是PXC的同步信息
  • gvwstate.dat文件:該文件保存的是PXC集羣中其他節點的信息
  • .pem:該文件存儲的是加解密用的證書和密鑰信息
  • .sock:套接字文件,用於本地連接MySQL
  • .err:錯誤日誌文件,MySQL所有錯誤信息都會保存在該文件中
  • .pid:MySQL進程id文件
  • ib_buffer_pool:InnoDB緩存文件
  • ib_logfile:InnoDB事務日誌(redo)
  • ibdata:InnoDB共享表空間文件
  • logbin:日誌文件(binlog)
  • index:日誌索引文件
  • ibtmp:臨時表空間文件

數據文件的碎片整理

數據文件中的碎片是什麼:

  • 我們都知道向數據表寫入數據,數據文件的體積會增大。但是刪除數據文件中的數據時,數據文件體積並不會減小,而數據被刪除後留下的空白就被稱作爲碎片

MySQL的數據文件一直存在着碎片化問題,MySQL之所以不會自動整理碎片縮減數據文件的體積,是因爲這個整理過程是會鎖表的。如果每刪除一條數據就鎖表整理碎片,那麼勢必會對數據表的讀寫造成很大的影響。不過MySQL在寫入新數據時,會優先將其寫入碎片空間,所以數據文件中的碎片空間對日常運行沒有什麼影響。

但是對於數據庫備份這個場景來說,如果數據文件中存在着大量的碎片,就會導致實際有意義的數據沒多少,而數據文件的體積又很大。這對備份來說就很佔用存儲空間和傳輸帶寬,所以在進行備份之前,需要對數據文件的碎片進行一個整理,儘量縮減數據文件的體積。

MySQL中整理數據文件碎片的SQL語句如下:

alter table ${table_name} engine=InnoDB;

需要注意的是,在執行該SQL之前,記得先將用於備份的數據庫節點的binlog給關掉。避免binlog中記錄了該SQL,導致在節點備份完成恢復到集羣后,其他節點同步了該SQL出現整個集羣鎖表的情況。所以需要註釋掉MySQL配置文件中的以下兩個參數,在備份完成後再打開:

# log_bin
# log_slave_updates

冷備份

在前面介紹了一些前置知識後,本小節就逐步演示一下如何實踐聯機冷備份。我這裏事先準備了一個由三個節點組成的PXC集羣
關於數據庫的各種備份與還原姿勢詳解

首先挑選集羣中的任意一個節點作爲備份節點,然後停止該節點:

[root@PXC-Node3 ~]# systemctl stop mysqld

編輯配置文件,註釋binlog相關參數:

[root@PXC-Node3 ~]# vim /etc/percona-xtradb-cluster.conf.d/mysqld.cnf
[mysqld]
...

#log-bin
#log_slave_updates

然後註釋PXC集羣相關參數:

[root@PXC-Node3 ~]# vim /etc/percona-xtradb-cluster.conf.d/wsrep.cnf
[mysqld]
#wsrep_provider=/usr/lib64/galera3/libgalera_smm.so
#wsrep_cluster_address=gcomm://192.168.190.132,192.168.190.133,192.168.190.134
#wsrep_slave_threads=8
#wsrep_log_conflicts
#wsrep_cluster_name=pxc-cluster
#wsrep_node_name=pxc-node-03
#pxc_strict_mode=DISABLED
#wsrep_sst_method=xtrabackup-v2
#wsrep_node_address=192.168.190.134
#wsrep_sst_auth=admin:Abc_123456

修改完畢後,啓動MySQL服務,此時該節點就退出了PXC集羣:

[root@PXC-Node3 ~]# systemctl start mysqld

該節點的數據文件有1.1個G:

[root@PXC-Node3 ~]# du -h /var/lib/* |grep /var/lib/mysql$
1.1G    /var/lib/mysql
[root@PXC-Node3 ~]# 

本示例中是對test庫進行備份,所以在進行備份之前,需要對test庫下所有的表進行碎片整理。由於表比較多,我這裏編寫了一個簡單的Java程序來實現:

import com.mysql.jdbc.Driver;

import java.sql.*;
import java.util.ArrayList;

/**
 * 數據表碎片整理
 *
 * @author 01
 * @date 2020-01-25
 **/
public class CleanFragments {

    public static void main(String[] args) throws SQLException {
        DriverManager.registerDriver(new Driver());
        String url = "jdbc:mysql://192.168.190.134:3306/test?useSSL=false";
        String username = "admin";
        String password = "Abc_123456";

        try (Connection connection = DriverManager.getConnection(url, username, password);
             PreparedStatement pst = connection.prepareStatement("show tables;")) {
            ResultSet resultSet = pst.executeQuery();

            ArrayList<String> tableNames = new ArrayList<>();
            while (resultSet.next()) {
                tableNames.add(resultSet.getString(1));
            }

            for (String tableName : tableNames) {
                if ("t_range_1".equals(tableName)) {
                    continue;
                }
                System.out.println("整理 " + tableName + " 表碎片...");
                pst.execute("alter table " + tableName + " engine=InnoDB;");
            }
        }
        System.out.println("整理完畢...");
    }
}

碎片整理完成後,停止MySQL服務,因爲冷備份需要停機:

[root@PXC-Node3 ~]# systemctl stop mysqld

然後就可以開始備份了,其實備份的過程也很簡單,沒用到啥高大上的特殊技術,就是使用tar命令將MySQL數據目錄打成一個壓縮包即可。例如我這裏的數據目錄是/var/lib/mysql,所以執行的命令如下:

# 先進入到/var/lib/目錄下
[root@PXC-Node3 ~]# cd /var/lib/
# 然後對數據目錄進行打包,mysql.tar.gz是打包後的文件名,存放在/home目錄下
[root@PXC-Node3 /var/lib]# tar -zcvf /home/mysql.tar.gz ./mysql
  • Tips:數據目錄是由配置文件中的datadir參數定義的

如果創建了表分區,並且將表分區映射到了其他目錄上,那麼就還需要對錶分區進行打包。例如我這裏有兩個表分區,分別映射到了/mnt/p0/data//mnt/p1/data/目錄,所以執行的命令如下:

[root@PXC-Node3 ~]# cd /mnt/
[root@PXC-Node3 /mnt]# tar -zcvf /home/p0.tar.gz ./p0/data/
[root@PXC-Node3 /mnt]# tar -zcvf /home/p1.tar.gz ./p1/data/

到此備份完成後,恢復配置文件中所註釋的配置項,然後重啓節點讓該節點重新加入到PXC集羣中即可。由於沒啥需要特殊說明的,所以這裏就不演示了。


冷還原

演示瞭如何冷備份後,接下來則演示如何將備份文件冷還原到其他的PXC節點上。首先將備份文件傳輸到需要還原的節點上,可以使用scprsync命令在Linux系統之間傳輸文件,如下示例:

[root@PXC-Node3 /home]# scp ./mysql.tar.gz 192.168.190.133:/home/mysql.tar.gz
[root@PXC-Node3 /home]# scp ./p0.tar.gz 192.168.190.133:/home/p0.tar.gz
[root@PXC-Node3 /home]# scp ./p1.tar.gz 192.168.190.133:/home/p1.tar.gz

還原節點接收到的備份文件如下:

[root@PXC-Node2 ~]# cd /home/
[root@PXC-Node2 /home]# ls
mysql.tar.gz  p0.tar.gz  p1.tar.gz
[root@PXC-Node2 /home]#

除此之外還需要進行一些準備工作,因爲備份節點是存在表分區的,並且映射了相應的數據目錄,若還原節點不存在則需要創建。如下示例:

[root@PXC-Node2 ~]# mkdir /mnt/p0/
[root@PXC-Node2 ~]# mkdir /mnt/p1/

由於是冷還原,所以和冷備份一樣也需要先停止還原節點:

[root@PXC-Node2 ~]# systemctl stop mysqld

還原MySQL的數據目錄,命令如下:

# 先備份原本的數據目錄,以防萬一
[root@PXC-Node2 ~]# mv /var/lib/mysql /var/lib/mysql-backup
# 將壓縮文件解壓到/var/lib/目錄下
[root@PXC-Node2 /home]# tar -zxvf mysql.tar.gz -C /var/lib/

然後是還原表分區數據目錄:

[root@PXC-Node2 /home]# tar -zxvf p0.tar.gz -C /mnt/
[root@PXC-Node2 /home]# tar -zxvf p1.tar.gz -C /mnt/

刪除auto.cnf文件,不然uuid重複的話,該節點是無法啓動的:

[root@PXC-Node2 ~]# rm -rf /var/lib/mysql/auto.cnf

如果你是使用PXC集羣中的首節點作爲備份節點,那麼就還需要將grastate.dat文件中的safe_to_bootstrap參數修改爲0,普通節點則不需要。如下示例:

[root@PXC-Node2 ~]# vim /var/lib/mysql/grastate.dat
...

safe_to_bootstrap: 0

到此就算是還原完成了,啓動MySQL服務即可:

[root@PXC-Node2 ~]# systemctl start mysqld

剩下就是自行驗證下該節點的數據是否正確還原了,能否與集羣中其他節點進行數據同步等。以及最後清理掉之前備份的舊數據目錄:

[root@PXC-Node2 ~]# rm -rf /var/lib/mysql-backup

冷備份的實際用途:

  • 當PXC集羣需要增加新節點時,可以利用冷備份來備份現有節點的數據,然後還原到新增的PXC節點中。讓新上線節點具有初始數據,避免在上線後與集羣中的節點進行全量的數據同步,進而觸發PXC集羣的流控機制,導致影響整個集羣的性能

常見的熱備份方案

經過以上小節,現在我們已經瞭解了冷備份和冷還原,從本節開始我們來學習熱備份。熱備份是數據庫運行的狀態下備份數據,也是難度最大的備份。PXC集羣常見的熱備份有LVM和XtraBackup兩種方案。

LVM方案

利用Linux的LVM技術來實現熱備份,將MySQL的數據目錄放到LVM邏輯捲上,然後通過LVM快照技術備份邏輯卷的內容。第一次備份是全量備份,之後的備份都是增量備份。在還原時,將快照中的數據目錄恢復到ySQL的數據目錄即可。

使用LVM這種技術不僅可以備份MySQL還可以備份MongoDB等其他數據庫,但使用LVM做熱備份方案也比較麻煩,因爲需要手動創建邏輯卷、遷移數據目錄、創建快照以及給數據庫加鎖等等,所以LVM並不是常用的熱備份方案。

XtraBackup方案

因爲LVM的麻煩,所以人們都希望使用專業的工具去做熱備份,這個工具就是XtraBackup。XtraBackup是由Percona開源的免費數據庫熱備份工具,它能對InnoDB數據庫和XtraDB存儲引擎的數據庫非阻塞地備份。因爲XtraBackup在備份過程中不會打斷正在執行的事務,而事務日誌中記錄了哪些是備份前寫入的數據哪些是備份後寫入的數據,所以無需加鎖。

另外,XtraBackup提供了對備份數據的壓縮功能,可以節約備份文件佔用的磁盤空間及網絡帶寬。但XtraBackup在備份使用MyISAM作爲存儲引擎的表時會加讀鎖,即表中的數據可讀但不可寫,不過這也不是問題,之前提到了可以使用聯機熱備份的方式來解決加讀鎖的問題。同樣,XtraBackup支持全量備份和增量備份,因爲XtraBackup的方便性,所以一般都是採用XtraBackup來做熱備份方案。


定時全量熱備份

系統中通常會同時存在全量備份和增量備份,以防其中一個備份出了問題,還有另一個備份可以使用。由於全量熱備份比較耗時,所以一般不會經常執行,會間隔一段時間才執行一次。例如,每個月一號零點執行或每週一零點執行等。

在Linux中有一個crontab命令,可以在固定的間隔時間執行指定的系統指令或shell腳本。使用crontab命令結合shell腳本可以實現定時的全量熱備份。

這裏就舉例演示一下,首先編寫執行全量熱備份的shell腳本如下:

[root@PXC-Node3 ~]# vim full-backup.sh
#!/bin/bash

time=`date "+%Y-%m-%d %H:%M:%S"`
echo '執行全量熱備份 '$time

innobackupex --defaults-file=/etc/my.cnf --host=192.168.190.134 --user=admin --password=Abc_123456 --port=3306 --no-timestamp --stream=xbstream --encrypt=AES256 --encrypt-threads=10 --encrypt-chunk-size 512 --encrypt-key='1K!cNoq&RUfQsY&&LAczTjco' -> /home/backup/fullBackupOfMysql.xbstream

賦予該腳本執行權限:

[root@PXC-Node3 ~]# chmod -R 777 full-backup.sh

最後配置一下crontab即可,例如我這裏定義每週一零點執行,這樣就實現了定時全量熱備份:

[root@PXC-Node3 ~]# crontab -e
# 每週一零點執行
0 0 * * 1 /root/full-backup.sh > /home/backup/full-backup.log 2>&1

XtraBackup全量冷還原

上面介紹了全量熱備份後,我們來看下如何將XtraBackup備份的文件進行還原。在還原這塊只能冷還原,不存在熱還原,因爲對一個正在運行中的數據庫進行在線還原操作,而同時用戶又在讀寫數據,這就有可能導致數據互相覆蓋,使得數據庫的數據發生錯亂。

因此,還原這塊就只能是冷還原,之前也介紹過冷還原,只不過使用XtraBackup進行冷還原會更加簡單,沒有還原冷備份時那麼麻煩。

首先關閉MySQL服務:

[root@PXC-Node3 ~]# systemctl stop mysqld

清空數據目錄及表分區的數據目錄:

[root@PXC-Node3 ~]# rm -rf /var/lib/mysql/*
[root@PXC-Node3 ~]# rm -rf /mnt/p0/data/*
[root@PXC-Node3 ~]# rm -rf /mnt/p1/data/*
  • Tips:這裏由於是示例就直接使用rm刪除了,如果是實際的運行環境,建議先使用mv重命名需要刪除的目錄,最後還原完備份文件並驗證沒有問題後,再使用rm刪除,以避免刪庫跑路的悲劇發生

備份文件是經過壓縮的,所以需要創建一個臨時目錄來存放解壓後的文件:

[root@PXC-Node3 ~]# mkdir /home/backup/temp

然後使用xbstream命令將備份文件解壓至該目錄下:

[root@PXC-Node3 ~]# xbstream -x < /home/backup/fullBackupOfMysql.xbstream -C /home/backup/temp/

因爲備份文件時進行了加密,所以解壓後的文件都是加密的,需要解密備份文件:

[root@PXC-Node3 ~]# innobackupex --decrypt=AES256 --encrypt-key='1K!cNoq\&RUfQsY\&\&LAczTjco' /home/backup/temp
  • Tips:因爲&是特殊字符,所以需要使用\轉義一下

由於是熱備份,所以事務日誌中可能會存在一些未完成的事務,這就需要回滾沒有提交的事務,以及同步已經提交的事務到數據文件。執行如下命令:

[root@PXC-Node3 ~]# innobackupex --apply-log /home/backup/temp

完成以上步驟後,就可以使用以下命令對備份文件進行還原:

[root@PXC-Node3 ~]# innobackupex --defaults-file=/etc/my.cnf --copy-back /home/backup/temp

接着給還原後的目錄文件賦予mysql用戶權限:

[root@PXC-Node3 ~]# chown -R mysql:mysql /var/lib/mysql/*

到此爲止就完成了冷還原,最後啓動MySQL服務並自行驗證下數據是否正常即可:

[root@PXC-Node3 ~]# systemctl start mysqld

增量熱備份

增量熱備份必須以全量熱備份爲基礎進行備份,所以在瞭解了XtraBackup的全量熱備份和全量冷還原後,接下來就可以實踐XtraBackup的增量熱備份了。

注意事項:

  • 無論全量熱備份使用了流式壓縮還是內容加密,都必須解壓或解密成普通的備份目錄
  • 增量熱備份也同樣可以使用流式壓縮和內容加密

之前演示冷還原的時候已經對全量備份的文件進行了解壓縮和內容解密,所以這裏以/home/backup/temp/備份目錄爲例,增量熱備份命令如下:

[root@PXC-Node3 ~]# innobackupex --defaults-file=/etc/my.cnf --host=192.168.190.134 --user=admin --password=Abc_123456 --port=3306 --incremental-basedir=/home/backup/temp/ --incremental /home/backup/increment
  • --incremental-basedir:指定全量備份文件所存儲的目錄,即基於哪個全量備份進行增量備份
  • --incremental:指定採用增量備份
  • /home/backup/increment:增量備份文件所存放的目錄

增量備份的文件目錄如下:

[root@PXC-Node3 ~]# ls /home/backup/increment/
2020-01-26_17-02-21
[root@PXC-Node3 ~]# ls /home/backup/increment/2020-01-26_17-32-41/
backup-my.cnf   ibdata1.delta  mysql               sys   tpcc                    xtrabackup_checkpoints  xtrabackup_logfile
ib_buffer_pool  ibdata1.meta   performance_schema  test  xtrabackup_binlog_info  xtrabackup_info
[root@PXC-Node3 ~]# 

可以使用du命令對比一下全量熱備份與增量熱備份的目錄大小:

[root@PXC-Node3 ~]# du -sh /home/backup/temp/
1.6G    /home/backup/temp/   # 全量熱備份的目錄大小
[root@PXC-Node3 ~]# du -sh /home/backup/increment/2020-01-26_17-32-41/
92M /home/backup/increment/2020-01-26_17-32-41/  # 增量熱備份的目錄大小
[root@PXC-Node3 ~]# 

之後的第二次增量備份就可以不基於全量備份,而是基於第一次的增量備份,如下示例:

[root@PXC-Node3 ~]# innobackupex --defaults-file=/etc/my.cnf --user=admin --password=Abc_123456 --incremental-basedir=/home/backup/increment/2020-01-26_17-32-41/ --incremental /home/backup/increment

如果增量備份時需要使用流式壓縮和內容加密,則添加相關參數即可。如下示例:

[root@PXC-Node3 ~]# innobackupex --defaults-file=/etc/my.cnf --user=admin --password=Abc_123456 --incremental-basedir=/home/backup/increment/2020-01-26_17-32-41/ --incremental --stream=xbstream --encrypt=AES256 --encrypt-threads=10 --encrypt-chunk-size 512 --encrypt-key='1K!cNoq&RUfQsY&&LAczTjco' ./ > /home/backup/increment
  • Tips:這裏的./表示將增量備份所有的內容都寫到流式壓縮文件裏,壓縮文件則存放在/home/backup/increment目錄下

Java程序定時增量熱備份數據庫

通常我們會讓增量熱備份作爲定時任務自動進行,從而避免人工定點去操作,以節省不必要的工作量。在全量熱備份時介紹了使用Linux的crontab命令來實現shell腳本的定時執行,而一些主流的編程語言也都基本具備實現定時任務的框架或類庫。

這裏以Java爲例,在Java的生態中,有Quartz和Spring框架可以實現定時任務,同樣也是使用Cron表達式語法。但Java的Cron表達式可以精確到秒,這一點與Linux的Cron表達式有所不同。

由於Quartz稍微複雜些,爲了簡單起見這裏就以Spring爲例。首先創建一個Spring Boot工程,pom.xml中的依賴項如下:

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
    </dependency>

    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
</dependencies>

在引導類上添加@EnableScheduling註解,以開啓定時調度功能:

@EnableScheduling
@SpringBootApplication
public class IncrementBackupApplication {
    ...
}

在Linux上創建一個文件,用於記錄每次增量熱備份時基於哪個目錄去備份。例如在第一次增量熱備份時是基於全量熱備份的目錄進行備份的,而在這之後的增量熱備份則是基於上一次增量熱備份的目錄進行備份:

[root@PXC-Node3 ~]# echo '/home/backup/temp/' > /home/backup/increment-backup.cnf

然後編寫實現定時執行增量熱備份的Java類:

package com.example.incrementbackup.task;

import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import java.io.*;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

/**
 * 增量熱備份定時任務
 *
 * @author 01
 * @date 2020-01-26
 **/
@Slf4j
@Component
public class IncrementBackupTask {

    /**
     * 每分鐘執行一次增量熱備份
     * 當然實際情況不會設置這麼短的間隔時間
     */
    @Scheduled(cron = "0 */1 * * * *")
    public void backup() {
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd_HH_mm_ss");
        String folderName = LocalDateTime.now().format(formatter);
        String configPath = "/home/backup/increment-backup.cnf";

        try (FileReader fileReader = new FileReader(configPath);
             BufferedReader bufferedReader = new BufferedReader(fileReader)) {

            String basedir = bufferedReader.readLine();
            String cmd = getCmd(basedir, folderName);
            log.info("開始進行增量熱備份. 執行的命令:{}", cmd);
            // 執行增量熱備份命令
            Process process = Runtime.getRuntime().exec(cmd);
            // 等待命令執行完成
            process.waitFor();

            try (FileWriter fileWriter = new FileWriter(configPath);
                 BufferedWriter bufferedWriter = new BufferedWriter(fileWriter)) {
                // 更新下次增量備份所使用的basedir路徑
                bufferedWriter.write("/home/backup/increment/" + folderName);
                log.info("增量熱備份結束");
            }
        } catch (IOException | InterruptedException e) {
            log.error("", e);
        }
    }

    /**
     * 拼裝 innobackupex 命令參數
     */
    private String getCmd(String basedir, String folderName) {
        String cmd = "innobackupex --defaults-file=/etc/my.cnf " +
                "--user=admin --password=Abc_123456 " +
                "--incremental-basedir=%s --no-timestamp " +
                "--incremental /home/backup/increment/%s";

        return String.format(cmd, basedir, folderName);
    }
}

完成以上代碼的編寫後,使用maven將項目打成jar包,然後將該jar包上傳到Linux中,通過java -jar命令執行。如下:

[root@PXC-Node3 ~]# java -jar increment-backup-0.0.1-SNAPSHOT.jar

執行過程中輸出的日誌信息如下:
關於數據庫的各種備份與還原姿勢詳解

等待備份結束後,可以看到increment-backup.cnf文件的內容也更新了:

[root@PXC-Node3 ~]# cat /home/backup/increment-backup.cnf
/home/backup/increment/2020-01-26_21_06_00
[root@PXC-Node3 ~]# 

生成的備份目錄結構也是正常的:

[root@PXC-Node3 ~]# ls /home/backup/increment/2020-01-26_21_12_00
backup-my.cnf   ibdata1.delta  mysql  sys  tpcc  xtrabackup_checkpoints  xtrabackup_logfile
ib_buffer_pool  ibdata1.meta   performance_schema  test  xtrabackup_binlog_info  xtrabackup_info
[root@PXC-Node3 ~]# 

到此爲止,我們就使用Java語言實現了定時增量熱備份數據庫。之所以介紹如何使用編程語言來實現,是因爲實際企業應用中,可能會有一些較爲複雜或個性化的需求,單純使用shell腳本是無法實現的。例如要求備份完成後發送郵件或短信通知相關人員,又或者要求可以在UI上控制定時執行的間隔時間等等。這種需求都得使用編程語言去定製化開發才能實現。


增量冷還原

經過以上小節可以得知增量熱備份僅備份新數據,並且生成的備份目錄體積也要比全量熱備份生成的目錄體積要小很多。那麼XtraBackup要如何將增量備份的數據還原到數據庫呢?其實也很簡單,就是先將增量熱備份的數據與全量熱備份的數據合併,然後基於合併後的備份數據去還原即可。

增量熱備份可以有很多個備份點,因爲除第一次增量熱備份外,其餘的增量熱備份都是基於上一次增量熱備份進行的。所以在還原的時候也可以選擇任意一個備份點去還原,但事務日誌的處理步驟與全量冷還原不一樣。

在之前演示全量冷還原的時候,有一個處理事務日誌的步驟,同樣增量冷還原也有這個步驟,但是有些差異。上面提到增量熱備份是可以有多個備份點的,那麼在還原某一個備份點時就需要處理該備份點及其之前備份點的事務日誌,否則就會出現數據混亂的情況。如下圖,有三個備份點:
關於數據庫的各種備份與還原姿勢詳解

例如,當還原“增量備份1”時,需要先處理其前一個備份點的事務日誌,即圖中的“全量熱備份”。接着再處理“增量備份1”這個備份點的事務日誌,然後合併“增量備份1”的數據到“全量熱備份”中。這樣才能保證多個備份點合併到全量備份點後的數據是一致的,最後還原“全量熱備份”中的數據即可。

再例如,要還原的是“增量備份2”,那麼就得先處理“全量熱備份”,然後處理“增量備份1”,接着處理“增量備份2”,按從前往後的順序依次將這三個備份點的事務日誌都給處理了後,才能合併備份點的數據到全量備份中,最後還原“全量熱備份”中的數據。其餘則以此類推......

接下來實操一下增量冷還原,這裏有三個與上圖對應的備份點目錄:

/home/backup/temp/  # 全量熱備份
/home/backup/increment/2020-01-27_10-11-24/  # 增量備份1
/home/backup/increment/2020-01-27_10-15-11/  # 增量備份2

因爲是冷還原,所以得先關閉MySQL服務:

[root@PXC-Node3 ~]# systemctl stop mysqld

在本例中要還原的是“增量備份2”這個備份點的數據,按照之前的說明,首先處理全量備份點的事務日誌,執行如下命令:

[root@PXC-Node3 ~]# innobackupex --apply-log --redo-only /home/backup/temp/
  • --redo-only:指定不回滾未提交的事務,因爲下個備份點的事務日誌裏可能會提交該備份點未提交的事務。如果回滾了就會導致下個備份點無法正常提交

然後處理“增量備份1”的事務日誌,並將"增量備份1"的數據合併到全量備份點上:

[root@PXC-Node3 ~]# innobackupex --apply-log --redo-only /home/backup/temp/ --incremental-dir=/home/backup/increment/2020-01-27_10-11-24/
  • --incremental-dir:指定要合併到全量備份的增量備份目錄

接着處理“增量備份2”的事務日誌,並將"增量備份2"的數據合併到全量備份點上。由於只還原到“增量備份2”這個備份點,所以就不需要加上--redo-only參數了,因爲沒有下個備份點了:

[root@PXC-Node3 ~]# innobackupex --apply-log /home/backup/temp/ --incremental-dir=/home/backup/increment/2020-01-27_10-15-11/

與全量冷還原一樣,也需清空數據目錄及表分區的數據目錄:

[root@PXC-Node3 ~]# rm -rf /var/lib/mysql/*
[root@PXC-Node3 ~]# rm -rf /mnt/p0/data/*
[root@PXC-Node3 ~]# rm -rf /mnt/p1/data/*

完成以上步驟後,就可以使用如下命令完成備份文件的還原了:

[root@PXC-Node3 ~]# innobackupex --defaults-file=/etc/my.cnf --copy-back /home/backup/temp/  # 注意這裏指定的是全量備份點的目錄

接着給還原後的目錄文件賦予mysql用戶權限:

[root@PXC-Node3 ~]# chown -R mysql:mysql /var/lib/mysql/*
[root@PXC-Node3 ~]# chown -R mysql:mysql /mnt/p0/data/*
[root@PXC-Node3 ~]# chown -R mysql:mysql /mnt/p1/data/*

到此爲止還原就完成了,最後啓動MySQL服務並自行驗證下數據是否正常即可:

[root@PXC-Node3 ~]# systemctl start mysqld
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章