暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

使用 Percona Backup for MongoDB 将 MongoDB 集群移动到不同的环境

原创 谭磊Terry 恩墨学院 2022-07-29
815

Percona Backup for MongoDB (PBM) 是用于分片和非分片集群的分布式备份和恢复工具。在 1.8.0 中,我们添加了 replset-remapping 功能,允许您在新的兼容集群拓扑上恢复数据。

新环境可以有不同的 replset 名称和/或在不同的主机和端口上服务。PBM 为您处理这项艰巨的工作。使此类迁移与通常的还原无法区分。在这篇博文中,我将向您展示如何实际迁移到新集群。

问题

通常要更改集群拓扑,您需要执行大量手动步骤。PBM 减少了这个过程。

让我们看一下我们将拥有一个初始集群和一个期望集群的情况。

初始集群:

configsrv: "configsrv/conf:27017"
shards:
  - "rs0/rs0:27017,rs1:27017,rs2:27017"
  - "extra-shard/extra:27018"

集群由configsrv configsvr replset和一个节点和两个碎片组成:rs0(replset 中的 3 个节点)和extra-shard (replset 中的 1 个节点)。名称、主机和端口在集群中不是常规的,但我们会解决这个问题。

目标集群:

configsrv: "cfg/cfg0:27019"
shards:
  - "rs0/rs00:27018,rs01:27018,rs02:27018"
  - "rs1/rs10:27018,rs11:27018,rs12:27018"
  - "rs2/rs20:27018,rs21:27018,rs22:27018"

这里我们有cfg configsvr replset,它有一个节点和 3 个分片 rs0–rs2,其中每个分片是 3 个节点的 replset。

想想你怎么能做到这一点。

使用 PBM,我们只需要部署集群和使用 PBM 1.5.0 或更高版本进行的逻辑备份。下面的简单命令将完成剩下的工作:

pbm restore $BACKUP_NAME --replset-remapping "cfg=configsrv,rs1=extra-shard"

迁移行动

让我向您展示它在实践中的样子。我将在文章末尾提供详细信息。在repo中,您可以找到此处使用的所有配置、脚本和输出。

如上所述,我们需要备份。为此,我们将部署一个集群、种子数据,然后进行备份。

部署初始集群

$> initial/deploy >initial/deploy.out
$> docker compose -f "initial/compose.yaml" exec pbm-conf \
     pbm status -s cluster
 
Cluster:
========
configsvr:
  - configsvr/conf:27019: pbm-agent v1.8.0 OK
rs0:
  - rs0/rs00:27017: pbm-agent v1.8.0 OK
  - rs0/rs01:27017: pbm-agent v1.8.0 OK
  - rs0/rs02:27017: pbm-agent v1.8.0 OK
extra-shard:
  - extra-shard/extra:27018: pbm-agent v1.8.0 OK

集群已准备就绪,我们可以添加一些数据。

种子数据

我们将在自然数序列中插入前 1000 个数字:1 – 1000。

$> mongosh "mongo:27017/rsmap" --quiet --eval "
     for (let i = 1; i <= 1000; i++)
       db.coll.insertOne({ i })" >/dev/null

获取数据状态

这些文档应在插入时跨所有分片进行分区。让我们看看,一般来说,如何。我们将在所有分片上使用“dbHash”命令来获取集合的状态。这将有助于以后的验证。

我们还将快速检查分片和 mongos。

$> initial/dbhash >initial/dbhash.out && cat initial/dbhash.out
 
# rs00:27017  db.getSiblingDB("rsmap").runCommand("dbHash").collections
{ "coll" : "550f86eb459b4d43de7999fe465e39e0" }
# rs01:27017  db.getSiblingDB("rsmap").runCommand("dbHash").collections
{ "coll" : "550f86eb459b4d43de7999fe465e39e0" }
# rs02:27017  db.getSiblingDB("rsmap").runCommand("dbHash").collections
{ "coll" : "550f86eb459b4d43de7999fe465e39e0" }
# extra:27018  db.getSiblingDB("rsmap").runCommand("dbHash").collections
{ "coll" : "4a79c07e0cbf3c9076d6e2d81eb77f0a" }
# rs00:27017  db.getSiblingDB("rsmap").coll
    .find().sort({ i: 1 }).toArray()
    .reduce(([count = 0, seq = true, next = 1], { i }) =>
             [count + 1, seq && next == i, i + 1], [])
    .slice(0, 2)
[ 520, false ]
# extra:27018  db.getSiblingDB("rsmap").coll
    .find().sort({ i: 1 }).toArray()
    .reduce(([count = 0, seq = true, next = 1], { i }) =>
             [count + 1, seq && next == i, i + 1], [])
    .slice(0, 2)
[ 480, false ]
# mongo:27017
[ 1000, true ]

所有rs0成员都有相同的数据。因此,辅助节点正确地从主节点复制。

在 initial/dbhash 脚本中使用的 quickcheck.js 描述了我们的文档。它返回文档的数量以及这些文档是否构成自然数序列。

我们有用于备份的数据。是时候做备份了。

进行备份

$> docker compose -f initial/compose.yaml exec pbm-conf bash
pbm-conf> pbm backup --wait
 
Starting backup '2022-06-15T08:18:44Z'....
Waiting for '2022-06-15T08:18:44Z' backup.......... done
 
pbm-conf> pbm status -s backups
 
Backups:
========
FS  /data/pbm
  Snapshots:
    2022-06-15T08:18:44Z 28.23KB <logical> [complete: 2022-06-15T08:18:49Z]

我们有备份。迁移到新集群就足够了。

让我们销毁初始集群并部署目标环境。(销毁初始集群不是必需的。我只是不想在上面浪费资源。)

部署目标集群

pbm-conf> exit
$> docker compose -f initial/compose.yaml down -v >/dev/null
$> target/deploy >target/deploy.out

让我们检查一下 PBM 状态。

PBM 状态

$> docker compose -f target/compose.yaml exec pbm-cfg0 bash
pbm-cfg0> pbm config --force-resync  # ensure agents sync from storage
 
Storage resync started
 
pbm-cfg0> pbm status -s backups
 
Backups:
========
FS  /data/pbm
  Snapshots:
    2022-06-15T08:18:44Z 28.23KB <logical> [incompatible: Backup doesn't match current cluster topology - it has different replica set names. Extra shards in the backup will cause this, for a simple example. The extra/unknown replica set names found in the backup are: extra-shard, configsvr. Backup has no data for the config server or sole replicaset] [2022-06-15T08:18:49Z]

正如预期的那样,它与新部署不兼容。

看看如何让它发挥作用

解决 PBM 状态

pbm-cfg0> export PBM_REPLSET_REMAPPING="cfg=configsvr,rs1=extra-shard"
pbm-cfg0> pbm status -s backups
 
Backups:
========
FS  /data/pbm
  Snapshots:
    2022-06-15T08:18:44Z 28.23KB <logical> [complete: 2022-06-15T08:18:49Z]

恢复

pbm-cfg0> pbm restore '2022-06-15T08:18:44Z' --wait
 
Starting restore from '2022-06-15T08:18:44Z'....Started logical restore.
Waiting to finish.....Restore successfully finished!

–wait 标志会阻止 shell 会话,直到还原完成。你等不及了,稍后再检查一下。

pbm-cfg0> pbm list --restore
 
Restores history:
  2022-06-15T08:18:44Z

到目前为止一切进展顺利。快完成了

让我们验证数据。

数据验证

pbm-cfg0> exit
$> target/dbhash >target/dbhash.out && cat target/dbhash.out
 
# rs00:27018  db.getSiblingDB("rsmap").runCommand("dbHash").collections
{ "coll" : "550f86eb459b4d43de7999fe465e39e0" }
# rs01:27018  db.getSiblingDB("rsmap").runCommand("dbHash").collections
{ "coll" : "550f86eb459b4d43de7999fe465e39e0" }
# rs02:27018  db.getSiblingDB("rsmap").runCommand("dbHash").collections
{ "coll" : "550f86eb459b4d43de7999fe465e39e0" }
# rs10:27018  db.getSiblingDB("rsmap").runCommand("dbHash").collections
{ "coll" : "4a79c07e0cbf3c9076d6e2d81eb77f0a" }
# rs11:27018  db.getSiblingDB("rsmap").runCommand("dbHash").collections
{ "coll" : "4a79c07e0cbf3c9076d6e2d81eb77f0a" }
# rs12:27018  db.getSiblingDB("rsmap").runCommand("dbHash").collections
{ "coll" : "4a79c07e0cbf3c9076d6e2d81eb77f0a" }
# rs20:27018  db.getSiblingDB("rsmap").runCommand("dbHash").collections
{ }
# rs21:27018  db.getSiblingDB("rsmap").runCommand("dbHash").collections
{ }
# rs22:27018  db.getSiblingDB("rsmap").runCommand("dbHash").collections
{ }
# rs00:27018  db.getSiblingDB("rsmap").coll
    .find().sort({ i: 1 }).toArray()
    .reduce(([count = 0, seq = true, next = 1], { i }) =>
             [count + 1, seq && next == i, i + 1], [])
    .slice(0, 2)
[ 520, false ]
# rs10:27018  db.getSiblingDB("rsmap").coll
    .find().sort({ i: 1 }).toArray()
    .reduce(([count = 0, seq = true, next = 1], { i }) =>
             [count + 1, seq && next == i, i + 1], [])
    .slice(0, 2)
[ 480, false ]
# rs20:27018  db.getSiblingDB("rsmap").coll
    .find().sort({ i: 1 }).toArray()
    .reduce(([count = 0, seq = true, next = 1], { i }) =>
             [count + 1, seq && next == i, i + 1], [])
    .slice(0, 2)
[ ]
# mongo:27017
[ 1000, true ]

如您所见,rs2 分片是空的。另外两个具有与初始集群相同的 dbHash 和快速检查结果。我认为平衡器可以说明这一点

平衡器状态

$> mongosh "mongo:27017" --quiet --eval "sh.balancerCollectionStatus('rsmap.coll')"
 
{
  balancerCompliant: false,
  firstComplianceViolation: 'chunksImbalance',
  ok: 1,
  '$clusterTime': {
    clusterTime: Timestamp({ t: 1655281436, i: 1 }),
    signature: {
      hash: Binary(Buffer.from("0000000000000000000000000000000000000000", "hex"), 0),
      keyId: Long("0")
    }
  },
  operationTime: Timestamp({ t: 1655281436, i: 1 })
}

我们知道该怎么做。启动平衡器并再次检查状态。

$> mongosh "mongo:27017" --quiet --eval "sh.startBalancer().ok"
 
1
 
$> mongosh "mongo:27017" --quiet --eval "sh.balancerCollectionStatus('rsmap.coll')"
 
{
  balancerCompliant: true,
  ok: 1,
  '$clusterTime': {
    clusterTime: Timestamp({ t: 1655281457, i: 1 }),
    signature: {
      hash: Binary(Buffer.from("0000000000000000000000000000000000000000", "hex"), 0),
      keyId: Long("0")
    }
  },
  operationTime: Timestamp({ t: 1655281457, i: 1 })
}
 
$> target/dbhash >target/dbhash-2.out && cat target/dbhash-2.out
 
# rs00:27018  db.getSiblingDB("rsmap").runCommand("dbHash").collections
{ "coll" : "550f86eb459b4d43de7999fe465e39e0" }
# rs01:27018  db.getSiblingDB("rsmap").runCommand("dbHash").collections
{ "coll" : "550f86eb459b4d43de7999fe465e39e0" }
# rs02:27018  db.getSiblingDB("rsmap").runCommand("dbHash").collections
{ "coll" : "550f86eb459b4d43de7999fe465e39e0" }
# rs10:27018  db.getSiblingDB("rsmap").runCommand("dbHash").collections
{ "coll" : "4a79c07e0cbf3c9076d6e2d81eb77f0a" }
# rs11:27018  db.getSiblingDB("rsmap").runCommand("dbHash").collections
{ "coll" : "4a79c07e0cbf3c9076d6e2d81eb77f0a" }
# rs12:27018  db.getSiblingDB("rsmap").runCommand("dbHash").collections
{ "coll" : "4a79c07e0cbf3c9076d6e2d81eb77f0a" }
# rs20:27018  db.getSiblingDB("rsmap").runCommand("dbHash").collections
{ "coll" : "6a54e10a5526e0efea0d58b5e2fbd7c5" }
# rs21:27018  db.getSiblingDB("rsmap").runCommand("dbHash").collections
{ "coll" : "6a54e10a5526e0efea0d58b5e2fbd7c5" }
# rs22:27018  db.getSiblingDB("rsmap").runCommand("dbHash").collections
{ "coll" : "6a54e10a5526e0efea0d58b5e2fbd7c5" }
# rs00:27018  db.getSiblingDB("rsmap").coll
    .find().sort({ i: 1 }).toArray()
    .reduce(([count = 0, seq = true, next = 1], { i }) =>
             [count + 1, seq && next == i, i + 1], [])
    .slice(0, 2)
[ 520, false ]
# rs10:27018  db.getSiblingDB("rsmap").coll
    .find().sort({ i: 1 }).toArray()
    .reduce(([count = 0, seq = true, next = 1], { i }) =>
             [count + 1, seq && next == i, i + 1], [])
    .slice(0, 2)
[ 480, false ]
# rs20:27018  db.getSiblingDB("rsmap").coll
    .find().sort({ i: 1 }).toArray()
    .reduce(([count = 0, seq = true, next = 1], { i }) =>
             [count + 1, seq && next == i, i + 1], [])
    .slice(0, 2)
[ 229, false ]
# mongo:27017
[ 1000, true ]

有趣的。rs2 分片有一些数据。但是,rs1和rs2没有改变。预计 mongos 会将一些块移动到rs2并更新路由器配置。物理删除分片上的块是一个单独的步骤。这就是为什么直接在分片上查询数据是不准确的。数据随时可能消失。尽管有路由器配置,但光标此时会返回 replset 中的所有可用文档。

无论如何,我们不应该再关心它了。现在由 mongos/mongod 负责更新路由器配置,查询正确的分片,并根据需要从分片中删除移动的块。最后,我们通过 mongos 得到了有效的数据。

但是等等,我们没有做备份!永远不要忘记做另一个可靠的备份。

进行新备份

最好更改存储,这样我们就可以在不同的地方为新部署进行备份,并且不会进一步看到初始集群中不兼容备份的错误。

$> pbm config --file "$NEW_PBM_CONFIG" >/dev/null
$> pbm config --force-resync >/dev/null
$> pbm backup -w >/dev/null
pbm-cfg0> pbm status -s backups
 
Backups:
========
FS  /data/pbm
  Snapshots:
    2022-06-15T08:25:44Z 165.34KB <logical> [complete: 2022-06-15T08:25:49Z]

现在我们完成了。并且可以睡得更好。

还有一件事:可能的错误配置

让我们回顾另一个想象的案例来解释所有可能的错误。

初始集群:cfg、rs0、rs1、rs2、rs3、rs4、rs5

目标集群:cfg、rs0、rs1、rs2、rs3、rs4、rs6

如果我们应用重映射:“ rs0=rs0,rs1=rs2,rs2=rs1,rs3=rs4 ” ,我们会得到类似 “missed replsets: rs3 , rs5 ”的错误。和rs6无关。

遗漏的rs5应该很明显:备份拓扑有rs5 replset,但在目标上遗漏了。并且目标rs6没有要从中恢复的数据。添加rs6=rs5 可以解决此问题。

但是错过的rs3可能会令人困惑。让我们形象化:

init | curr
-----+-----
cfg     cfg  # unchanged
rs0 --> rs0  # mapped. unchanged
rs1 --> rs2
rs2 --> rs1
rs3 -->      # err: no shard
rs4 --> rs3
     -> rs4  # ok: no data
rs5 -->      # err: no shard
     -> rs6  # ok: no data

当我们将备份从rs4重新映射到rs3时,目标rs3被保留。备份中的rs3现在没有目标 replset。只需将rs3重新映射到可用的rs4也可以修复它。

这种保留避免了数据重复。这就是我们使用 mongos 快速检查的原因。

细节

兼容拓扑

简单来说,兼容拓扑等于或在目标部署中有更多的分片。在我们的示例中,我们最初有 2 个分片,但将它们恢复为 3 个分片。PBM 仅在两个分片上恢复数据。MongoDB 可以稍后在启用平衡器时将其与剩余的分片一起分发(sh.startBalancer() )。replset 成员的数量无关紧要,因为 PBM 从成员(每个 replset)获取备份并将其仅还原到主要成员。其他承载数据的成员从主数据库复制数据。因此,您可以从多成员 replset 进行备份,然后将其还原到单个成员 replset。

您无法恢复到不同的 replset 类型,例如从shardsvr到configsvr。

预配置环境

应该在添加所有分片的情况下部署集群。应提前添加和分配用户和权限。PBM 代理应该配置到相同的存储并且可以从新集群访问。

注意:PBM 代理将备份元数据存储在存储中,并将缓存保存在 MongoDB 中。pbm config –force-resync允许您从存储中刷新缓存。在部署后立即在新集群上执行此操作,以查看从初始集群生成的备份/oplog 块。

了解 replset 重新映射

您可以通过 –replset-remapping 标志或PBM_REPLSET_REMAPPING 环境变量重新映射 replset 名称。如果两者都设置,则标志具有优先权。

对于完全恢复、时间点恢复和 oplog 重播,PBM CLI 将映射作为命令中的参数发送。每个命令都有一个单独的显式映射(或没有)。它只能通过 CLI 完成。代理不使用环境变量,也没有标志。

pbm status 和 pbm list 使用 flag/envvar 重新映射备份/oplog 元数据中的 replsets 并将此映射应用于当前部署以正确显示它们。如果备份和当前的 replset 名称不匹配,pbm list将不会显示这些备份,并且pbm status会打印一个缺少 replset 名称的错误。

使用重新映射进行还原仅适用于逻辑备份。

PBM 如何做到这一点?

在恢复过程中,PBM 检查当前拓扑,并按名称分别将成员的快照和 oplog 块分配给每个 shard/replset。重新映射会更改默认分配。

还原完成后,PBM 代理会同步路由器配置以使还原的数据“本机”到该集群。

幕后

config.shards 集合描述了当前的拓扑。PBM 使用它来知道要在哪里恢复以及要恢复什么。PBM 不会修改该集合。但恢复的数据包含一些其他用于初始拓扑的路由器配置。

我们更新了两个集合以在恢复的数据中用新的分片名称替换旧的分片名称:

  • config.databases – 非分片数据库的主分片
  • config.chunks – 块所在的分片
    在此之后,MongoDB 知道数据库、集合和块在新集群中的位置。

结论

集群的迁移需要大量的关注、知识和冷静。Percona Backup for MongoDB 中的 replset-remapping 功能降低了在两个不同环境之间迁移的复杂性。我会说,它现在已经接近日常工作了。

祝你今天过得愉快!

原文标题:Moving MongoDB Cluster to a Different Environment with Percona Backup for MongoDB
原文作者:Dmytro Zghoba
原文地址:https://www.percona.com/blog/moving-mongodb-cluster-to-a-different-environment-with-percona-backup-for-mongodb/

「喜欢这篇文章,您的关注和赞赏是给作者最好的鼓励」
关注作者
【版权声明】本文为墨天轮用户原创内容,转载时必须标注文章的来源(墨天轮),文章链接,文章作者等基本信息,否则作者和墨天轮有权追究责任。如果您发现墨天轮中有涉嫌抄袭或者侵权的内容,欢迎发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论