mongodb副本集简介

MongoDB 副本集(Replica Set)包括主节点(primary)跟副本节点(Secondaries)。主节点只能有一个,所有的写操作请求都在主节点上面处理。副本节点可以有多个,通过同步主节点的操作日志(oplog)来备份主节点数据。

在主节点挂掉后,有选举权限的副本节点会自动发起选举,并从中选举出新的主节点。副本节点可以通过配置,指定其具体的属性,比如选举、隐藏、延迟同步等,最多可以有50个副本节点,但只能有7个副本节点能参与选举。虽然副本节点不能处理写操作,但可以处理读请求。

搭建一个副本集集群最少需要三个节点:一个主节点,两个备份节点,三个节点的架构如下图所示:

mongodb添加 Auth_mongodb


如果只有一个主节点,一个副本节点,且没有资源拿来当第二个副本节点,那就可以起一个仲裁者节点(arbiter),不存数据,只用来选举用,如下图所示:

mongodb添加 Auth_mongodb添加 Auth_02


当主节点挂掉后,那么两个副本节点会进行选举,从中选举出一个新的主节点,流程如下:

mongodb添加 Auth_副本集_03

mongodb副本集部署

搭建一套3节点副本集(1个 Primary 节点,2个 Secondary 节点),节点规划如下:

主机名

IP地址

角色

mongodb01

192.168.92.80

primary

mongodb02

192.168.92.81

secondary

mongodb03

192.168.92.82

secondary

3个节点分别配置主机名

hostnamectl set-hostname mongodb01
hostnamectl set-hostname mongodb02
hostnamectl set-hostname mongodb03

3个节点分别配置主机名解析

cat > /etc/hosts <<EOF
192.168.92.80 mongodb01
192.168.92.81 mongodb02
192.168.92.82 mongodb03
EOF

关闭防火墙和selinux

systemctl disable --now firewalld
sed -i 's/^SELINUX=enforcing$/SELINUX=disabled/' /etc/selinux/config && setenforce 0

配置时间同步

yum install -y chrony
systemctl enable --now  chronyd

配置国内yum源

cat > /etc/yum.repos.d/mongodb.repo <<'EOF'
[mongodb-org]
name=MongoDB Repository
baseurl=https://mirrors.tuna.tsinghua.edu.cn/mongodb/yum/el$releasever/
gpgcheck=0
enabled=1
EOF

3个节点安装mongodb

yum install -y mongodb-org

修改mongodb配置文件,增加以下配置

# cat /etc/mongod.conf
...
replication:
   replSetName: "rs0"
net:
   bindIp: 0.0.0.0
...

启动mongodb服务

systemctl enable --now mongod

使用 mongo 进入第一个实例mongodb01:

[root@mongodb01 ~]# mongo

使用 rs.initiate() 进行初始化

rs.initiate( {
   _id : "rs0",
   members: [
      { _id: 0, host: "mongodb01:27017" },
      { _id: 1, host: "mongodb02:27017" },
      { _id: 2, host: "mongodb03:27017" }
   ]
})

以上就已经完成了一个副本集的搭建,在 mongo shell 中执行 rs.conf() 可以看到每个节点中 host、arbiterOnly、hidden、priority、 votes、slaveDelay等属性。

rs0:PRIMARY> rs.conf()
{
        "_id" : "rs0",
        "version" : 1,
        "term" : 1,
        "protocolVersion" : NumberLong(1),
        "writeConcernMajorityJournalDefault" : true,
        "members" : [
                {
                        "_id" : 0,
                        "host" : "mongodb01:27017",
                        "arbiterOnly" : false,
                        "buildIndexes" : true,
                        "hidden" : false,
                        "priority" : 1,
                        "tags" : {

                        },
                        "slaveDelay" : NumberLong(0),
                        "votes" : 1
                },
                {
                        "_id" : 1,
                        "host" : "mongodb02:27017",
                        "arbiterOnly" : false,
                        "buildIndexes" : true,
                        "hidden" : false,
                        "priority" : 1,
                        "tags" : {

                        },
                        "slaveDelay" : NumberLong(0),
                        "votes" : 1
                },
                {
                        "_id" : 2,
                        "host" : "mongodb03: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("6062b2aa526a897f92fa34bc")
        }
}

或者使用以下命令确认primary节点:

db.isMaster()

确保副本集具有主副本集,使用rs.status()来识别副本集的主。

rs0:PRIMARY> rs.status()
......
        "members" : [
                {
                        "_id" : 0,
                        "name" : "mongodb01:27017",
                        "health" : 1,
                        "state" : 1,
                        "stateStr" : "PRIMARY",
                        "uptime" : 938,
                        "optime" : {
                                "ts" : Timestamp(1617081783, 1),
                                "t" : NumberLong(1)
                        },
                        "optimeDate" : ISODate("2021-03-30T05:23:03Z"),
                        "syncSourceHost" : "",
                        "syncSourceId" : -1,
                        "infoMessage" : "",
                        "electionTime" : Timestamp(1617081013, 1),
                        "electionDate" : ISODate("2021-03-30T05:10:13Z"),
                        "configVersion" : 1,
                        "configTerm" : 1,
                        "self" : true,
                        "lastHeartbeatMessage" : ""
                },
                {
                        "_id" : 1,
                        "name" : "mongodb02:27017",
                        "health" : 1,
                        "state" : 2,
                        "stateStr" : "SECONDARY",
                        "uptime" : 781,
                        "optime" : {
                                "ts" : Timestamp(1617081783, 1),
                                "t" : NumberLong(1)
                        },
                        "optimeDurable" : {
                                "ts" : Timestamp(1617081783, 1),
                                "t" : NumberLong(1)
                        },
                        "optimeDate" : ISODate("2021-03-30T05:23:03Z"),
                        "optimeDurableDate" : ISODate("2021-03-30T05:23:03Z"),
                        "lastHeartbeat" : ISODate("2021-03-30T05:23:04.172Z"),
                        "lastHeartbeatRecv" : ISODate("2021-03-30T05:23:03.059Z"),
                        "pingMs" : NumberLong(2),
                        "lastHeartbeatMessage" : "",
                        "syncSourceHost" : "mongodb01:27017",
                        "syncSourceId" : 0,
                        "infoMessage" : "",
                        "configVersion" : 1,
                        "configTerm" : 1
                },
                {
                        "_id" : 2,
                        "name" : "mongodb03:27017",
                        "health" : 1,
                        "state" : 2,
                        "stateStr" : "SECONDARY",
                        "uptime" : 781,
                        "optime" : {
                                "ts" : Timestamp(1617081783, 1),
                                "t" : NumberLong(1)
                        },
                        "optimeDurable" : {
                                "ts" : Timestamp(1617081783, 1),
                                "t" : NumberLong(1)
                        },
                        "optimeDate" : ISODate("2021-03-30T05:23:03Z"),
                        "optimeDurableDate" : ISODate("2021-03-30T05:23:03Z"),
                        "lastHeartbeat" : ISODate("2021-03-30T05:23:04.167Z"),
                        "lastHeartbeatRecv" : ISODate("2021-03-30T05:23:03.215Z"),
                        "pingMs" : NumberLong(2),
                        "lastHeartbeatMessage" : "",
                        "syncSourceHost" : "mongodb01:27017",
                        "syncSourceId" : 0,
                        "infoMessage" : "",
                        "configVersion" : 1,
                        "configTerm" : 1
                }
        ],
......

mongodb副本集故障转移

可以直接停掉主节点mongodb01来测试下主节点挂掉后,副本节点重新选举出新的主节点,即自动故障转移(Automatic Failover)。

[root@mongodb01 ~]# systemctl stop mongod

杀掉主节点 mongodb01后,可以看到 mongodb02 的输出日志里面选举部分,已经发起选举,并成功参选成为主节点:

[root@mongodb02 ~]# cat /var/log/mongodb/mongod.log |grep PRIMARY
{"t":{"$date":"2021-03-30T13:10:13.173+08:00"},"s":"I",  "c":"REPL",     "id":21215,   "ctx":"ReplCoord-0","msg":"Member is in new state","attr":{"hostAndPort":"mongodb01:27017","newState":"PRIMARY"}}
{"t":{"$date":"2021-03-30T13:27:51.523+08:00"},"s":"I",  "c":"REPL",     "id":21358,   "ctx":"ReplCoord-11","msg":"Replica set state transition","attr":{"newState":"PRIMARY","oldState":"SECONDARY"}}

然后执行 rs.status() 查看当前副本集情况

rs.status().members.forEach( 
    function(z){ 
            printjson(z.name);
            printjson(z.stateStr);
    } 
)

可以看到mongodb02变为主节点,mongodb01显示已挂掉。

"mongodb01:27017"
"(not reachable/healthy)"
"mongodb02:27017"
"PRIMARY"
"mongodb03:27017"
"SECONDARY"

再次启动mongodb01:

[root@mongodb01 ~]# systemctl start mongod

可以看到已检测到 mongodb01,并且已变为副本节点,通过rs.status 查看结果也是如此。

"mongodb01:27017"
"SECONDARY"
"mongodb02:27017"
"PRIMARY"
"mongodb03:27017"
"SECONDARY"

从副本集删除成员

有2种方法从副本集中删除成员,使用rs.remove()或rs.reconfig()。

使用rs.remove()删除成员

1、关闭您要删除的mongod成员的实例,以删除mongodb03成员为例:

[root@mongodb03 ~]# mongo

rs0:SECONDARY> use admin;

rs0:SECONDARY> db.shutdownServer()

2、连接到副本集的当前primary,可以连接到任意副本集成员使用db.isMaster()确定当前的主数据库。使用rs.remove()以下两种形式之一删除成员:

[root@mongodb02 ~]# mongo

rs0:PRIMARY> rs.remove("mongodb03:27017")

rs0:PRIMARY> rs.remove("mongodb03")

3、使用以下命令查看集群信息

rs.status().members.forEach( 
    function(z){ 
            printjson(z.name);
            printjson(z.stateStr);
    } 
)

确认成功移除mongodb03节点

"mongodb01:27017"
"SECONDARY"
"mongodb02:27017"
"PRIMARY"

使用rs.reconfig()删除成员

使用rs.conf() 确定要删除的节点位置

[root@mongodb02 ~]# mongo

rs0:PRIMARY> rs.conf()

删除成员

rs0:PRIMARY> cfg = rs.conf()

rs0:PRIMARY> cfg.members.splice(2,1)

rs0:PRIMARY> rs.reconfig(cfg)

将成员添加到副本集

1、启动新mongod实例,启动时需要在命令行指定副本集名称,这里通过在配置文件中配置:

# cat /etc/mongod.conf
...
replication:
   replSetName: "rs0"
...

清空数据并重新启动

[root@mongodb03 ~]# rm -rf /var/lib/mongo/*
[root@mongodb03 ~]# systemctl restart mongod

2、添加成员到副本集

连接到副本集的主数据库,您只能在连接到主要成员时添加成员:

[root@mongodb02 ~]# mongo

rs0:PRIMARY> rs.add( { host: "mongodb03:27017", priority: 0, votes: 0 } )

3、重新配置优先级

一旦新加入的成员已经过渡到 SECONDARY状态,如果需要的话,使用rs.reconfig()更新新添加的成员priority和votes。

使用rs.conf()查看新成员id,则将其priority和votes更新为 1,请使用以下操作序列:

var cfg = rs.conf();
cfg.members[2].priority = 1
cfg.members[2].votes = 1
rs.reconfig(cfg)