挖一挖MongoDB的備份與還原
一 研究背景需求
目前作者所在公司的MongoDB資料庫是每天凌晨做一次全庫完整備份,但資料庫出現故障時,只能保證恢復到全備時間點,比如,00:30 做的完整備份,而出現故障是下午18:00,那麼現有的備份機制只可以恢復到00:30,即丟失00:30 – 18:00 的操作資料。
此外,我們現在的副本集沒有delay節點,當出現誤操作或需要恢復到指定時間點操作時,目前災備機制也不支援此操作。上線這種備份方案,心裡總是惶惶的。
並且細究mongodump機制原理,此命令在執行過程中並不會把資料庫鎖死(或建立快照,以保證整個庫凍結在一個固定的時間點),實現資料庫完整性,而是細化到集合級別,如此,會導致資料完整性問題。 例如,集合A中存放了訂單概要資訊,集合B中存放了訂單的所有明細,那麼只有一個訂單有完整的明細時才是正確的狀態。那麼備份時,如果備份集合A處於時間點x,而備份集合B處於x之後的一個時間點y時,可以想象A和B中的資料極有可能不對應而失去意義(部分訂單有訂單明細而沒有訂單概要資訊)。
二 原理分析
關係型資料庫,例如MySQL ,SQL Server 都有事務日誌(或bin log),會將資料庫的DML 、 DDL、DCL等操作記錄在事務檔案中,可以通過日誌備份搭建還原體系。MongoDB沒有此類機制和資料檔案,難以實現。
但MongoDB 副本集 有通過 oplog(主要記錄在local資料庫oplog.rs集合中) 實現節點間的同步,此集合記錄了資料庫的OP操作,記錄的是整個mongod例項一段時間內的所有變更(插入/更新/刪除)操作。
是否可以考慮通過oplog.rs集合的備份還原來解決以上問題(資料完整性;不能還原到指定時間點;時效性差。)。
值得注意的是,oplog為replica set或者master/slave模式專用,standalone模式執行mongodb並不推薦。
檢視mongodb備份命令Mongodump,其中有一個相關引數oplog。
Mongodump 中--oplog引數
引數 |
引數說明 |
--oplog |
Use oplog for taking a point-in-time snapshot |
該引數的主要作用是我們在匯出庫集合資料的同時生成一個oplog.bson檔案,裡面存放了開始進行dump到dump結束之間所有的op log 操作。
注意:--oplog選項只對全庫匯出有效。
相應的 Mongorestore 中與 Oplog 相關的引數
引數 |
引數說明 |
oplogReplay |
replay oplog for point-in-time restore |
oplogLimit |
only include oplog entries before the provided Timestamp |
oplogFile |
oplog file to use for replay of oplog |
三 驗證測試
3.1 場景1 備份還原後,如何保證資料一致性、完整性
在Insert資料過程中,備份資料庫,為說明問題,資料插入跨越整個備份過程。
3.1.1 在不使用--oplog 引數下備份還原
Step 1 向資料庫ygtest041602插入資料,源庫沒有集合order0531、orderdetial
插入語句:
for(var i = 0; i < 10000; i++)
{ db.order0531.insert({a: i});};
for(i=0;i<300000;i++)
{ db.orderdetial.insert({"id":i,"name":"shenzheng","addr":"龍崗","date":new Date()}); };
step 2 在上述命令執行期間 執行mongodump 備份
./mongodump -h 172.177.XXX.XXX --port 埠 --authenticationDatabase admin -u 使用者名稱 -p 密碼 --gzip -o /data/mongodb_back/mongotestdump
Step 3 還原上述備份檔案
./mongorestore -h 172.177.XXX.XXX --port 埠 --authenticationDatabase admin -u 使用者名稱 -p 密碼 --gzip /data/mongodb_back/mongotestdump
Step 4 檢查源庫Insert 語句, 執行完畢(一定是mongodump完畢,再insert結束;但不強調,mongorestore與insert的時間關係)
Step 5 待語句執行完畢後,比較 源庫和 還原庫 的資料。
源庫
還原庫
我們看到集合orderdetial在兩個資料庫不一致,有差異。即這種備份還原 無法保證資料一致性。
3.1.2 增加 引數--oplog 引數下備份還原
Step 1 刪除 上次測試中遺留的集合 和 備份檔案
Step 2 啟動insert命令
for(var i = 0; i < 10000; i++)
{ db.order0531.insert({a: i});};
for(i=0;i<150000;i++)
{ db.orderdetial.insert({"id":i,"name":"shenzheng","addr":"龍崗","date":new Date()}); };
Step 3 執行備份命令
./mongodump -h 172.177.XXX.XXX --port 埠 --oplog --authenticationDatabase admin -u 使用者名稱 –p密碼 --gzip -o /data/mongodb_back/mongotestdump
從列印結果來看,有對oplog命令的輸出,檢視備份檔案 多了一個 oplog.bson 檔案
Step 4 執行還原命令,增加引數 --oplogReplay
./mongorestore -h 172.177.XXX.XXX --port 埠 --oplogReplay --authenticationDatabase admin -u 使用者名稱 –p 密碼 --gzip /data/mongodb_back/mongotestdump
從執行情況來看,此類還原命令有還原oplog
Step 5 比對資料
源庫
還原庫
本場景測試結論:
雖然結果資料仍不一致,但從後者的備份還原過程可以看出,有對oplog做單獨的處理。
資料可以保證以oplog結尾為準,實現了多集合(表)的時間一致性(MongoDB本身特性,不保證體現資料庫的事務一致性)。
3.2 場景二 還原到指定時間點
使用場景3.1.2 的備份檔案,測試還原到備份過程中的某個時刻。從上次備份過程可知,開始時間為2018-05-31T11:13:46.501+0800,結束時間為 2018-05-31T11:14:12.961+0800
此輪測試還原點選擇在此時間段內。時間還原點的選擇,假如我們還原到orderdetial 集合"_id" : ObjectId("5b0f6876c52291864d3485b9")的插入時的時間點。
檢視此時間點對應的時間和操作序列,在源庫local中的oplog.rs 集合查詢。
(oplog.rs集合的資料意義,我們在其他章節介紹,在此不做贅述)
"ts" : Timestamp(1527736438, 858)
此文件對應的 "id" : 9719.0,插入時間為2018-05-31T11:13:58.721+0800 【正好,在mongodump的過程中】
Step 1 刪除還原伺服器中的還原庫(上面測試遺留的資料庫)
Step 2 執行還原命令,通過--oplogLimit 引數指定時間點,時間點為上一步查到的Timestamp(1527736438, 858)
./mongorestore -h 172.177.XXX.XXX --port 埠 --oplogReplay --oplogLimit "1527736438:858" --authenticationDatabase admin -u 使用者名稱 -p 密碼 /data/mongodb_back/mongotestdump
Step 3 結果驗證
從print 出來的執行過程來看,相同的備份檔案,相同的還原環境,新增--oplogLimit "1527736438:858" 引數的 replay Oplog,要小於上次不帶--oplogLimit引數的全還原。
本次還原,新增 --oplogLimit,只還原了 824 KB 的oplog
2018-05-31T16:55:21.975+0800 replaying oplog
2018-05-31T16:55:22.724+0800 oplog 85.9KB
2018-05-31T16:55:25.720+0800 oplog 575KB
2018-05-31T16:55:27.231+0800 oplog 842KB
2018-05-31T16:55:27.231+0800 done
上次,沒有新增 --oplogLimit,預設全還原,所以還原了3.22MB oplog。
2018-05-31T11:23:33.770+0800 replaying oplog
2018-05-31T11:23:34.682+0800 oplog 146KB
2018-05-31T11:23:40.686+0800 oplog 1.10MB
2018-05-31T11:23:49.688+0800 oplog 2.61MB
2018-05-31T11:23:52.680+0800 oplog 3.11MB
2018-05-31T11:23:53.246+0800 oplog 3.22MB
2018-05-31T11:23:53.246+0800 done
這也容易理解。全還原一定大於指定時間的部分還原。
資料檢驗 :
(1)此時最大ID為"id" : 9718.0,和引數值對應的ID值連續
(2)此過程共還原資料9719,低於全還原的63862
本場景測試結論:
使用引數--oplogReplay –oplogLimit 可以將資料庫還原到指定時刻。
3.3 場景三 oplog.rs 集合中匯出資料進行還原
在場景2 中的 oplog.bson 是我們在mongodump 全庫時,新增引數--oplog引數後自動生成的。 全庫備份,受限於效能損耗和資源限制,不能高頻率執行和長時間儲存。考慮到副本集的oplog儲存在oplog.rs集合中,此輪測試驗證是否只從oplog.rs匯出,就可以保證資料連續性。即從oplog.rs匯出的資料是否可以替代mongodump過程中產生的oplog.bson.
類似於我們從oplog.rs集合中匯出MySQL的binlog檔案,也類似於SQL Server Log日誌,可以實現增量備份與增量還原。
Step 1 在上述場景中繼續測試,我們已經執行全庫還原,只是時間點還原到"id" : 9718.0 對應的資料。
Step 2 刪除 上次全備過程中產生的備份檔案
Step 3 為增加試驗效果,再次在源庫插入部分資料
for(var i = 0; i < 10000; i++)
{ db.order0531.insert({a: i});};
for(i=0;i<200000;i++)
{ db.orderdetial.insert({"id":i,"name":"shenzheng","addr":"龍崗","date":new Date()}); };
step 4 將資料庫local中集合oplog.rs 匯出(建議匯出時,增加時間引數,例如一小時內的或大於某時間時刻。注意,此處的測試場景中,我們選擇的Timestamp(1527552239,1),在我們場景2中的Timestamp(1527736438, 858)的前面,故意時間重疊)
./mongodump -h 172.177.XXX.XXX --port 埠 --authenticationDatabase admin -u 使用者名稱 -p 密碼 -d local -c oplog.rs --query '{ts:{$gte:Timestamp(1527552239,1)}}' -o /data/mongodb_back/mongotestoplog
step 5 將備份產生的檔案local/oplog.rs.bson,copy至還原路徑下,並將其命名為oplog.bson
cp ./mongotestoplog/local/oplog.rs.bson ./mongotestdump/oplog.bson
step 6 執行還原命令
./mongorestore -h 172.177.XXX.XXX --port 埠 --oplogReplay --authenticationDatabase admin -u 使用者名稱 –p 密碼 /data/mongodb_back/mongotestdump
step 7 資料驗證
還原庫
源庫
兩者資料一致
本場景測試結論:
(1)dumprestor 命令,可以接受從別處而來,除了--oplog之外,可人為獲取的oplog;還原時需重新命名(step 5)。
(2)可以實現類似關係型資料庫的增量備份與增量還原
(3)oplog有一個非常重要的特性——冪等性(idempotent)。即對一個數據集合,使用oplog中記錄的操作重放時,無論被重放多少次,其結果會是一樣的。舉例來說,如果oplog中記錄的是一個插入操作,並不會因為你重放了兩次,資料庫中就得到兩條相同的記錄。
四 總結
1. MongoDB 不支援事務,無法保證備份還原命令中的事務完整性、業務一致性(無關係數型據庫中基於事務日誌的重做還原機制)。但結合命令引數--oplog,可以實現資料、業務的時間一致性。
2. 資料庫還原時,結合引數--oplogReplay --oplogLimit實現指定時間點的還原。
3. 搭建副本集的MongoDB,定期匯出oplog.rs,可以實現增量備份與增量還原。