MongoDB 副本集把SECONDARY提升为PRIMARY

事故背景

​ 线上环境有一个MongoDB副本集,由于是部署在客户那边本地机房,客户误操作把部署副本集的另外2个节点的 VM 给删除了(并且VM已经无法恢复了)。所幸的是还有一个节点存活,登录节点后发现这个节点是 SECONDARY,所以可能会有一部分数据丢失,而且此时已经无法对应用提供读写服务。此时只能停服维护,并对集群进行恢复。

​ 基于以上问题,下面对副本集恢复操作步鄹进行了记录。

 

处理思路

  1. 对mongodb数据进行备份(防止恢复集群时出现意外导致数据丢失)。
  2. 把仅存的 SECONDARY 节点提升为 PRIMARY,删除集群中另外2个不存活的节点,然后重新配置MongoDB副本集。
  3. 新部署2个MongoDB节点,并加入到集群中。
  4. 等待 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。(为了必然偶然性,可以进行多次测试)

点赞

发表回复