一、MongoDB备份与恢复
不管是什么数据库,数据备份其实是一个基本操作。下面就说说mongodb如何备份数据,以及简单说一下mongodump在线备份是怎么保证数据的一致性的。
最简单的物理复制数据进行备份,不用多做解释,在创建MongoDB服务的时候,通过--dbpath
指定目录就是存放mongdb数据库文件目录,我们可以通过复制这些文件实现数据库的冷备。这种备份方式几乎对任何数据库都有用,简单粗暴。但像多数数据库一样,这个操作必须在mongod实例停止的情况下进行才能保证你得到的是正确状态下的数据库。否则在备份过程中如果有写操作,可能造成备份到的库处于非正常状态而不可使用。必须停止数据库这一点就造成了这个方法的可用性非常低,使用场景有限。
平滑关闭mongodb server的命令。
1 2 |
> use admin > db.shutdownServer() |
下面就说说mongo提供的几种备份和恢复工具,其次MongoDB在3.0用Go语言重写了所有的工具集。
mongodump备份数据库
首先对一个最常用的mongodump备份工具做一些介绍。主要注意的地方:
第一:mongodump可以使用多线程来进行并发dump,但单个集合还是只能单线程。
第二:使用oplog选项可以实现Point In Time备份。
1)常用命令格式
1 |
mongodump -h IP --port PORT -u USERNAME -p PASSWORD -d DATABASE -o DIR |
命令选项说明:
:指定主机地址。--host
<hostname><:port>
,
-h
<hostname><:port>
:指定实例端口。--port
<port>
--username
<username>
,
-u
<username>
:指定用户名。
:指定用户密码。--password
<password>
,
-p
<password>
--authenticationDatabase
<dbname>
:指定认证库。
--db
<database>
,
-d
<database>
:指定导出的数据库,不指定导出所有。
:指定导出的集合,不指定导出此库下的所有集合。--collection
<collection>
,
-c
<collection>
:指定查询条件进行导出数据。--query
<json>
,
-q
<json>
--queryFile
<path>
:当导出的查询条件比较复杂时,-q不是那么好用,这个时候可以查询json放入文件,指定文件进行查询。
:指定导出路径。--out
<path>
,
-o
<path>
--gzip
:压缩输出。
--oplog
:备份支持point-in-time snapshot。
2)导出所有数据库
1 2 3 4 5 6 7 8 9 |
$ mongodump -h 127.0.0.1 -o /home/zhangy/mongodb/ connected to: 127.0.0.1 Tue Dec 3 06:15:55.448 all dbs Tue Dec 3 06:15:55.449 DATABASE: test to /home/zhangy/mongodb/test Tue Dec 3 06:15:55.449 test.system.indexes to /home/zhangy/mongodb/test/system.indexes.bson Tue Dec 3 06:15:55.450 1 objects Tue Dec 3 06:15:55.450 test.posts to /home/zhangy/mongodb/test/posts.bson Tue Dec 3 06:15:55.480 0 objects .......... |
3)导出指定数据库
1 2 3 4 5 6 7 8 9 10 11 12 |
$ mongodump -h 192.168.1.108 -d tank -o /home/zhangy/mongodb/ connected to: 192.168.1.108 Tue Dec 3 06:11:41.618 DATABASE: tank to /home/zhangy/mongodb/tank Tue Dec 3 06:11:41.623 tank.system.indexes to /home/zhangy/mongodb/tank/system.indexes.bson Tue Dec 3 06:11:41.623 2 objects Tue Dec 3 06:11:41.623 tank.contact to /home/zhangy/mongodb/tank/contact.bson Tue Dec 3 06:11:41.669 2 objects Tue Dec 3 06:11:41.670 Metadata for tank.contact to /home/zhangy/mongodb/tank/contact.metadata.json Tue Dec 3 06:11:41.670 tank.users to /home/zhangy/mongodb/tank/users.bson Tue Dec 3 06:11:41.685 2 objects Tue Dec 3 06:11:41.685 Metadata for tank.users to /home/zhangy/mongodb/tank/users.metadata.json ............. |
mongorestore还原数据库
1)常用命令格式
1 |
mongorestore -h IP --port PORT -u USERNAME -p PASSWORD -d DATABASE --drop DIR |
命令选项说明:
--drop
:先删除所有的记录,然后恢复。
--gzip
:恢复压缩输入。
--oplogReplay
:replay oplog for point-in-time restore。
--oplogLimit=<seconds>[:ordinal]
: only include oplog entries before the provided Timestamp。
2)恢复所有数据库到mongodb中
1 |
$ mongorestore /home/zhangy/mongodb/ |
这里的路径是所有库的备份路径。
3)还原指定的数据库
1 2 3 4 5 |
# tank这个数据库的备份路径; $ mongorestore -d tank /home/zhangy/mongodb/tank/ # 将tank还原到tank_new数据库中; $ mongorestore -d tank_new /home/zhangy/mongodb/tank/ |
这二个命令,可以实现数据库的备份与还原,文件格式是json和bson的,无法指写到表备份或者还原。
4)还原指定数据库集合
1 |
$ mongorestore -d tank_new -c t_source /home/zhangy/mongodb/tank/t_source.bson |
mongoexport导出表,或者表中部分字段
1)常用命令格式
1 |
mongoexport -h IP --port PORT -u USERNAME -p PASSWORD -d DATABASE -c TABLE -f FIELD -q WHERE --type=csv -o FILENAME.csv |
上面的参数好理解,重点说一下:
-f
:导出指字段,以字号分割,-f name,email,age 导出 name,email,age 这三个字段。
-q
:可以根查询条件导出,-q ‘{ “uid” : {$gt: 100} }’ 导出uid大于100的数据。
--type=csv
:表示导出的文件格式为csv的,这个比较有用,因为大部分的关系型数据库都是支持csv,在这里有共同点。默认是json。
--queryFile
<path>
:当导出的查询条件比较复杂时,-q不是那么好用,这个时候可以查询json放入文件,指定文件进行查询。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
{ "$and" : [ { "nae" : "dkey" }, { "time" : { "$gt" : NumberLong(1562515200000) } }, { "time" : { "$lt" : NumberLong(1562947199000) } } ] } |
2)导出整张表
1 2 3 |
$ mongoexport -d tank -c users -o /home/zhangy/mongodb/tank/users.dat connected to: 127.0.0.1 exported 4 records |
3)导出表中部分字段
1 2 3 |
$ mongoexport -d tank -c users --type=csv -f uid,name,sex -o tank/users.csv connected to: 127.0.0.1 exported 4 records |
4)根据条件导出数据
1 2 3 |
$ mongoexport -d tank -c users -q '{uid:{$gt:1}}' -o tank/users.json connected to: 127.0.0.1 exported 3 records |
mongoimport导入表,或者表中部分字段
1)常用命令格式
1.1 还原整表导出的非csv文件
1 |
mongoimport -h IP --port PORT -u USERNAME -p PASSWORD -d DATABASE -c TABLE --upsert --drop FILENAME |
重点说一下--upsert
,其他参数上面的命令已有提到,--upsert
插入或者更新现有数据。
1.2 还原部分字段的导出文件
1 |
mongoimport -h IP --port PORT -u USERNAME -p PASSWORD -d DATABASE -c TABLE --upsertFields FIELD --drop FILENAME |
--upsertFields
跟--upsert
一样。
1.3 还原导出的csv文件
1 |
mongoimport -h IP --port PORT -u USERNAME -p PASSWORD -d DATABASE -c TABLE --type TYPE --headerline --upsert --drop FILENAME |
上面三种情况,还可以有其他排列组合的。
2)还原导出的表数据
1 2 3 |
$ mongoimport -d tank -c users --upsert tank/users.dat connected to: 127.0.0.1 Tue Dec 3 08:26:52.852 imported 4 objects |
3)部分字段的表数据导入
1 2 3 |
$ mongoimport -d tank -c users --upsertFields uid,name,sex tank/users.dat connected to: 127.0.0.1 Tue Dec 3 08:31:15.179 imported 4 objects |
4)还原csv文件
1 2 3 |
$ mongoimport -d tank -c users --type csv --headerline --file tank/users.csv connected to: 127.0.0.1 Tue Dec 3 08:37:21.961 imported 4 objects |
总体感觉,mongodb的备份与还原,还是挺强大的,虽然有点麻烦。
mongodump/mongostore和mongoexport/mongoimport的区别在哪里?
- mongoexport/mongoimport导入/导出的是JSON格式,而mongodump/mongorestore导入/导出的是BSON格式。
- JSON可读性强但体积较大,BSON则是二进制文件,体积小但对人类几乎没有可读性。
- 在一些mongodb版本之间,BSON格式可能会随版本不同而有所不同,所以不同版本之间用mongodump/mongorestore可能不会成功,具体要看版本之间的兼容性。当无法使用BSON进行跨版本的数据迁移的时候,使用JSON格式即mongoexport/mongoimport是一个可选项。跨版本的mongodump/mongorestore个人并不推荐,实在要做请先检查文档看两个版本是否兼容(大部分时候是的)。
- JSON虽然具有较好的跨版本通用性,但其只保留了数据部分,不保留索引,账户等其他基础信息。使用时应该注意。
总之,这两套工具在实际使用中各有优势,应该根据应用场景选择使用(好像跟没说一样)。但严格地说,mongoexport/mongoimport的主要作用还是导入/导出数据时使用,并不是一个真正意义上的备份工具。所以这里也不展开介绍了。
mongodump的--oplog
选项
注意这是replica set或者master/slave模式专用(standalone模式运行mongodb并不推荐),文档说明:
1 |
--oplog use oplog for taking a point-in-time snapshot |
point-in-time快照,我第一次看到这句话的时候的理解是它可以让数据库回到这段时间中的任意一个时间点的状态,但实际上并不是。它的实际作用是在导出的同时生成一个oplog.bson文件,存放在你开始进行dump到dump结束之间所有的oplog。oplog.bson具体有什么用先卖个关子,用图形来说明下oplog.bson的覆盖范围:
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。
1 2 3 4 |
use test for(var i = 0; i < 100000; i++) { db.foo.insert({a: i}); } |
然后在插入过程中模拟一次mongodump并指定--oplog
。
1 |
mongodump -h 127.0.0.1 --oplog |
注意--oplog
选项只对全库导出有效,所以不能指定-d
选项。因为整个实例的变更操作都会集中在local库中的oplog.rs集合中。
根据上面所说,从dump开始的时间系统将记录所有的oplog到oplog.bson中,所以我们得到这些文件:
1 2 3 |
$ ll dump/ -rw-r--r-- 1 yaoxing yaoxing 442470 Sep 14 17:21 oplog.bson drwxr-xr-x 2 yaoxing yaoxing 4096 Sep 14 17:21 test |
其中test是我们刚才使用的数据库,oplog.bson是导出期间进行的所有操作。如果对oplog.bson中的内容好奇,可以用bsondump工具来查看其中的内容,例如:
1 2 |
{"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中我们挑选第一条和最后一条内容出来观察
1 2 3 4 5 |
{"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中数据的最后一条。
1 |
{"_id":{"$oid":"55f834ae6b530b5854f9d73d"},"a":7863.0} |
可以发现,最终dump出的数据既不是最开始的状态,也不是最后的状态,而是中间某个随机状态。这正是因为集合不断变化造成的。
那么使用mongorestore来恢复:
1 2 3 4 5 6 7 8 |
$ mongorestore -h 127.0.0.1 --oplogReplay dump 2015-09-19T01:22:20.095+0800 building a list of dbs and collections to restore from dump dir 2015-09-19T01:22:20.095+0800 reading metadata for test.foo from 2015-09-19T01:22:20.096+0800 restoring test.foo from 2015-09-19T01:22:20.248+0800 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 2015-09-19T01:22:20.463+0800 done |
注意红字的两句,第一句表示test.foo集合中恢复了7864个文档;第二句表示重放了oplog中的所有操作。所以理论上foo应该有16857个文档(7864个来自foo.bson,剩下的来自oplog.bson)。验证一下:
1 2 |
test:PRIMARY> db.foo.count() 16857 |
这就是带oplog的mongodump的真正作用。
二、简单MongoDB备份脚本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
#!/bin/bash # # create variables today=`date +'%Y-%m-%d-%H'` sevenday=`date -d '7 day ago' +'%Y-%m-%d-%H'` # create backup directory backup_dir=/backup IP="127.0.0.1" PORT="27017 27018" USERNAME="" PASSWORD="" # backup data for i in $PORT;do mkdir $backup_dir/$i/${today} -p mongodump -h $IP --port $PORT --oplog -o $backup_dir/$i/${today} 2> /dev/null #mongodump -h $IP --port $PORT -u $USERNAME -p $PASSWORD --oplog -o $backup_dir/$i/${today} sleep 3 done # rsync to remote server for i in $PORT;do tar Jcf /tmp/$i-${today}.tar.xz $backup_dir/$i/${today} &> /dev/null if [ $? = 0 ];then rsync -avzc /tmp/$i-${today}.tar.xz 10.10.33.2::backup/mongodb/10.10.136.12 &> /dev/null rm -fr /tmp/$i-${today}.tar.xz 2> /dev/null else echo "backup file not exist!" fi done # delete 7 days before for i in $PORT;do rm -rf $backup_dir/$i/${sevenday} done |
<延伸>
mongodb point in time recovery