MongoDB的常规备份策略
MongoDB的备份其实算是一个基本操作,最近总是有人问起,看来很多人对这里还不太熟悉。为了避免一次又一次地重复解释,特总结成一篇博客供后来者查阅。如有不尽正确之处请指正。
1. 内建方法
1.1 复制数据库文件
不用多做解释,几乎对任何数据库都有用,简单粗暴。但像多数数据库一样,这个操作必须在mongod实例停止的情况下进行才能保证你得到的是正确状态下的数据库。否则在备份过程中如果有写操作,可能造成备份到的库处于非正常状态而不可使用。必须停止数据库这一点就造成了这个方法的可用性非常低,使用场景有限。
1.2 mongodump/mongorestore
我不打算细说这两个命令如何使用,因为介绍这两个命令的文章网上已经一大堆了,大家可以轻松从别处或者官方文档中找到说明。不想细说的另一个原因也是希望看到这里的读者能够养成独立解决问题的能力,不要对所谓的“高手”产生依赖,遇到问题独立思考独立解决,这将是你今后的道路上必不可少的技能之一。
下面介绍一些别人说得相对少的东西。
1.2.1 除了mongodump/mongorestore之外还有一对组合是mongoexport/mongoimport
区别在哪里?
- mongoexport/mongoimport导入/导出的是JSON格式,而mongodump/mongorestore导入/导出的是BSON格式。
- JSON可读性强但体积较大,BSON则是二进制文件,体积小但对人类几乎没有可读性。
- 在一些mongodb版本之间,BSON格式可能会随版本不同而有所不同,所以不同版本之间用mongodump/mongorestore可能不会成功,具体要看版本之间的兼容性。当无法使用BSON进行跨版本的数据迁移的时候,使用JSON格式即mongoexport/mongoimport是一个可选项。跨版本的mongodump/mongorestore个人并不推荐,实在要做请先检查文档看两个版本是否兼容(大部分时候是的)。
- JSON虽然具有较好的跨版本通用性,但其只保留了数据部分,不保留索引,账户等其他基础信息。使用时应该注意。
总之,这两套工具在实际使用中各有优势,应该根据应用场景选择使用(好像跟没说一样)。但严格地说,mongoexport/mongoimport的主要作用还是导入/导出数据时使用,并不是一个真正意义上的备份工具。所以这里也不展开介绍了。
1.2.2 mongodump有一个值得一提的选项是--oplog
注意这是replica set或者master/slave模式专用(standalone模式运行mongodb并不推荐)。
--oplog use oplog for taking a point-in-time snapshot
看英文说明好牛B的样子,point-in-time快照哦,我第一次看到这句话的时候的理解是它可以让数据库回到这段时间中的任意一个时间点的状态,美了好一阵。但实际上并不是。它的实际作用是在导出的同时生成一个oplog.bson文件,存放在你开始进行dump到dump结束之间所有的oplog。这个东西具体有什么用先卖个关子。用图形来说明下oplog.bson的覆盖范围:
为了后面的讲解不至于把人说晕,进一步说明之前先解释一下什么是oplog及其相关概念。官方提供的文档其实已经很全面地做了解释,点击查看中文文档或英文文档。
简单地说,在replica set中oplog是一个定容集合(capped collection),它的默认大小是磁盘空间的5%(可以通过--oplogSizeMB参数修改),位于local库的db.oplog.rs,有兴趣可以看看里面到底有些什么内容。其中记录的是整个mongod实例一段时间内数据库的所有变更(插入/更新/删除)操作。当空间用完时新记录自动覆盖最老的记录。所以从时间轴上看,oplog的覆盖范围大概是这样的:
其覆盖范围被称作oplog时间窗口。需要注意的是,因为oplog是一个定容集合,所以时间窗口能覆盖的范围会因为你单位时间内的更新次数不同而变化。想要查看当前的oplog时间窗口预计值,可以使用以下命令:
test:PRIMARY> rs.printReplicationInfo()
configured oplog size: .5615234375MB <--集合大小
log length start to end: 423849secs (.74hrs) <--预计窗口覆盖时间
oplog first event time: Wed Sep :: GMT+ (CST)
oplog last event time: Mon Sep :: GMT+ (CST)
now: Mon Sep :: GMT+ (CST)
oplog有一个非常重要的特性——幂等性(idempotent)。即对一个数据集合,使用oplog中记录的操作重放时,无论被重放多少次,其结果会是一样的。举例来说,如果oplog中记录的是一个插入操作,并不会因为你重放了两次,数据库中就得到两条相同的记录。这是一个很重要的特性,也是后面这些操作的基础。
回到主题上来,看看oplog.bson到底有什么作用。首先要明白的一个问题是数据之间互相有依赖性,比如集合A中存放了订单,集合B中存放了订单的所有明细,那么只有一个订单有完整的明细时才是正确的状态。假设在任意一个时间点,A和B集合的数据都是完整对应并且有意义的(对非关系型数据库要做到这点并不容易,且对于MongoDB来说这样的数据结构并非合理。但此处我们假设这个条件成立),那么如果A处于时间点x,而B处于x之后的一个时间点y时,可以想象A和B中的数据极有可能不对应而失去意义。
再回来看mongodump的操作。mongodump的进行过程中并不会把数据库锁死以保证整个库冻结在一个固定的时间点,这在业务上常常是不允许的。所以就有了dump的最终结果中A集合是10点整的状态,而B集合则是10点零1分的状态这种情况。这样的备份即使恢复回去,可以想象得到的结果恐怕意义有限。那么上面这个oplog.bson的意义就在这里体现出来了。如果在dump数据的基础上,再重做一遍oplog中记录的所有操作,这时的数据就可以代表dump结束时那个时间点(point-in-time)的数据库状态。
这个结论成立的重要条件就是幂等性:已存在的数据,重做oplog不会重复;不存在的数据重做oplog就可以进入数据库。所以当做完截止到某个时间点的oplog时,数据库就恢复到了截止那个时间点的状态。
来看看mongorestore的选项。跟oplog相关的选项有--oplogReplay和--oplogLimit。第一个选项顾名思义,可以重放oplog.bson中的操作内容。第二个选项
后面再做介绍。先来看一个例子:
首先我们模拟一个不断有插入操作的集合foo,
use test
for(var i = 0; i < 100000; i++) {
db.foo.insert({a: i});
}
然后在插入过程中模拟一次mongodump并指定--oplog。
mongodump -h 127.0.0.1 --oplog
注意--oplog选项只对全库导出有效,所以不能指定-d选项。因为整个实例的变更操作都会集中在local库中的oplog.rs集合中。
根据上面所说,从dump开始的时间系统将记录所有的oplog到oplog.bson中,所以我们得到这些文件:
yaoxing ~ $ ll dump/
total
-rw-r--r-- yaoxing yaoxing Sep : oplog.bson
drwxr-xr-x yaoxing yaoxing Sep : test
其中test是我们刚才使用的数据库,oplog.bson是导出期间进行的所有操作。如果对oplog.bson中的内容好奇,可以用bsondump工具来查看其中的内容,例如:
{"h":{"$numberLong":"2279811375157953332"},"ns":"test.foo","o":{"_id":{"$oid":"55f834ae6b530b5854f9d6ee"},"a":7784.0},"op":"i","ts":{"$timestamp":{"t":1442329774,"i":3248}},"v":2}
从oplog.bson中我们挑选第一条和最后一条内容出来观察
{"h":{"$numberLong":"2279811375157953332"},"ns":"test.foo","o":{"_id":{"$oid":"55f834ae6b530b5854f9d6ee"},"a":7784.0},"op":"i","ts":{"$timestamp":{"t":1442329774,"i":3248}},"v":2}
...
{"h":{"$numberLong":"-1177358680665374097"},"ns":"test.foo","o":{"_id":{"$oid":"55f834b26b530b5854f9fa5e"},"a":16856.0},"op":"i","ts":{"$timestamp":{"t":1442329778,"i":1361}},"v":2}
红字部分可以看出,从开始进行mongodump时,循环进行到i=7784,而到整个操作结束时,循环进行到i=16856。再看一下test/foo.bson中数据的最后一条
{"_id":{"$oid":"55f834ae6b530b5854f9d73d"},"a":7863.0}
可以发现,最终dump出的数据既不是最开始的状态,也不是最后的状态,而是中间某个随机状态。这正是因为集合不断变化造成的。
那么使用mongorestore来恢复:
yaoxing ~ $ mongorestore -h 127.0.0.1 --oplogReplay dump
--19T01::20.095+ building a list of dbs and collections to restore from dump dir
--19T01::20.095+ reading metadata for test.foo from
--19T01::20.096+ restoring test.foo from
--19T01::20.248+ restoring indexes for collection test.foo from metadata
2015-09-19T01:22:20.248+0800 finished restoring test.foo (7864 documents)
2015-09-19T01:22:20.248+0800 replaying oplog
--19T01::20.463+ done
注意红字的两句,第一句表示test.foo集合中恢复了7864个文档;第二句表示重放了oplog中的所有操作。所以理论上foo应该有16857个文档(7864个来自foo.bson,剩下的来自oplog.bson)。验证一下:
test:PRIMARY> db.foo.count()
16857
这就是带oplog的mongodump的真正作用。
1.2.3 从别处而来的oplog
聪明如你可能已经想到,既然dump出的数据配合oplog就可以把数据库恢复到某个状态,那是不是拥有一份从某个时间点开始备份的dump数据,再加上从dump开始之后的oplog,如果oplog足够长,是不是就可以把数据库恢复到其后的任意状态了?是的!事实上replica set正是依赖oplog的重放机制在工作。当secondary第一次加入replica set时做的initial sync就相当于是在做mongodump,此后只需要不断地同步和重放oplog.rs中的数据,就达到了secondary与primary同步的目的。
既然oplog一直都在oplog.rs中存在,我们为什么还需要在mongodump时指定--oplog呢?需要的时候从oplog.rs中拿不就完了吗?答案是肯定的,你确实可以只dump数据,不需要oplog。在需要的时候可以再从oplog.rs中取。但前提是oplog时间窗口(忘了时间窗口概念的请往前翻)必须能够覆盖dump的开始时间。
明白了这个道理,理论上只要我们的mongodump做得足够频繁,是可以保证数据库能够恢复到过去的任意一个时间点的。MMS(现在叫Cloud Manager)的付费备份也正是在利用这个原理工作。假设oplog时间窗口有24小时,那么理论上只要我在每24小时内完成一次dump,即可保证dump之后的24小时的point-in-time数据恢复。在oplog时间窗口快要滑出24小时的时候,只要及时完成下一次dump,就又可以有24小时的安全期。
来做个测试。仍然使用前面的方法模拟一段时间的数据库插入操作:
use test
for(var i = 0; i < 100000; i++) {
db.foo.insert({a: i});
}
同时做一次mongodump并不带--oplog:
yaoxing ~/dump $ mongodump -h 127.0.0.1
2015-09-24T00:06:11.929+0800 writing test.system.indexes to dump/test/system.indexes.bson
2015-09-24T00:06:11.929+0800 done dumping test.system.indexes (1 document)
2015-09-24T00:06:11.929+0800 writing test.foo to dump/test/foo.bson
2015-09-24T00:06:11.963+0800 done dumping test.foo (11162 documents)
可见我们导出了i=11162时的状态。等插入完成后,理论上我们的foo集合应该有100000条记录
> use test
switched to db test
> db.foo.count()
100000
现在假设我进行了一次误操作:
> db.foo.remove({})
WriteResult({ "nRemoved" : 100000 })
更惨的是我之后又往foo里面插入了一条数据
> db.foo.insert({a: 100001});
WriteResult({ "nInserted" : 1 })
现在怎样让时间倒流,回到灾难前的状态呢?由于在灾难前我们有过一次dump,因此现在只要在oplog时间窗口还能覆盖导出时间之前把oplog抢救出来就好了:
yaoxing ~ $ mongodump -h 127.0.0.1 -d local -c oplog.rs
--24T00::41.040+ writing local.oplog.rs to dump/local/oplog.rs.bson
--24T00::41.525+ done dumping local.oplog.rs ( documents)
为什么会有200003条记录呢?请自行使用bsondump工具查看发生了什么事情。
从前面讲的可知,使用mongodump加oplog.bson(请注意文件位置)即可恢复数据库。这里的dump/local/oplog.rs.bson其实就是我们需要的oplog.bson。因此把它重命名后放到合适的位置,一个模拟出来的恢复环境就准备好了
yaoxing ~/dump $ ll
total
-rw-r--r-- yaoxing yaoxing Sep : oplog.bson
drwxr-xr-x yaoxing yaoxing Sep : test
但是这个oplog.bson可是包含了所有灾难操作的啊,如果全盘恢复回去,就等于是先让时光倒流,再看悲剧重演一遍。心都碎了……这时候你需要一个新朋友,就是上面提到的--oplogLimit。它与--oplogReplay一起使用时,可以限制重放到的时间点。那么重要的问题就是如何找到灾难发生的时间点了。仍然是bsondump。如果你对Linux命令熟悉,可以在管道中直接操作。如果不行,那就先dump到文件中,再用文本编辑器打开慢慢找好了。我们需要找的内容是"op":"d",它表示进行了一次删除操作。可以发现,oplog.bson中有100000次删除操作,实际上是一条一条把记录删除掉,这也是为什么remove({})操作会这么慢。如果对一个集合进行drop()就会快得多,它进行的操作请读者自己尝试。
yaoxing ~/dump $ bsondump oplog.bson | grep "\"op\":\"d\"" | head
{"b":true,"h":{"$numberLong":""},"ns":"test.foo","o":{"_id":{"$oid":"5602cdf1befd4a4bfb4d149b"}},"op":"d","ts":{"$timestamp":{"t":1443024507,"i":1}},"v":}
...
此条记录中我们需要的是红色的$timestamp部分,它代表的发生这个操作的时间,也正是我们的--oplogLimit需要传入的时间,只是格式稍稍有变:
yaoxing ~ $ mongorestore -h 127.0.0.1 --oplogReplay --oplogLimit "1443024507:1" dump/
--24T00::09.533+ building a list of dbs and collections to restore from dump dir
--24T00::09.534+ reading metadata for test.foo from
--24T00::09.534+ restoring test.foo from
--24T00::09.659+ restoring indexes for collection test.foo from metadata
--24T00::09.659+ finished restoring test.foo ( documents)
--24T00::09.659+ replaying oplog
--24T00::11.548+ done
其中1443024507即是$timestamp中的"t",1即是$timestamp中的"i"。这样配置后oplog将会重放到这个时间点以前,即正好避开了第一条删除语句及其后面的操作,数据库停留在灾难前状态。验证一下:
rs0:PRIMARY> db.foo.count()
1.3 小结
结合上术知识,我们可以总结一些mongodb的备份准则(只针对replica或master/slave),满足这些准则,MongoDB就可以进行point-in-time恢复操作:
- 任意两次数据备份的时间间隔(第一次备份开始到第二次备份结束)不能超过oplog时间窗口覆盖范围。
- 在上次数据备份的基础上,在oplog时间窗口没有滑出上次备份结束的时间点前进行完整的oplog备份。请充分考虑oplog备份需要的时间,权衡服务器空间情况确定oplog备份间隔。
实际应用中的注意事项:
- 考虑到oplog时间窗口是个变化值,请关注oplog时间窗口的具体时间。
- 在靠近oplog时间窗口滑动出有效时间之前必须要有足够的时间dump出需要的oplog.rs,请预留足够的时间,不要顶满时间窗口再备份。
- 当灾难发生时,第一件事情就是要停止数据库的写入操作,以往oplog滑出时间窗口。特别是像上述这样的remove({})操作,瞬间就会插入大量d记录从而导致oplog迅速滑出时间窗口。
2. 外置方法
1. MMS/Cloud Manager
以前叫做MMS,监控功能免费,备份功能收费;前两个月更名为Cloud Manager并开始对监控功能收费,另外增加一个运维自动化机器人,挺傻瓜化的,有兴趣可以玩玩。不过既然收费,我相信我国国情决定了可能没有几个公司会愿意使用。因此不做重点介绍,只要知道它能提供任意时间点的数据恢复就可以了,功能强大,土豪公司欢迎使用。
2. 磁盘快照
常见的如LVM快照,是可以用来备份MongoDB的,但必须开启MongoDB的Journal并且Journal必须和数据文件在一个卷上。话说应该多数人都是开启Journal的吧?没有Journal的话断电是会丢失最多60s的数据的。
快照其实是一个较mongodump/mongorestore来得更快的备份方式,相当实用,但只能提供到备份时间点的数据恢复,作用有限。不展开细说,有兴趣可以查阅官方文档。理论上说,快照当然也可以结合oplog重放来达到point-in-time数据恢复的。但是本人没有亲身实验,有兴趣的朋友可以玩玩并做个介绍。
MongoDB的常规备份策略的更多相关文章
- mongodb备份策略
概述 数据库的备份非常非常非常重要!!!否则出问题连哭的机会有没有(欲哭无泪)今天主要是做一个mongodb的数据库备份. 1.关于备份 备份其实很简单,这里选择的是对mongodb中的某个库进行全备 ...
- odoo开发笔记 -- 数据库备份策略
odoo默认的数据库为postgresql数据库, PG是个非常强大的数据库,也是未来的一个趋势. 对于odoo的数据备份,odoo提供了自己的备份方式, 1. 从前台页面.输入odoo应用访问地址, ...
- TODO:MongoDB MySQL数据库备份
TODO:MongoDB MySQL数据库备份 1. MongoDB使用命令备份 mongodump进行整个数据库备份,主要用到的命令参数: -d 要备份的数据库 -o 输出的路径 ./mongodu ...
- DG环境数据库RMAN备份策略制定
DG环境数据库RMAN备份策略制定: 主库(Primary) 全库备份 归档备份 删除历史文件夹 备库(Standby) 删除归档 引用说明 主库(Primary) $ crontab -l 0 1 ...
- 记录一则RMAN备份策略修正案例
背景:在给某客户处理问题时,发现客户数据库的备份空间即将用尽,进一步查看发现是用户数据库的当前RMAN备份策略存在潜在问题,需要修改备份策略. 环境:SunOS 5.10 + Oracle 11.2. ...
- Oracle备份及备份策略
第二章. 了解备份的重要性 可以说,从计算机系统出世的那天起,就有了备份这个概念,计算机以其强大的速度处理能力,取代了很多人为的工作,但是,往往很多时候,它又是那么弱不禁风,主板上的芯片.主板电路.内 ...
- Oracle RMAN备份策略
建立增量备份:如果数据库运行于不归档模式下,只能在数据库干净关闭的情况下 ( 以 normal .immediate . transactional 方式关闭 ) 才能进行一致性的增量备份,如果数据库 ...
- MongoDB整库备份与还原以及单个collection备份、恢复方法
mongodb数据库维护离不开必要的备份.恢复操作,而且一般不会出错,所以我们在使用的时候大部分时候使用备份和恢复操作就可以了 mongodump.exe备份的原理是通过一次查询获取当前服务器快照 ...
- SVN服务器几种备份策略---重点svnsync备份---OK
配置管理的一个重要使命是保证数据的安全性,防止服务器应硬盘损坏.误操作造成数据无法恢复的灾难性后果.因此制定一个完整的备份策略非常重要. 一般来说,备份策略应规定如下几部分内容:备份频度.备份方式.备 ...
随机推荐
- SQL SERVER 数据库字段简单加密解密
--------加密函数----------- )) RETURNS varbinary(max) AS BEGIN declare @pwd varbinary(max) SELECT @pwd = ...
- front-end architecture
这些东西都需要管理,并且提供一种比较好的方案去维护.在JavaScript被模块化之后,也可以通过单元测试来控制它们的质量,并且把这个过程自动化,每次版本有变更之前,保证它们最基本的正确性.最终,需要 ...
- (转)C# BackgroundWorker组件的原理分析
原文地址:http://www.cnblogs.com/chaosimple/archive/2012/11/30/2796069.html 主要属性: 1.CancellationPending 获 ...
- DB通用类:MySQL通用类
Mysql类为网络上收集的,没有测试过.. using System; using System.Collections; using System.Collections.Generic; usin ...
- MapReduce高级编程
MapReduce 计数器.最值: 计数器 数据集在进行MapReduce运算过程中,许多时候,用户希望了解待分析的数据的运行的运行情况.Hadoop内置的计数器功能收集作业的主要统计信息,可以帮助用 ...
- 阿里云发送短信验证码php_SDK
1.登录阿里云账号下载——aliyun-dysms-php-sdk(我使用的php版本) 下载地址:https://help.aliyun.com/document_detail/55359.html ...
- tp3.2分页
tp3.2分页 使用tp3.2自带的分页类 <?php // +----------------------------------------------------------------- ...
- union与union all的用法给区别
用法: 当我们需要把两个或多个sql联合起来查询就用到了union或者union all 区别: 这两者的区别就在于union会自动的把多个sql查出来重复的排除掉,而union all这是会全部显示 ...
- WPF Stake
WPF中的StackPanel.WrapPanel.DockPanel 转:http://blog.sina.com.cn/s/blog_6c81891701017a34.html StackPane ...
- 硬盘读取不了-->>完美解决
说明:电脑装了两个硬盘,一个固态一个机械,装完系统之后读取不到机械硬盘的数据,网上很多人都说格式化之后分盘,但是!!!里面的数据咋办?千万别自己倒腾转换格式什么的,一不小心数据真的丢了,那就再也找不回 ...