一個案例講明白!如何更安全地實(shí)現(xiàn)數(shù)據(jù)備份和恢復(fù)
本文轉(zhuǎn)載自微信公眾號「數(shù)倉寶貝庫」,作者李玥 。轉(zhuǎn)載本文請聯(lián)系數(shù)倉寶貝庫公眾號。
保證數(shù)據(jù)安全,最簡單且有效的方法就是定期備份數(shù)據(jù),這樣無論因?yàn)槌霈F(xiàn)何種問題而導(dǎo)致的數(shù)據(jù)損失,都可以通過備份來恢復(fù)數(shù)據(jù)。但是,如何備份才能最大程度地保證數(shù)據(jù)安全,并不是一件簡單的事情。
2018年曾出現(xiàn)過一次重大故障,某著名云服務(wù)商因?yàn)橛脖P損壞,導(dǎo)致多個客戶數(shù)據(jù)全部丟失。通常來說,一個大的云服務(wù)商,數(shù)據(jù)通常都會有多個備份,即使硬盤損壞,也不會導(dǎo)致數(shù)據(jù)丟失的重大事故,但是因?yàn)楦鞣N各樣的原因,最終的結(jié)果是數(shù)據(jù)的三個副本都被刪除,數(shù)據(jù)丟失無法找回。
所以,并不是簡單地定期備份數(shù)據(jù)就可以高枕無憂了。下面就以最常用的MySQL為例來講解,如何更安全地實(shí)現(xiàn)數(shù)據(jù)的備份和恢復(fù)。
最簡單的備份方式就是全量備份。備份的時候,把所有的數(shù)據(jù)復(fù)制一份,存放到文件中,恢復(fù)的時候再把文件中的數(shù)據(jù)復(fù)制回去,這樣就可以保證恢復(fù)之后,數(shù)據(jù)庫中的數(shù)據(jù)與備份時的數(shù)據(jù)是完全一樣的。在MySQL中,我們可以使用mysqldump命令執(zhí)行全量備份。
比如,全量備份數(shù)據(jù)庫test的命令如下:
- 1$mysqldump -uroot -p test > test.sql
備份出來的文件是一個SQL文件,文件的內(nèi)容就是創(chuàng)建數(shù)據(jù)庫、表,寫入數(shù)據(jù)等之類的SQL語句,如果要恢復(fù)數(shù)據(jù),則直接執(zhí)行這個備份的SQL文件就可以了:
- $mysql -uroot test < test.sql
不過,全量備份的代價非常高,為什么這么說呢?
首先,備份文件包含了數(shù)據(jù)庫中的所有數(shù)據(jù),占用的磁盤空間非常大;其次,每次備份操作都要拷貝大量的數(shù)據(jù),備份過程中會占用數(shù)據(jù)庫服務(wù)器大量的CPU和磁盤IO資源。同時,為了保證數(shù)據(jù)一致性,備份過程中很有可能會鎖表。這些都會導(dǎo)致在備份期間,數(shù)據(jù)庫本身的性能嚴(yán)重下降。所以,我們不能頻繁地對數(shù)據(jù)庫執(zhí)行全量備份操作。
一般來說,在生產(chǎn)系統(tǒng)中,每天執(zhí)行一次全量備份就已經(jīng)是非常頻繁的了。這就意味著,如果數(shù)據(jù)庫中的數(shù)據(jù)丟失了,就只能恢復(fù)到最近一次全量備份的那個時間點(diǎn),這個時間點(diǎn)之后的數(shù)據(jù)是無法找回的。也就是說,因?yàn)槿總浞莸拇鷥r比較高,不能頻繁地執(zhí)行備份操作,所以全量備份不能做到完全無損的恢復(fù)。
既然全量備份代價太高,不能頻繁執(zhí)行,那么有沒有代價較低的備份方法,能讓我們的數(shù)據(jù)少丟失甚至不丟失呢?增量備份可以達(dá)到這個目的。相比于全量備份,增量備份每次只用備份相對于上一次備份發(fā)生了變化的那部分?jǐn)?shù)據(jù),所以增量備份的速度更快。
MySQL自帶的Binlog,就是一種實(shí)時的增量備份工具。Binlog所記錄的就是MySQL數(shù)據(jù)變更的操作日志。開啟Binlog之后,MySQL中數(shù)據(jù)的每次更新操作,都會記錄到Binlog中。Binlog是可以回放的,回放Binlog,就相當(dāng)于是把之前對數(shù)據(jù)庫中所有數(shù)據(jù)的更新操作,都按順序重新執(zhí)行一遍,回放完成之后,數(shù)據(jù)自然就恢復(fù)了。這就是Binlog增量備份的基本原理。很多數(shù)據(jù)庫都有類似于MySQL Binlog的日志工具,原理也與Binlog相同,備份和恢復(fù)的方法也與之類似。
下面就來通過一個例子,講解如何使用Binlog進(jìn)行備份和恢復(fù)。首先,使用“show variables like '%log_bin%'”命令確認(rèn)一下是否開啟了Binlog功能:
- 1mysql> show variables like '%log_bin%';
- 2
- 3+---------------------------------+-----------------------------------+
- 4
- 5| Variable_name | Value |
- 6
- 7+---------------------------------+-----------------------------------+
- 8
- 9| log_bin | ON |
- 10
- 11| log_bin_basename | /usr/local/var/mysql/binlog |
- 12
- 13+---------------------------------+-----------------------------------+
- 14
- 15mysql> show master status;
- 16
- 17+-------------+--------+------------+----------------+-----------------+
- 18
- 19| File |Position|Binlog_Do_DB|Binlog_Ignore_DB|Executed_Gtid_Set|
- 20
- 21+-------------+--------+------------+----------------+-----------------+
- 22
- 23|binlog.000001| 18745| | | |
- 24
- 25+-------------+--------+------------+----------------+-----------------+
- 26
我們可以看到,當(dāng)前這個數(shù)據(jù)庫已經(jīng)開啟了Binlog,log_bin_basename表示Binlog文件在服務(wù)器磁盤上的具體位置。然后,我們用“show master status”命令查看當(dāng)前Binlog的狀態(tài),結(jié)果顯示了正在寫入的Binlog文件,以及其當(dāng)前的位置。假設(shè)我們每天凌晨用mysqldump做一個全量備份,然后開啟Binlog,借助于這些備份操作,我們可以把數(shù)據(jù)恢復(fù)到全量備份之后的任意一個時刻。
下面就來做一個簡單的備份恢復(fù)演示。我們先模擬一次“刪庫跑路”的場景,直接把賬戶余額表清空:
- 1mysql> truncate table account_balance;
- 2
- 3Query OK, 0 rows affected (0.02 sec)
- 4
- 5mysql> select * from account_balance;
- 6
- 7Empty set (0.00 sec)
- 8
然后進(jìn)行數(shù)據(jù)恢復(fù),首先執(zhí)行一次全量恢復(fù),把數(shù)據(jù)庫恢復(fù)到當(dāng)天凌晨的狀態(tài):
- 1$mysql -uroot test < dump.sql
- 2
- 3mysql> select * from account_balance;
- 4
- 5+---------+---------+---------------------+--------+
- 6
- 7| user_id | balance | timestamp | log_id |
- 8
- 9+---------+---------+---------------------+--------+
- 10
- 11| 0 | 100 | 2020-02-13 20:24:33 | 3 |
- 12
- 13+---------+---------+---------------------+--------+
可以看到,表里面的數(shù)據(jù)已經(jīng)恢復(fù)了,但還是比較舊的數(shù)據(jù)。接下來,我們再用Binlog把數(shù)據(jù)恢復(fù)到“刪庫跑路”之前的那個時刻:
- 1$mysqlbinlog --start-datetime "2020-02-20 00:00:00" --stop-datetime "2020-02-20 15:09:00" /usr/local/var/mysql/binlog.000001 | mysql -uroot
- 2
- 3mysql> select * from account_balance;
- 4
- 5+---------+---------+---------------------+--------+
- 6
- 7| user_id | balance | timestamp | log_id |
- 8
- 9+---------+---------+---------------------+--------+
- 10
- 11| 0 | 200 | 2020-02-20 15:08:12 | 0 |
- 12
- 13+---------+---------+---------------------+--------+
- 14
由恢復(fù)結(jié)果可以看出,數(shù)據(jù)已經(jīng)恢復(fù)到當(dāng)天的15點(diǎn)了。
通過定期的全量備份,配合Binlog,我們可以把數(shù)據(jù)恢復(fù)到任意一個時間點(diǎn),再也不怕程序員“刪庫跑路”了。詳細(xì)的命令,可以參考MySQL官方文檔中的“備份和恢復(fù)”相關(guān)章節(jié)。
在執(zhí)行備份和恢復(fù)的時候,大家需要特別注意如下兩個要點(diǎn)。
第一,也是最重要的,“不要把所有的雞蛋放在同一個籃子中”,無論是全量備份還是Binlog,都不要與數(shù)據(jù)庫存放在同一個服務(wù)器上。最好能存放到不同的機(jī)房,甚至不同城市,離得越遠(yuǎn)越好。這樣即使出現(xiàn)機(jī)房著火、光纜被挖斷甚至地震也不怕數(shù)據(jù)丟失。
第二,在回放Binlog的時候,指定的起始時間可以比全量備份的時間稍微提前一點(diǎn)兒,這樣可以確保全量備份之后的所有操作都在恢復(fù)的Binlog范圍內(nèi),從而保證數(shù)據(jù)恢復(fù)的完整性。
因?yàn)榛胤臖inlog的操作是具備冪等性的(為了確保回放的冪等性,需要將Binlog的格式設(shè)置為ROW格式)。
關(guān)于作者:李玥,美團(tuán)基礎(chǔ)技術(shù)部高級技術(shù)專家,極客時間《后端存儲實(shí)戰(zhàn)課》《消息隊(duì)列高手課》等專欄作者。曾在當(dāng)當(dāng)網(wǎng)、京東零售等公司任職。從事互聯(lián)網(wǎng)電商行業(yè)基礎(chǔ)架構(gòu)領(lǐng)域的架構(gòu)設(shè)計(jì)和研發(fā)工作多年,曾多次參與雙十一和618電商大促。專注于分布式存儲、云原生架構(gòu)下的服務(wù)治理、分布式消息和實(shí)時計(jì)算等技術(shù)領(lǐng)域,致力于推進(jìn)基礎(chǔ)架構(gòu)技術(shù)的創(chuàng)新與開源。
本文摘編自《電商存儲系統(tǒng)實(shí)戰(zhàn):架構(gòu)設(shè)計(jì)與海量數(shù)據(jù)處理》,經(jīng)出版方授權(quán)發(fā)布。(ISBN:9787111697411)轉(zhuǎn)載請保留文章出處。