作者:任坤

现居珠海,先后担任专职 Oracle 和 MySQL DBA,现在主要负责 MySQL、mongoDB 和 Redis 维护工作。

本文来源:原创投稿

*爱可生开源社区出品,原创内容未经授权不得随意使用,转载请联系小编并注明来源。


1 背景

网上基于 mongo 单实例 PITR 的案例比较多,官档也有关于mongo cluser 的恢复步骤,但于基于 mongo cluster 的 PITR 案例几乎没有。本文基于实验环境,模拟线上环境完成一次针对 mongo cluster 的 PITR 。

原集群实例分布情况:

172.16.129.170 shard1 27017 shard2 27018 config 37017 mongos 47017
172.16.129.171 shard1 27017 shard2 27018 config 37017 mongos 47017
172.16.129.172 shard1 27017 shard2 27018 config 37017 mongos 47017

考虑到线上环境的实际数据会比较大,这里只将 shard 恢复成单实例,能供开发查询数据即可。 恢复后的实例分布情况:

172.16.129.173 shard1 27017 shard2 27018 config 37017 mongos 47017
172.16.129.174 config 37017
172.16.129.175 config 37017

mongo 版本为 percona 4.2.13,对每个 shard 实例和 config server 都部署 hot backup 定时备份脚本,同时对每个 shard 实例和 config server 部署 oplog 定时备份脚本。 构建测试数据,通过 mongos 登录集群,创建一个 hash 分片表并插入10条数据:

use admin
db.runCommand({"enablesharding":"renkun"})
sh.shardCollection("renkun.user", { id: "hashed" } )
use renkun
var tmp = [];
for(var i =0; i<10; i++){
tmp.push({ 'id':i, "name":"Kun " + i});
}
db.user.insertMany(tmp);

对 shard1 、shard2 和 config server 执行物理热备,继续插入数据:

use renkun
var tmp = [];
for(var i =10; i<20; i++){
tmp.push({ 'id':i, "name":"Kun " + i});
}
db.user.insertMany(tmp);

对 shard 、shard2 和 config server 进行 oplog 备份,oplog 备份文件里包含了以上所有操作。 此时 user 表有20条数据,并且 id 从0-20依次递增。我们的要求是将集群恢复到 max(id)=15 时的快照点。

官档给出的步骤是依次恢复出 config server、shard 和 mongo ,但是官档上没有前滚 oplog 的操作。

我们的案例中需要先从 shard 的 oplog 文件中解析出待恢复的时间点,因此调整一下顺序,先恢复 shard 再恢复 config server 。

2 恢复 shard 单实例

将 shard 的物理备份和 oplog 备份传输到目标机器,物理备份直接解压到数据目录即可启动。

2.1 确认待恢复快照点

对两个 shard 的 oplog 备份分别执行如下操作

bsondump oplog.rs.bson > oplog.rs.json
more oplog.rs.json | egrep "\"op\"\:\"\i\",\"ns\":\"renkun\.user\"" | grep "\"Kun
15\""

在 shard2 的 oplog 备份上查询到

{"ts":{"$timestamp":{"t":1623408268,"i":6}},"t":{"$numberLong":"1"},"h":
{"$numberLong":"0"},"v":{"$numberInt":"2"},"op":"i","ns":"renkun.user","ui":{"$binary":
{"base64":"uhlG0e4sRB+RUfFOzpMCEQ==","subType":"04"}},"wall":{"$date":
{"$numberLong":"1623408268694"}},"o":{"_id":{"$oid":"60c33e8c1c3edd59f25eecb5"},"id":
{"$numberDouble":"15.0"},"name":"Kun 15"}}

由此确认 id=15 插入时对应的时间戳为1623408268:6 ,但是 mongorestore 的--oplogLimit 指定的时间戳参数为开区间,即不会重放指定时间戳对应的 oplog entry ,因此要想恢复到这个时间点就必须对其再加1位,于是变成了1623408268:7 。

2.2 创建临时账号

用 root 账号登录 shard 实例,创建 1 个具有 __system 角色的用户,命名为 internal_restore 。 这个用户不仅用于前滚 oplog ,还用于修改 admin.system.version 以及删除 local db,root 账号默认没有权限执行这些操作。

use admin
db.createUser( { user: "internal_restore", pwd: "internal_restore", roles: [
"__system" ] })

用internal_restore登录两个shard实例,删除local db 。

use local
db.dropDatabase()

2.3 前滚oplog至指定时间点

mongorestore ‐h 127.0.0.1 ‐uinternal_restore ‐p"internal_restore" ‐‐port 27017 ‐‐
oplogReplay ‐‐oplogLimit "1623408268:7" ‐‐authenticationDatabase admin
/data/backup/202106111849_27017/local/oplog.rs.bson
mongorestore ‐h 127.0.0.1 ‐uinternal_restore ‐p"internal_restore" ‐‐port 27018 ‐‐
oplogReplay ‐‐oplogLimit "1623408268:7" ‐‐authenticationDatabase admin
/data/backup/202106111850_27018/local/oplog.rs.bson

分别登录 shard1 和 shard2 查看数据:

‐‐shard1 27017
> db.user.find().sort({"id":1})
{ "_id" : ObjectId("60c330322424943565780766"), "id" : 3, "name" : "Kun 3" }
{ "_id" : ObjectId("60c330322424943565780769"), "id" : 6, "name" : "Kun 6" }
{ "_id" : ObjectId("60c33032242494356578076b"), "id" : 8, "name" : "Kun 8" }
{ "_id" : ObjectId("60c33e8c1c3edd59f25eecb1"), "id" : 11, "name" : "Kun 11" }
{ "_id" : ObjectId("60c33e8c1c3edd59f25eecb2"), "id" : 12, "name" : "Kun 12" }
‐‐shard2 27018
> db.user.find().sort({"id":1})
{ "_id" : ObjectId("60c330322424943565780763"), "id" : 0, "name" : "Kun 0" }
{ "_id" : ObjectId("60c330322424943565780764"), "id" : 1, "name" : "Kun 1" }
{ "_id" : ObjectId("60c330322424943565780765"), "id" : 2, "name" : "Kun 2" }
{ "_id" : ObjectId("60c330322424943565780767"), "id" : 4, "name" : "Kun 4" }
{ "_id" : ObjectId("60c330322424943565780768"), "id" : 5, "name" : "Kun 5" }
{ "_id" : ObjectId("60c33032242494356578076a"), "id" : 7, "name" : "Kun 7" }
{ "_id" : ObjectId("60c33032242494356578076c"), "id" : 9, "name" : "Kun 9" }
{ "_id" : ObjectId("60c33e8c1c3edd59f25eecb0"), "id" : 10, "name" : "Kun 10" }
{ "_id" : ObjectId("60c33e8c1c3edd59f25eecb3"), "id" : 13, "name" : "Kun 13" }
{ "_id" : ObjectId("60c33e8c1c3edd59f25eecb4"), "id" : 14, "name" : "Kun 14" }
{ "_id" : ObjectId("60c33e8c1c3edd59f25eecb5"), "id" : 15, "name" : "Kun 15" }

数据已恢复完成,总共16条,max(id)=15

2.4 修改 admin.system.version

shard 由原来的3节点复制集变成了单实例,host ip 也发生了改变,因此要修改对应的元数据信息。以 internal_restore 用户登录两个 shard 实例,分别执行:

use admin
db.system.version.deleteOne( { _id: "minOpTimeRecovery" } )
db.system.version.find( {"_id" : "shardIdentity" } )
db.system.version.updateOne(
 { "_id" : "shardIdentity" },
{ $set :
{ "configsvrConnectionString" :
"configdb/172.16.129.173:37017,172.16.129.174:37017,172.16.129.175:37017"}
}
)

在被删除和修改之前,这两条记录分别存储的还是老的 config server 信息,详情如下:

{ "_id" : "shardIdentity", "shardName" : "repl", "clusterId" :
ObjectId("60c2c9b44497a4f2e02510fd"), "configsvrConnectionString" :
"configdb/172.16.129.170:37017,172.16.129.171:37017,172.16.129.172:37017" }
{ "_id" : "minOpTimeRecovery", "configsvrConnectionString" :
"configdb/172.16.129.170:37017,172.16.129.171:37017,172.16.129.172:37017", "minOpTime" :
{ "ts" : Timestamp(1623383008, 6), "t" : NumberLong(1) }, "minOpTimeUpdaters" : 0,
"shardName" : "repl" }

至此2个 shard server 已经完成恢复,下一步是恢复 config server 。

3 恢复 config server

将物理备份传输到目标机器,直接解压到数据目录即可用。 首先以单实例身份启动 config server ,用 root 账号登录后,创建1个具有__system角色的用户(同上)。

3.1 前滚 oplog

mongorestore ‐h 127.0.0.1 ‐uinternal_restore ‐p"internal_restore" ‐‐port 37017 ‐‐
oplogReplay ‐‐oplogLimit "1623408268:7" ‐‐authenticationDatabase admin
/data/backup/202106111850_37017/local/oplog.rs.bson

3.2 修改元数据

以 interal_restore 身份登录实例,修改集群的 shard 元数据信息,具体操作如下:

use local
db.dropDatabase()
use config
db.shards.find()
db.shards.updateOne({ "_id" : "repl" }, { $set : { "host" : "172.16.129.173:27017" } })
db.shards.updateOne({ "_id" : "repl2" }, { $set : { "host" : "172.16.129.173:27018" } })
db.shards.find()

3.3 启动集群

关闭 config server ,以集群模式启动,对应的配置文件开启如下参数:

sharding:
clusterRole: configsvr
replication:
oplogSizeMB: 10240
replSetName: configdb

登录后执行

rs.initiate()
rs.add("172.16.129.174:37017")
rs.add("172.16.129.175:37017")

此时 config server 变成1个3节点集群,也恢复完毕。

4 配置 mongos

可以直接把原环境的 mongos 配置文件复制过来,只需要修改一下 sharding 和 bindIp 参数即可

sharding:
configDB: "configdb/172.16.129.173:37017,172.16.129.174:37017,172.16.129.175:37017"
net:
port: 47017
bindIp: 127.0.0.1,172.16.129.173

启动后登录 mongos 查询 user 表,总共16条记录,max(id)=15,符合预期结果。

mongos> use renkun
switched to db renkun
mongos> db.user.find().sort({"id":1})
{ "_id" : ObjectId("60c330322424943565780763"), "id" : 0, "name" : "Kun 0" }
{ "_id" : ObjectId("60c330322424943565780764"), "id" : 1, "name" : "Kun 1" }
{ "_id" : ObjectId("60c330322424943565780765"), "id" : 2, "name" : "Kun 2" }
{ "_id" : ObjectId("60c330322424943565780766"), "id" : 3, "name" : "Kun 3" }
{ "_id" : ObjectId("60c330322424943565780767"), "id" : 4, "name" : "Kun 4" }
{ "_id" : ObjectId("60c330322424943565780768"), "id" : 5, "name" : "Kun 5" }
{ "_id" : ObjectId("60c330322424943565780769"), "id" : 6, "name" : "Kun 6" }
{ "_id" : ObjectId("60c33032242494356578076a"), "id" : 7, "name" : "Kun 7" }
{ "_id" : ObjectId("60c33032242494356578076b"), "id" : 8, "name" : "Kun 8" }
{ "_id" : ObjectId("60c33032242494356578076c"), "id" : 9, "name" : "Kun 9" }
{ "_id" : ObjectId("60c33e8c1c3edd59f25eecb0"), "id" : 10, "name" : "Kun 10" }
{ "_id" : ObjectId("60c33e8c1c3edd59f25eecb1"), "id" : 11, "name" : "Kun 11" }
{ "_id" : ObjectId("60c33e8c1c3edd59f25eecb2"), "id" : 12, "name" : "Kun 12" }
{ "_id" : ObjectId("60c33e8c1c3edd59f25eecb3"), "id" : 13, "name" : "Kun 13" }
{ "_id" : ObjectId("60c33e8c1c3edd59f25eecb4"), "id" : 14, "name" : "Kun 14" }
{ "_id" : ObjectId("60c33e8c1c3edd59f25eecb5"), "id" : 15, "name" : "Kun 15" }

至此,一个完整的 mongo cluster pitr 操作正式完成。

5 总结

mongo 4.X 开始引入事务,尤其是4.2支持跨分片事务,按照官档介绍恢复事务涉及的数据必须要用指定工具,上述操作暂不适用。


爱可生开源社区
426 声望207 粉丝

成立于 2017 年,以开源高质量的运维工具、日常分享技术干货内容、持续的全国性的社区活动为社区己任;目前开源的产品有:SQL审核工具 SQLE,分布式中间件 DBLE、数据传输组件DTLE。