备份
1、备份方法
在 MongoDB 中有许多种方式对集群进行备份。作为MongoDB 的官方云服务,MongoDB Atlas 提供了连续备份和云供应商快照。连续备份采用集群中的数据增量备份,确保备份通常只比操作系统落后几秒。云供应商快照使用集群的云服务供应商(如 Amazon Web Services、Microsoft Azure 或 Google Cloud Platform)的快照功能提供本地化的备份存储。连续备份是大多数情况下的最佳备份解决方案。
MongoDB 还通过 Cloud Manager 和 Ops Manager 提供了备份的能力。Cloud Manager 是 MongoDB 的一个托管备份、监控和自动化服务。Ops Manager 是本地部署的解决方案,具有与 Cloud Manager 类似的功能。
对于直接管理 MongoDB 集群的个人和团队,有若干备份策略可供使用。
2、对服务器进行备份
有多种方法可以创建备份。但无论采用哪种方法,备份操作都会对系统造成压力:备份通常需要将所有数据读入内存中。因此,备份通常应该在副本集从节点而不是主节点上进行。如果是单机服务器,则应该在空闲时间进行备份。
除非另有说明,本节介绍的技术适用于任何 mongod,无论是单机服务器还是副本集成员。
2.1、文件系统快照
文件系统快照使用系统级别的工具创建保存 MongoDB数据文件的设备的副本。这种方式的完成过程耗时非常短,并且很可靠,但需要在 MongoDB 之外进行额外的系统配置。
MongoDB 3.2 中增加了在使用 WiredTiger 存储引擎时对 MongoDB 实例进行磁盘卷级别备份的支持,即使实例中的数据文件和日志文件位于不同的卷上。然而,为了备份的一致性,必须对数据库进行锁定,并且在备份过程中挂起对数据库的所有写操作。
在 MongoDB 3.2 之前,使用 WiredTiger 创建MongoDB 实例的卷级别备份需要数据文件和日志文件在同一个卷上。
快照的工作原理是在实时数据和特殊的快照卷之间创建指针。这些指针在理论上相当于"硬链接"。由于工作数据与快照不一致,快照进程会使用写时复制策略,因此快照只保存那些修改过的数据。
创建快照后,可以将快照映像挂载到文件系统中,并从快照中复制数据。产生的备份包含了所有数据的完整副本。
当进行快照的时候,数据库必须是有效的。这意味着数据库接受的所有写操作都需要完全写入磁盘:写入日志或数据文件。如果备份发生时有些写操作没有在磁盘上,那么备份将不会反映这些更改。
对于 WiredTiger 存储引擎,数据文件反映了截至最后一个检查点的一致性状态。检查点每分钟会出现一次。
快照会创建整个磁盘或卷的映像。除非需要备份整个系统,否则最好将 MongoDB 的数据文件、日志(如果启用了的话)以及配置隔离在一个不包含任何其他数据的逻辑磁盘上。另外,也可以将所有 MongoDB 的数据文件存储在专用设备上,这样在备份时就可以不复制那些无关的数据了。
确保将快照中的数据复制到其他系统,这样才能保证站点故障发生时数据的安全。
如果 mongod 实例启用了日志功能,那么可以使用任何类型的文件系统或卷/块级别的快照工具来创建备份。
如果允许在基于 Linux 的系统中对基础架构进行管理,那么可以使用 Linux 逻辑卷管理器(LVM)来配置系统,以提供磁盘打包和快照的能力。LVM 可以灵活地组合和拆分物理磁盘分区,从而支持动态调整大小的文件系统。同样也可以在云/虚拟化的环境中使用基于 LVM 的设置。
在 LVM 的初始设置中,首先需要将磁盘分区分配给物理卷(pvcreate),然后将其中一个或多个卷分配给卷组(vgcreate),接下来创建引用卷组的逻辑卷(lvcreate)。可以在逻辑卷上构建一个文件系统(mkfs),构建完成后可以挂载该逻辑卷来使用(mount)。
快照的备份与恢复过程
本节概述了在 Linux 系统中使用 LVM 进行简单备份的过程。虽然你系统中的工具、命令和路径可能(稍微)有些不同,但以下步骤提供了对备份操作的一个高层次的概述。
对于系统和基础设施的备份,以下过程仅作为一个指导。在生产环境中备份系统必须考虑许多特定于应用程序的要求和特定环境中的因素。
要使用 LVM 创建快照,需要以 root 用户身份运行如下命令:
js
# lvcreate --size 100M --snapshot --name mdb-snap01 /dev/vg0/mongodb
以上命令创建了一个名为 mdb-snap01 的 LVM 快照(使用 --snapshot 选项),该快照属于 vg0 卷组中的mongodb 卷,位于 /dev/vg0/mdb-snap01 中。系统、卷组及设备的位置和路径可能会根据操作系统的 LVM 配置略有不同。
由于参数为 --size 100M,因此快照的上限为 100MB。这个大小并不反映磁盘上的数据总量,而是反映了/dev/vg0/mongodb 的当前状态与快照(/dev/vg0/mdb-snap01)之间的差异。
当命令返回时,快照就创建完毕了。可以在任何时候直接从快照进行恢复,也可以创建新的逻辑卷,并从快照恢复到备用映像。
虽然快照对于快速创建高质量的备份非常有用,但它们并不是存储备份数据的理想格式。快照通常依赖并驻留于与原始磁盘映像相同的存储基础设施上。因此,将这些快照归档并存储在其他地方是至关重要的。
创建快照后,挂载快照并将数据复制到独立的存储。或者使用以下命令获取快照映像的块级副本。
js
# umount /dev/vg0/mdb-snap01
# dd if=/dev/vg0/mdb-snap01 | gzip > mdb-snap01.gz
这些命令会执行以下操作。
- 确保 /dev/vg0/mdb-snap01 设备没有被挂载。
- 使用 dd 命令执行整个快照映像的块级别复制,并将结果压缩到当前工作目录的一个 gzip 文件中。
dd 命令会在当前工作目录中创建一个较大的 .gz 文件。请确保在有足够剩余空间的文件系统中运行此命令。
要恢复使用 LVM 创建的快照,可以使用以下命令。
js
# lvcreate --size 1G --name mdb-new vg0
# gzip -d -c mdb-snap01.gz | dd of=/dev/vg0/mdb-new
# mount /dev/vg0/mdb-new /srv/mongodb
这些命令会执行以下操作。
- 在 /dev/vg0 卷组中创建名为 mdb-new 的新逻辑卷。新设备的路径为 /dev/vg0/mdb-new。可以使用不同的名称,并将 1G 更改为所需的卷大小。将 mdb-snap01.gz 文件解压缩到 mdb-new 磁盘映像。
- 将 mdb-new 磁盘映像挂载到 /srv/mongodb 目录下。根据需要修改挂载点,使其与 MongoDB 数据文件的位置或其他位置相对应。
恢复的快照会有一个旧的 mongod.lock 文件。如果没有从快照中删除该文件,那么 MongoDB 可能会认为这个旧的 mongod.lock 文件意味着之前系统存在非正常的关闭。如果在运行时启用了 storage.journal.enabled 并且没有使用 db.fsyncLock(),则不需要移除 mongod.lock文件。如果使用了 db.fsyncLock(),那么就需要将其删除。
要对未写入压缩的 .gz 文件的备份进行恢复,可以使用以下命令:
js
# umount /dev/vg0/mdb-snap01
# lvcreate --size 1G --name mdb-new vg0
# dd if=/dev/vg0/mdb-snap01 of=/dev/vg0/mdb-new
# mount /dev/vg0/mdb-new /srv/mongodb
还可以使用组合进程和 SSH 来实现脱机备份,其命令与前面解释过的流程相同,只是使用了 SSH 在远程系统中进行归档并对备份进行压缩:
js
umount /dev/vg0/mdb-snap01
dd if=/dev/vg0/mdb-snap01 | ssh username@example.com/bin/bash-c"gzip > opt/backup/
mdb-snap01.gz"
lvcreate --size 1G --name mdb-new vg0
ssh username@example.com gzip -d -c /opt/backup/mdb-snap01.gz | dd
of=/dev/vg0/mdb-new
mount /dev/vg0/mdb-new /srv/mongodb
从 MongoDB 3.2 开始,在使用 WiredTiger 对MongoDB 实例进行卷级别备份时,数据文件和日志不再需要位于同一个卷上。然而,在备份过程中数据库必须处于锁定状态,并且所有对数据库的写操作都必须被挂起,以确保备份的一致性。
如果 mongod 实例在运行时没有记录日志,或者日志文件位于单独的卷上,则必须将所有写操作刷新到磁盘并锁定数据库,以防止在备份过程中发生写操作。如果配有一个副本集,那么可以使用不接收读请求的从节点(比如一个隐藏的成员)来进行备份。
要实现这个目的,可以在 mongo shell 中调用db.fsyncLock() 方法:
js
> db.fsyncLock();
然后执行前面描述的备份操作。
快照完成后,在 mongo shell 中执行以下命令解锁数据库:
js
> db.fsyncUnlock();
下一节会更详细地描述这个过程。
2.2、复制数据文件
创建单服务器备份的另一种方法是复制数据目录中的所有内容。因为在没有文件系统支持的情况下是无法同时复制所有文件的,所以在进行复制时必须防止数据文件发生变化。这可以通过一个名为 fsyncLock 的命令来实现:
js
> db.fsyncLock();
该命令会对数据库进行锁定以禁止任何写操作,然后将所有脏数据刷新到磁盘上(fsync),确保数据目录中的文件具有最新的一致信息,不会发生更改。
运行此命令后,mongod 会将所有接收到的写操作放入队列中。在解锁之前,它不会再处理任何写操作。注意,这个命令会停止对所有数据库而不仅仅是连接的那个数据库的写操作。
fsyncLock 命令返回后,需要将数据目录中的所有文件复制到一个用于备份的位置。在 Linux 中,可以通过如下命令来实现:
bash
$ cp -R /data/db/* /mnt/external-drive/backup
确保将数据目录中的每个文件和文件夹都复制到备份位置。文件或目录的缺失可能会导致备份不可用或损坏。
完成数据的复制之后,就可以解锁数据库,使其再次进行写操作了:
js
> db.fsyncUnlock();
数据库将重新正常地开始处理写操作。
注意,身份验证和 fsyncLock 命令存在一些锁定问题。如果启用了身份验证,那么在调用 fsyncLock 和fsyncUnlock 之间不要关闭 shell。如果断开了连接,则可能无法重新进行连接,并且必须重新启动 mongod。fsyncLock 的设置在重启之间不会进行持久化,mongod总是以解锁的状态启动。
作为 fsyncLock 的替代方案,还可以关闭 mongod,复制文件,然后重新启动 mongod。关闭 mongod 可以有效地将所有更改刷新到磁盘,并防止在备份期间发生新的写操作。
如果从数据目录的副本进行恢复,那么请确保 mongod没有处于运行状态,且需要恢复的数据目录为空。将备份的数据文件复制到数据目录中,然后启动 mongod。例如,以下命令将恢复前面命令所备份的文件:
js
$ cp -R /mnt/external-drive/backup/* /data/db/
$ mongod -f mongod.conf
尽管会收到一些有关部分数据目录复制的警告,但如果使用了 --directoryperdb 选项并且知道要复制的内容和位置,则可以使用此方法备份单个数据库。要备份单个数据库(比如 myDB),只需复制整个 myDB 目录。部分数据目录复制只有在使用 --directoryperdb 选项时才可以使用。
将具有正确数据库名称的文件复制到相应的数据目录中可以恢复特定的数据库。要进行这样的部分恢复,需要确保数据库上一次是正常关闭的。如果发生了崩溃或硬关闭,则不要尝试从备份中恢复单个数据库,而是替换整个目录并启动 mongod,以允许日志文件进行重放。
永远不要同时使用 fsyncLock 和 mongodump(下一节会介绍)。如果数据库被锁定,那么 mongodump可能会永远被挂起,这取决于数据库正在做什么。
2.3、使用mongodump
进行单服务器备份的最后一种方法是使用 mongodump。最后介绍 mongodump 是因为它有一些缺点。这种方式相对来说比较慢(无论是创建备份还是从备份中恢复),而且在处理副本集时也存在一些问题。然而,它也有一些优点:在备份单个数据库、集合甚至集合子集时它是很好的选择。
mongodump 有多个选项,可以运行 mongodump --help 获知具体信息。这里将重点讨论那些和备份有关的选项。
要备份所有数据库,只需运行 mongodump。如果在mongod 的同一台机器上运行 mongodump,那么可以简单地指定 mongod 的运行端口:
bash
$ mongodump --dbpath /data/db
如果 mongod 正在运行,则不应该使用 --dbpath 选项。
mongodump 的一个问题是它不是瞬时备份:系统可能在备份发生时进行写操作。因此,可能会出现这种情况:用户 A 开始了一个备份,从而导致 mongodump 对数据库A 进行了转储,但是在这期间用户 B 删除了数据库 A。然而,mongodump 已经对数据库 A 进行了转储,于是最终得到的数据快照与原始服务器的状态不一致。
为了避免这种情况,如果运行 mongod 时使用了 --replSet 选项,则可以使用 mongodump 的 --oplog 选项。这会跟踪转储发生时服务器上发生的所有操作,从而在恢复备份时重放这些操作。这样就可以从源服务器上得到某一时间点的一致性快照。
如果给 mongodump 传递一个副本集的连接字符串(如"setName/seed1,seed2,seed3"),那么它会自动选择主节点来进行转储。如果想使用从节点,则可以指定一个读偏好。读偏好可以通过 --uri 连接字符串、uri readPreferenceTags 选项或命令行选项 --readPreference 来指定。关于各种设置和选项的详细信息,请参阅 mongodump 的 MongoDB 文档页面。
从 mongodump 备份中恢复需要使用 mongorestore 工具:
bash
$ mongorestore -p 31000 --oplogReplay dump/
如果转储数据库时使用了 --oplog 选项,则运行mongorestore 时必须使用 --oplogReplay 选项以获取某一时间点的快照。
如果要替换正在运行的服务器上的数据,那么你可能希望(也可能不希望)使用 --drop 选项,该选项会在恢复一个集合之前先删除它。
从 MongoDB 4.2 开始,不能再使用 mongodump
从 MongoDB 4.2 开始,不能再使用 mongodump或 mongorestore 作为分片集群的备份策略了。这些工具无法维护跨分片事务的原子性保证。
使用mongodump和mongorestore移动集合和数据库
可以从转储中恢复到完全不同的数据库和集合中。如果不同的环境使用不同的数据库名称(比如 dev 和prod),但集合名称相同,则这会非常有用。
要将 .bson 文件恢复到特定的数据库和集合中,需要在命令行中指定目标:
bash
$ mongorestore --db newDb --collection someOtherColl dump/oldDB/oldColl.bson
利用这些工具的归档特性,也可以借由 SSH 来使用这些工具以执行数据迁移,而不需要任何磁盘 I/O。以前必须先备份到磁盘,然后将这些备份文件复制到目标服务器,接下来在该服务器上运行mongorestore 来恢复备份,现在这 3 个步骤可以简化为一个操作:
bash
$ ssh eoin@proxy.server.com mongodump --host source.server.com\ --archive
| ssh eoin@target.server.com mongorestore --archive
还可以将压缩与这些工具的归档特性结合起来,以进一步减少执行数据迁移时发送信息的大小。下面是使用这些工具的归档和压缩特性来实现的同一个 SSH数据迁移示例。
bash
$ ssh eoin@proxy.server.com mongodump --host source.server.com\ --archive
--gzip | ssh eoin@target.server.com mongorestore --archive --gzip
管理唯一索引带来的混乱
在任何集合中如果存在唯一索引(除了 "_id" 之外),则应该考虑使用 mongodump 和mongorestore 之外的备份方式。唯一索引要求数据在复制期间不会以违反唯一约束的方式进行更改。确保这一点最安全的方法是先"冻结"数据,然后按照前面介绍过的方法进行备份。
如果决定使用 mongodump 和 mongorestore,那么在从备份恢复时可能需要对数据进行预处理。
3、副本集的特殊注意事项
在备份副本集时,除了所需数据之外,还需要获取副本集的状态,以确保生成整个部署集群的准确时间点快照。
通常,应该对从节点进行备份:这可以减轻主节点的负载,并且锁定从节点不会影响应用程序(只要应用程序不向从节点发送读请求)。可以使用前面介绍的 3 种方法中的任何一种来备份副本集成员,但是建议使用文件系统快照或复制数据文件的方式。这两种技术在应用到副本集从节点上时不需要做任何修改。
当启用了复制时,mongodump 的使用就不那么简单了。首先,如果使用了 mongodump,则必须使用 --oplog选项进行备份,以获得某个时间点的快照,否则备份的状态会与集群中任何其他成员的状态都不匹配。在从 mongodump 备份恢复时,还必须创建一份 oplog,否则被恢复的成员就不知道它被同步到哪里了。
要从 mongodump 备份恢复副本集成员,需要将目标副本集成员作为单机服务器启动,该服务器上要有一个空的数据目录,并使用 --oplogReplay 选项在其上运行mongorestore(如上一节所述)。现在它应该有一个完整的数据副本了,但是还需要一份 oplog。使用createCollection 命令来创建 oplog:
js
> use local
> db.createCollection("oplog.rs", {"capped" : true, "size" : 10000000})
以字节为单位指定集合的大小。要获得关于 oplog 大小的建议,请参阅 13.4.6 节。现在需要填充 oplog。最简单的方法是将转储中的 oplog.bson 备份文件恢复到local.oplog.rs 集合中:
bash
$ mongorestore -d local -c oplog.rs dump/oplog.bson
注意,这不是对 oplog 本身的转储(dump/local/oplog.rs.bson),而是转储期间发生的oplog 操作。在完成 mongorestore 后,可以将这个服务器作为副本集成员重新启动。
4、分片集群的特殊注意事项
使用本章介绍的方法备份分片群集时,主要需要考虑的是那些只能在它们处于活动状态时对其进行备份的场景,而又不可能对活动状态下的分片群集进行"完美"备份:因为无法在某个时间点获得集群整个状态的快照。然而,通常情况下都会避开这个限制,因为随着集群越来越大,从备份中恢复整个集群的可能性会越来越小。因此,在处理分片集群时,我们会将重点放在对部分组件的备份上:单独备份配置服务器和副本集。如果需要将整个集群备份到某个特定的时间点,或者希望使用自动化解决方案,则可以使用 MongoDB 的 Cloud Manager 或 Atlas 中的备份功能。
在分片集群上执行任何这些操作(备份或恢复)之前都需要先关闭均衡器。当数据块处于迁移过程中时是无法获得一致性快照的。
4.1、备份和恢复整个集群
当集群非常小或处于开发阶段时,你可能希望转储并恢复整个集群。可以关闭均衡器,然后在 mongos 上运行mongodump 来实现这一点。这会在 mongodump 所运行的任何机器上创建所有分片的备份。
要从这种类型的备份中恢复数据,需要连接到 mongos上来运行 mongorestore。
或者,也可以在关闭均衡器之后对每个分片和配置服务器进行文件系统或数据目录备份。然而,这将不可避免地在稍有不同的时间点获得每个备份,这可能是个问题。此外,一旦打开均衡器并发生迁移,那么从某个分片中备份的一些数据就可能会消失。
4.2、备份和恢复单个分片
更多时候,只需要恢复集群中的单个分片。如果你不是很挑剔,则可以使用之前描述的其中一种单服务器处理方法从该分片的备份中恢复。
然而,有一个重要的问题需要注意。假设在周一对集群进行了备份。到了周四,磁盘发生了损坏,必须从备份中进行恢复。在这段时间内,新的数据块可能移动到了这个分片上。而周一的分片备份并没有包含这些新的数据块。也许可以使用配置服务器的备份来确定这些消失的数据块在周一时的位置,但这比简单地恢复分片要困难得多。在大多数情况下,恢复分片并忽略这些块中的数据是更可取的选择。
可以直接连接到一个分片,从备份中进行恢复(而不是通过 mongos)。