事故背景
线上环境有一个MongoDB副本集,由于是部署在客户那边本地机房,客户误操作把部署副本集的另外2个节点的 VM 给删除了(并且VM已经无法恢复了)。所幸的是还有一个节点存活,登录节点后发现这个节点是 SECONDARY,所以可能会有一部分数据丢失,而且此时已经无法对应用提供读写服务。此时只能停服维护,并对集群进行恢复。
基于以上问题,下面对副本集恢复操作步鄹进行了记录。
处理思路
- 对mongodb数据进行备份(防止恢复集群时出现意外导致数据丢失)。
- 把仅存的 SECONDARY 节点提升为 PRIMARY,删除集群中另外2个不存活的节点,然后重新配置MongoDB副本集。
- 新部署2个MongoDB节点,并加入到集群中。
- 等待 PRIMARY 节点数据同步到另外2个新节点后,进行数据验证,结束生产环境维护。
注意:
由于原先的集群中只存有 SECONDARY 节点,PRIMARY 节点已经丢失,所以存在部署数据没同步到 SECONDARY 的可能。但由于PRIMARY节点的VM已经被删,这部分未同步的数据的丢失在所难免,想恢复这部分数据只能根据自己的业务、代码逻辑设定才有补上丢失的数据的可能性。
集群恢复
1、在SECONDARY节点删除挂掉的primary节点
1.1 查看当前副本集配置
rs1:SECONDARY> rs.conf()
输出内容:
rs1:SECONDARY> use admin
switched to db admin
rs1:SECONDARY> rs_conf = rs.config()
{
"_id" : "rs1",
"version" : 7,
"protocolVersion" : NumberLong(1),
"members" : [
{
"_id" : 0,
"host" : "192.168.30.207:27017",
"arbiterOnly" : false,
"buildIndexes" : true,
"hidden" : false,
"priority" : 1,
"tags" : {
},
"slaveDelay" : NumberLong(0),
"votes" : 1
},
{
"_id" : 1,
"host" : "192.168.30.213:27017",
"arbiterOnly" : false,
"buildIndexes" : true,
"hidden" : false,
"priority" : 1,
"tags" : {
},
"slaveDelay" : NumberLong(0),
"votes" : 1
},
{
"_id" : 2,
"host" : "192.168.30.214:27017",
"arbiterOnly" : false,
"buildIndexes" : true,
"hidden" : false,
"priority" : 1,
"tags" : {
},
"slaveDelay" : NumberLong(0),
"votes" : 1
}
],
"settings" : {
"chainingAllowed" : true,
"heartbeatIntervalMillis" : 2000,
"heartbeatTimeoutSecs" : 10,
"electionTimeoutMillis" : 10000,
"catchUpTimeoutMillis" : -1,
"catchUpTakeoverDelayMillis" : 30000,
"getLastErrorModes" : {
},
"getLastErrorDefaults" : {
"w" : 1,
"wtimeout" : 0
},
"replicaSetId" : ObjectId("5f5094994a4d5004eae73e2f")
}
}
1.2 删除集群成员
- 比如要删除members中 host 为
192.168.30.213:27017
的成员,通过rs.conf()
找到成员的_id
{
"_id" : 1,
"host" : "192.168.30.213:27017",
"arbiterOnly" : false,
"buildIndexes" : true,
"hidden" : false,
"priority" : 1,
"tags" : {
},
"slaveDelay" : NumberLong(0),
"votes" : 1
},
- 删除
_id
为1的成员
splice的第一个参数表示要删除的数组元素的下标
0 表示集群中成员节点的
"_id"
1 表示删除的个数
rs1:SECONDARY> rs_conf = rs.conf()
rs1:SECONDARY> rs_conf.members.splice(0,1)
输出内容:
rs1:SECONDARY> rs_conf.members.splice(1,1)
[
{
"_id" : 1,
"host" : "192.168.30.213:27017",
"arbiterOnly" : false,
"buildIndexes" : true,
"hidden" : false,
"priority" : 1,
"tags" : {
},
"slaveDelay" : NumberLong(0),
"votes" : 1
}
]
依照此方法删除副本集中不存活的节点。
注意:
有一点需要注意,由于已经删除了
_id
为1的成员,所以后面的成员的_id
号都会减小1,与数组中元素的下标相同。
2、重新配置MongoDB副本集
2.1 重置集群配置
rs_conf
就是上面修改后的配置,加force参数是因为 SECONDARY 默认没有执行此命令的权限
rs1:SECONDARY> rs.reconfig(rs_conf, {"force":true})
返回内容:
rs1:SECONDARY> rs.reconfig(rs_conf, {"force":true})
{
"ok" : 1,
"operationTime" : Timestamp(1619586716, 1),
"$clusterTime" : {
"clusterTime" : Timestamp(1619588924, 1),
"signature" : {
"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
"keyId" : NumberLong(0)
}
}
}
rs1:PRIMARY>
2.2 查看集群状态
rs1:PRIMARY> rs.status()
返回内容:
{
"set" : "rs1",
"date" : ISODate("2021-04-28T05:51:03.672Z"),
"myState" : 1,
"term" : NumberLong(17),
"syncingTo" : "",
"syncSourceHost" : "",
"syncSourceId" : -1,
"heartbeatIntervalMillis" : NumberLong(2000),
"optimes" : {
"lastCommittedOpTime" : {
"ts" : Timestamp(1619589055, 1),
"t" : NumberLong(17)
},
"readConcernMajorityOpTime" : {
"ts" : Timestamp(1619589055, 1),
"t" : NumberLong(17)
},
"appliedOpTime" : {
"ts" : Timestamp(1619589055, 1),
"t" : NumberLong(17)
},
"durableOpTime" : {
"ts" : Timestamp(1619589055, 1),
"t" : NumberLong(17)
}
},
"members" : [
{
"_id" : 0,
"name" : "192.168.30.207:27017",
"health" : 1,
"state" : 1,
"stateStr" : "PRIMARY",
"uptime" : 7482,
"optime" : {
"ts" : Timestamp(1619589055, 1),
"t" : NumberLong(17)
},
"optimeDate" : ISODate("2021-04-28T05:50:55Z"),
"syncingTo" : "",
"syncSourceHost" : "",
"syncSourceId" : -1,
"infoMessage" : "",
"electionTime" : Timestamp(1619588924, 1),
"electionDate" : ISODate("2021-04-28T05:48:44Z"),
"configVersion" : 124340,
"self" : true,
"lastHeartbeatMessage" : ""
}
],
"ok" : 1,
"operationTime" : Timestamp(1619589055, 1),
"$clusterTime" : {
"clusterTime" : Timestamp(1619589055, 1),
"signature" : {
"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
"keyId" : NumberLong(0)
}
}
}
此时发现,这个 SECONDARY 节点已经提升为 PRIMARY,并且集群状态中,也就只有我们当前一个节点。
接下来就可以向副本集中添加新的MongoDB节点了。
3、添加新的MongoDB节点
这里省略新节点的部署过程,具体可以参考[《MongoDB 单节点升级为副本集高可用集群》](MongoDB 单节点升级为副本集高可用集群 - HEBIN博客 (wanhebin.com))文章中MongoDB节点部署的步鄹。
注意:
向mongodb副本集添加实例后,PRIMARY节点数据能够自动同步到新添加的SECONDARY节点,无需人工干预。
3.1 增加实例
登录PRIMARY节点,添加MongoDB实例。
新添加的实例优先级权重默认为1,如需调整,建议等数据同步完成后进行权重更改。
rs1:PRIMARY> use admin
rs1:PRIMARY> rs.add('192.168.30.213:27017')
rs1:PRIMARY> rs.add('192.168.30.214:27017')
添加节点的返回结果如下:
rs1:PRIMARY> rs.add('192.168.30.213:27017')
{
"ok" : 1,
"operationTime" : Timestamp(1619581966, 1),
"$clusterTime" : {
"clusterTime" : Timestamp(1619581966, 1),
"signature" : {
"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
"keyId" : NumberLong(0)
}
}
}
rs1:PRIMARY> rs.add('192.168.30.214:27017')
{
"ok" : 1,
"operationTime" : Timestamp(1619581975, 1),
"$clusterTime" : {
"clusterTime" : Timestamp(1619581975, 1),
"signature" : {
"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
"keyId" : NumberLong(0)
}
}
}
3.2 删除实例
如果添加错节点时,可以通过 rs.remove()
来删除错误的节点(因为此时当前实例已经是 PRIMARY 了,所以不需要用 1.2 中方法剔除节点)。
从mongodb副本集中移除实例,不可移除primary
rs1:PRIMARY> use admin
rs1:PRIMARY> rs.remove('192.168.30.214:27017')
返回内容:
rs1:PRIMARY> rs.remove('192.168.30.213:27017')
{
"ok" : 1,
"operationTime" : Timestamp(1619581713, 1),
"$clusterTime" : {
"clusterTime" : Timestamp(1619581713, 1),
"signature" : {
"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
"keyId" : NumberLong(0)
}
}
}
rs1:PRIMARY> rs.remove('192.168.30.214:27017')
{
"ok" : 1,
"operationTime" : Timestamp(1619581777, 2),
"$clusterTime" : {
"clusterTime" : Timestamp(1619581777, 2),
"signature" : {
"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
"keyId" : NumberLong(0)
}
}
}
注意:
副本集经过添加删除后顺序会乱,可以根据需要设置权重来调整。
4、调整节点权重
如果想在集群宕机恢复后,还想让某一节点始终保持为 PRIMARY,可以把此节点的权重设置成最大。
4.1 设置权重
找到对应节点在副本集中成员_id
,进行权重设置。
这里以成员0为例,其host为192.168.30.207:27017
rs1:PRIMARY> rs_conf = rs.config()
rs1:PRIMARY> rs_conf.members[0].priority=10
4.2 生效配置
rs1:PRIMARY> rs.reconfig(rs_conf)
返回结果:
rs1:PRIMARY> rs.reconfig(rs_conf)
{
"ok" : 1,
"operationTime" : Timestamp(1619591404, 1),
"$clusterTime" : {
"clusterTime" : Timestamp(1619591404, 1),
"signature" : {
"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
"keyId" : NumberLong(0)
}
}
}
4.3 验证权重配置
- 查询成员0的权重
rs1:PRIMARY> rs.config()
返回内容:
{
"_id" : 0,
"host" : "192.168.30.207:27017",
"arbiterOnly" : false,
"buildIndexes" : true,
"hidden" : false,
"priority" : 10,
"tags" : {
},
"slaveDelay" : NumberLong(0),
"votes" : 1
}
- 模拟宕机恢复后的集群状态
关闭三个节点的mongodb服务,再无序恢复,然后连接进节点192.168.30.207:27017
,成员0依然还是PRIMARY。(为了必然偶然性,可以进行多次测试)