[译文]还原MongoDB 分片集群到另一个环境
还原MongoDB 分片集群到另一个环境
如果您在数据库领域工作一段时间,您可能遇到需要基于现有的数据库,创建新的数据库的需求。最常见的例子我想是创建一个生产数据库的副本用于测试。对于MongoDB分片集群,官方文档介绍了从备份恢复分片集群的过程。但是,如果我们想要将数据还原到不同的主机,并重命名分片或者副本集,该如何处理?文档中提到了一些元数据重命名,但是这些步骤不完整。我将提供MongoDB4.2的详细步骤,但我应该告诉您使用它们的风险,因为这不是一个受支持的过程。我不会在这里详细介绍备份部分,但是通常我们可以停止平衡器,然后关闭每个分片的从节点,以及config server,并创建快照作为新集群的成员。您还可以查看Corrado的博客Percona Backup for MongoDB in Action(题外话:这个工具之前版本测试过,感觉很多问题,也希望有成功使用这个工具的大佬能够留言交流一波,虽然目前还是停留在单口相声被白嫖阶段>_<),以不停机的情况下来获得分片集群的一致性备份。
恢复MongoDB分片集群概述
在这个场景下,我假设我们要克隆一个集群,其中组件命名如下:
# config servers
prod-cfg/prod-cfg1.domain,prod-cfg2.domain,prod-cfg3.domain
# shards
prod-shard-01/prod-s01a.domain,prod-s01b.domain,prod-s01c.domain
prod-shard-02/prod-s02a.domain,prod-s02b.domain,prod-s02c.domain
...
目标集群有以下命名:
# config servers
stage-cfg/stage-cfg1.domain,stage-cfg2.domain,stage-cfg3.domain
# shards
stage-shard-01/stage-s01a.domain,stage-s01b.domain,stage-s01c.domain
stage-shard-02/stage-s02a.domain,stage-s02b.domain,stage-s02c.domain
...
这里需要注意的一点是,目标集群需要保持与源集群相同数量的分片。根据分片的数量,元数据编辑可能非常耗时,因此您应该使用某种自动化的方法。至少使用将终端复用,这样您可以将在单个选项卡中输出的命令广播到其他选项卡中(每个分片对应一个窗口)
还原Config Servers
首先您要做的是将config server恢复到新服务器上。一旦我们有了这些,遵循以下步骤:
1.以独立模式启动config server。重命名config server中的配置文件,注释与复制集有关选项
sed -i 's/sharding:/#sharding:/' /etc/mongod.conf
sed -i 's/ clusterRole: /# clusterRole: /' /etc/mongod.conf
sed -i 's/replication:/#replication:/' /etc/mongod.conf
sed -i 's/ replSetName: prod-cfg/# replSetName: stage-cfg/' /etc/mongod.conf
service mongod start
2.删除
local
数据库
mongo -u$user -p$password --port=27019 <<EOF
use local
db.dropDatabase()
EOF
3.更新
config.shards
集合的分片元数据
use config
db.shards.find({}).forEach(function(oldshard) {
newshard=oldshard
newshard["_id"]=oldshard["_id"].replace(/prod/gi,'stage');
newshard["host"]=oldshard["host"].replace(/prod/gi,'stage');
db.shards.save(newshard);
db.shards.remove({_id: oldshard["_id"]});
})
4.对未分片的集合修改主分片的名称
use config
db.databases.find().forEach(function(shard) {
shard["primary"]=shard["primary"].replace(/prod/gi,'stage');
db.databases.save(shard);
})
5.修改每个chunk的元数据(如果您有很多chunk这可能会持续一段时间)
db.chunks.find().forEach(function(shard) {
shard["shard"]=shard["shard"].replace(/prod/gi,'stage');
db.chunks.save(shard);
})
6.修改chunk的历史元数据
db.chunks.find().forEach(function(shard) {
shard["history"][0].shard=shard["history"][0].shard.replace(/prod/gi,'stage');
db.chunks.save(shard);
})
7.以新名称启动config server并初始化副本集
sed -i 's/#sharding:/sharding:/' /etc/mongod.conf
sed -i 's/# clusterRole: / clusterRole: /' /etc/mongod.conf
sed -i 's/#replication:/replication:/' /etc/mongod.conf
sed -i 's/# replSetName:/ replSetName:/' /etc/mongod.conf
service mongod restart
mongo -u$user -p$password --port=27019
rs.initiate(
{
_id: "stage-cfg",
configsvr: true,
members: [
{ _id : 0, host : "stage-cfg1.domain:27019" },
]
}
)
此时,可以安全地添加config server 副本集的其他成员。
rs.add("stage-cfg2.domain:27019")
rs.add("stage-cfg3.domain:27019")
接着,我们处理分片。
恢复分片
恢复每个分片的备份后,执行以下操作:
1.以独立模式启动每个分片成员
sed -i 's/sharding:/#sharding:/' /etc/mongod.conf
sed -i 's/ clusterRole: /# clusterRole: /' /etc/mongod.conf
sed -i 's/replication:/#replication:/' /etc/mongod.conf
sed -i 's/ replSetName: prod-shard-/# replSetName: stage-shard-/' /etc/mongod.conf
service mongod start
2.创建一个具有
__system
角色的临时用户。这用于编辑系统集合。
mongo -u$user -p$password --port=27018 <<EOF
db.createUser(
{
user: "mySystemUser",
pwd: "percona",
roles: [ "__system" ]
}
)
EOF
3.删除
local
数据库
db.auth("mySystemUser","percona")
use local
db.dropDatabase()
4.删除存储Oplog还原信息的文档(我们想在启动时避免任何类型的还原,只需保持数据不变)
use admin
db.system.version.deleteOne( { _id: "minOpTimeRecovery" } )
5.更新分片身份元数据
db.system.version.find({"_id" : "shardIdentity"}).forEach(function(shard) {
shard["configsvrConnectionString"]=shard["configsvrConnectionString"].replace(/prod/gi,'stage');
shard["shardName"]=shard["shardName"].replace(/prod/gi,'stage');
db.system.version.save(shard);
})
6.删除缓存的元数据集合的所有文档。基本上都是以
db.cache
开头
use config
db.cache.chunks.config.system.sessions.remove({})
db.cache.collections.remove({})
db.cache.databases.remove({})
db.cache.chunks["mydb.mycol1"].remove({})
db.cache.chunks["mydb.mycol2"].remove({})
...
7.重新以单节点副本集模式启动每个分片,并添加从节点。在本例中,我将它们添加为无投票权限的,因为我们不希望它们意外地成为主节点(至少在他们从具有实际数据的节点完成同步之前)
sed -i 's/#sharding:/sharding:/' /etc/mongod.conf
sed -i 's/# clusterRole: / clusterRole: /' /etc/mongod.conf
sed -i 's/#replication:/replication:/' /etc/mongod.conf
sed -i 's/# replSetName:/ replSetName:/' /etc/mongod.conf
service mongod restart
rs.initiate( { _id: "stage-s01", members: [ { _id : 0, host :"stage-s01a.domain:27018" }, ] })
rs.add({host:"stage-s01b.domain:27018", priority: 0, votes: 0 })
rs.add({host:"stage-s01c.domain:27018", priority: 0, votes: 0 })
8.最后,不要忘记删除我们之前创建的有权限的用户。
use admin
db.removeUser("mySystemUser")
最后的话
虽然上面的步骤是有效的,但是如果MongoDB能有一些内置的脚本完成这个繁琐的任务就好了。希望未来的版本包括这些内容。如果您有机会使用这个方法,请在下面留下您的评论,让我知道它的进展。