事故背景 线上环境有一个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> 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 },
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 查看集群状态
返回内容:
{ "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节点部署的步鄹。
注意:
向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 验证权重配置
返回内容:
{ "_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。(为了必然偶然性,可以进行多次测试)