天天看点

mongodb副本集

副本集介绍

mongodb的集群大体分为两种模式,副本集模式和分片模式。副本集模式包括无仲裁者和有仲裁者的副本集。

副本集中包含一个Primary节点和多个Secondary节点,primary负责数据的写入,secondary从Primary同步写入的数据,保证副本集内数据的同步,提供数据的高可用。

mongodb只支持单一的primary节点。primary节点的选举需要副本集中的大多数节点同意,要求大多数节点同意的目的是避免出现两个primary节点。

比如5个副本的副本集需要至少3个节点同意才能产生primary,此时由于网络分区导致其中3台和另外两台不能通信。其中两台由于不能满足大多数节点的要求(少于3),所以他们无法选择primary,即使这两个中有一个节是primary节点,当它注意到它无法获取大多数节点的支持时,它也会退化成为备份节点。

如果让这两个节点可以选出primary节点,而另外3个节点也选出primary节点,这样就存在了两个primary节点了。

当备份节点无法与主节点连通时,它会联系并请求其他副本集成员将自己选举为主节点,如果竞选节点成员能够得到大多数投票,就会成为主节点。节点被选为primary的权重包括,节点数据是否最新?有没有其他更高优先级的成员可以被选举为主节点?

因为成为primary需要大多数节点同意,在副本集的环境中,要是所有的Secondary都宕机了,只剩下Primary,最后Primary会变成Secondary,不能提供服务。

副本集结构

mongo官网建议副本集的最少节点数为3,节点配置可以为:

mongodb副本集

或:

mongodb副本集

两者的区别在于是否有仲裁节点,仲裁节点既不保存数据也不为客户端提供服务,只参与投票。如果资源有限或不想保存三份数据,可以使用仲裁节点代替一个备份节点。

数据同步

Primary节点负责写入数据,secondary会检查自己local库的oplog.rs集合,找出最近的时间戳,检查Primary节点local库oplog.rs集合,找出大于此时间戳的记录,将找到的记录插入到自己的oplog.rs集合中,并执行这些操作。

副本集初始化时,会进行初始化同步,尝试从副本集的另一个成员那里进行全量复制。

Primary选举

Primary选举时机一般包括:

Secondary节点检测到Primary宕机时,会触发新Primary的选举
当有Primary节点主动stepDown(主动降级为Secondary)时,也会触发新的Primary选举           
节点间心跳

复制集成员间默认每2s会发送一次心跳信息,如果10s未收到某个节点的心跳,则认为该节点已宕机;如果宕机的节点为Primary,Secondary(前提是可被选为Primary)会发起新的Primary选举。

成员状态:

STARTUP : 刚启动时处于这个状态,加载副本集成功后就进入STARTUP2状态
STARTUP2 : 整个初始化同步都处于这个状态,这个状态下,MongDB会创建几个线程,用于处理复制和选举,然后就会切换到RECOVERING状态
RECOVERING : 表示运行正常,当暂时不能处理读取请求。如果有成员处于这个状态,可能会造成轻微系统过载
ARBITER : 仲裁者处于这个状态
DOWN : 一个正常运行的成员不可达,就处于DOWN状态。这个状态有可能是网络问题
UNKNOWN : 成员无法到达其他任何成员,其他成员就知道无法它处于什么状态,就会处于UNKNOWN。表明这个未知状态的成员挂掉了。或者两个成员间存在网络访问问题。
REMOVED : 被移除副本集时处于的状态,添加回来后,就会回到正常状态
ROLLBACK : 处于数据回滚时就处于ROLLBACK状态。回滚结束后,会换为RECOVERING状态,然后成为备份节点。
FATAL : 发生不可挽回错误,也不再尝试恢复,就处于这个状态。这个时候通常应该重启服务器           
节点优先级

每个节点都倾向于投票给优先级最高的节点,优先级为0的节点不会主动发起Primary选举,当Primary发现有优先级更高Secondary,并且该Secondary的数据落后在10s内,则Primary会主动降级,让优先级更高的Secondary有成为Primary的机会。

网络分区

只有大多数投票节点间保持网络连通,才有机会被选Primary;如果Primary与大多数的节点断开连接,Primary会主动降级为Secondary。当发生网络分区时,可能在短时间内出现多个Primary,故Driver在写入时,最好设置大多数成功的策略,这样即使出现多个Primary,也只有一个Primary能成功写入大多数。

复制集的读写设置

默认情况下,复制集的所有读请求都发到Primary,Driver可通过设置Read Preference来将读请求路由到其他的节点。

primary: 默认规则,所有读请求发到Primary
primaryPreferred: Primary优先,如果Primary不可达,请求Secondary
secondary: 所有的读请求都发到secondary
secondaryPreferred:Secondary优先,当所有Secondary不可达时,请求Primary
nearest:读请求发送到最近的可达节点上(通过ping探测得出最近的节点)           

回滚(rollback)

Primary执行了一个写请求之后挂了,但是备份节点还没有来得及复制这次操作。新选举出来的主节点结就会漏掉这次写操作。当旧Primary恢复之后,就要回滚部分操作。回滚回将失败之前未复制的操作撤销。

副本集实例

在三台服务器上分别部署相同的mongodb,规划:

192.168.47.129 slave
192.168.47.130 master
192.168.47.141 arbiter           

相对于单机版mongo,副本集只需要添加如下相关配置:

replication:
    oplogSizeMB: 10240
    replSetName: nh_sc           

启动所有mongo,之后进入任意节点配置主、备、仲裁节点:

> use admin
switched to db admin
> nh_config={_id:"nh_sc",members:[{_id:0,host:'192.168.47.130:27017',priority:9},{_id:1,host:'192.168.47.129:27017',priority:1},{_id:2,host :'192.168.47.141:27017',arbiterOnly:true}]};
{
    "_id" : "nh_sc",
    "members" : [
        {
            "_id" : 0,
            "host" : "192.168.47.130:27017",
            "priority" : 9
        },
        {
            "_id" : 1,
            "host" : "192.168.47.129:27017",
            "priority" : 1
        },
        {
            "_id" : 2,
            "host" : "192.168.47.141:27017",
            "arbiterOnly" : true
        }
    ]
}
> rs.initiate(nh_config)
{
    "ok" : 1,
    "$clusterTime" : {
        "clusterTime" : Timestamp(1589343960, 1),
        "signature" : {
            "hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
            "keyId" : NumberLong(0)
        }
    },
    "operationTime" : Timestamp(1589343960, 1)
}           

注意仲裁节点需要有个特别的配置——arbiterOnly:true。之后执行rs.status()命令会看到如下信息:

nh_sc:SECONDARY> rs.status()
{
    "set" : "nh_sc",
    "date" : ISODate("2020-05-13T04:28:19.430Z"),
    "myState" : 1,
    "term" : NumberLong(1),
    "syncingTo" : "",
    "syncSourceHost" : "",
    "syncSourceId" : -1,
    "heartbeatIntervalMillis" : NumberLong(2000),
    "majorityVoteCount" : 2,
    "writeMajorityCount" : 2,
    "optimes" : {
        "lastCommittedOpTime" : {
            "ts" : Timestamp(1589344091, 1),
            "t" : NumberLong(1)
        },
        "lastCommittedWallTime" : ISODate("2020-05-13T04:28:11.276Z"),
        "readConcernMajorityOpTime" : {
            "ts" : Timestamp(1589344091, 1),
            "t" : NumberLong(1)
        },
        "readConcernMajorityWallTime" : ISODate("2020-05-13T04:28:11.276Z"),
        "appliedOpTime" : {
            "ts" : Timestamp(1589344091, 1),
            "t" : NumberLong(1)
        },
        "durableOpTime" : {
            "ts" : Timestamp(1589344091, 1),
            "t" : NumberLong(1)
        },
        "lastAppliedWallTime" : ISODate("2020-05-13T04:28:11.276Z"),
        "lastDurableWallTime" : ISODate("2020-05-13T04:28:11.276Z")
    },
    "lastStableRecoveryTimestamp" : Timestamp(1589344091, 1),
    "lastStableCheckpointTimestamp" : Timestamp(1589344091, 1),
    "electionCandidateMetrics" : {
        "lastElectionReason" : "electionTimeout",
        "lastElectionDate" : ISODate("2020-05-13T04:26:11.215Z"),
        "electionTerm" : NumberLong(1),
        "lastCommittedOpTimeAtElection" : {
            "ts" : Timestamp(0, 0),
            "t" : NumberLong(-1)
        },
        "lastSeenOpTimeAtElection" : {
            "ts" : Timestamp(1589343960, 1),
            "t" : NumberLong(-1)
        },
        "numVotesNeeded" : 2,
        "priorityAtElection" : 9,
        "electionTimeoutMillis" : NumberLong(10000),
        "numCatchUpOps" : NumberLong(0),
        "newTermStartDate" : ISODate("2020-05-13T04:26:11.252Z"),
        "wMajorityWriteAvailabilityDate" : ISODate("2020-05-13T04:26:11.733Z")
    },
    "members" : [
        {
            "_id" : 0,
            "name" : "192.168.47.130:27017",
            "health" : 1,
            "state" : 1,
            "stateStr" : "PRIMARY",
            "uptime" : 2348,
            "optime" : {
                "ts" : Timestamp(1589344091, 1),
                "t" : NumberLong(1)
            },
            "optimeDate" : ISODate("2020-05-13T04:28:11Z"),
            "syncingTo" : "",
            "syncSourceHost" : "",
            "syncSourceId" : -1,
            "infoMessage" : "",
            "electionTime" : Timestamp(1589343971, 1),
            "electionDate" : ISODate("2020-05-13T04:26:11Z"),
            "configVersion" : 1,
            "self" : true,
            "lastHeartbeatMessage" : ""
        },
        {
            "_id" : 1,
            "name" : "192.168.47.129:27017",
            "health" : 1,
            "state" : 2,
            "stateStr" : "SECONDARY",
            "uptime" : 138,
            "optime" : {
                "ts" : Timestamp(1589344091, 1),
                "t" : NumberLong(1)
            },
            "optimeDurable" : {
                "ts" : Timestamp(1589344091, 1),
                "t" : NumberLong(1)
            },
            "optimeDate" : ISODate("2020-05-13T04:28:11Z"),
            "optimeDurableDate" : ISODate("2020-05-13T04:28:11Z"),
            "lastHeartbeat" : ISODate("2020-05-13T04:28:17.595Z"),
            "lastHeartbeatRecv" : ISODate("2020-05-13T04:28:18.088Z"),
            "pingMs" : NumberLong(2),
            "lastHeartbeatMessage" : "",
            "syncingTo" : "192.168.47.130:27017",
            "syncSourceHost" : "192.168.47.130:27017",
            "syncSourceId" : 0,
            "infoMessage" : "",
            "configVersion" : 1
        },
        {
            "_id" : 2,
            "name" : "192.168.47.141:27017",
            "health" : 1,
            "state" : 7,
            "stateStr" : "ARBITER",
            "uptime" : 138,
            "lastHeartbeat" : ISODate("2020-05-13T04:28:17.595Z"),
            "lastHeartbeatRecv" : ISODate("2020-05-13T04:28:17.767Z"),
            "pingMs" : NumberLong(3),
            "lastHeartbeatMessage" : "",
            "syncingTo" : "",
            "syncSourceHost" : "",
            "syncSourceId" : -1,
            "infoMessage" : "",
            "configVersion" : 1
        }
    ],
    "ok" : 1,
    "$clusterTime" : {
        "clusterTime" : Timestamp(1589344091, 1),
        "signature" : {
            "hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
            "keyId" : NumberLong(0)
        }
    },
    "operationTime" : Timestamp(1589344091, 1)
}           

连接slave mongo:

root@faith129:/server/deployments/mongodb-4.2.6# mongo 192.168.47.129
nh_sc:SECONDARY> show databases
2020-05-12T21:30:23.292-0700 E  QUERY    [js] uncaught exception: Error: listDatabases failed:{
    "operationTime" : Timestamp(1589344221, 1),
    "ok" : 0,
    "errmsg" : "not master and slaveOk=false",
    "code" : 13435,
    "codeName" : "NotMasterNoSlaveOk",
    "$clusterTime" : {
        "clusterTime" : Timestamp(1589344221, 1),
        "signature" : {
            "hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
            "keyId" : NumberLong(0)
        }
    }
} :
_getErrorWithCode@src/mongo/shell/utils.js:25:13
Mongo.prototype.getDBs/<@src/mongo/shell/mongo.js:135:19
Mongo.prototype.getDBs@src/mongo/shell/mongo.js:87:12
shellHelper.show@src/mongo/shell/utils.js:906:13
shellHelper@src/mongo/shell/utils.js:790:15
@(shellhelp2):1:1
nh_sc:SECONDARY> rs.slaveOk()
nh_sc:SECONDARY> show databases
admin   0.000GB
config  0.000GB
local   0.000GB
nh_sc   0.006GB
test1   0.000GB
nh_sc:SECONDARY> use nh_sc
switched to db nh_sc
nh_sc:SECONDARY> db.cmPhoneFeatures.count()
100000           

之前slave里没有nh_sc库,可以看到现在已经有了,数据也被同步过来了。

再查看仲裁节点:

root@faith141:/server/deployments/mongodb-4.2.6# mongo 192.168.47.141
nh_sc:ARBITER> show databases
2020-05-12T21:33:00.820-0700 E  QUERY    [js] uncaught exception: Error: listDatabases failed:{
    "ok" : 0,
    "errmsg" : "not master and slaveOk=false",
    "code" : 13435,
    "codeName" : "NotMasterNoSlaveOk"
} :
_getErrorWithCode@src/mongo/shell/utils.js:25:13
Mongo.prototype.getDBs/<@src/mongo/shell/mongo.js:135:19
Mongo.prototype.getDBs@src/mongo/shell/mongo.js:87:12
shellHelper.show@src/mongo/shell/utils.js:906:13
shellHelper@src/mongo/shell/utils.js:790:15
@(shellhelp2):1:1
nh_sc:ARBITER> rs.slaveOk()
nh_sc:ARBITER> show databases
admin   0.000GB
config  0.000GB
local   0.000GB
nh_sc:ARBITER>            

发现仲裁节点是不存储数据的。

测试服务转移:

1. 关闭slave,master仍然提供服务;
2. 关闭master,此时slave升级为master并提供服务。           

有人说启动两个副本集,一主一从,如果master死掉或slave死掉,这时候集群中只有一个secondary,如果加上一个仲裁节点则正常,将这种情况的原因归结于仲裁节点是不太对的。

其实很好理解,因为一主一从的话,大部分节点为2,根据mongo的机制,此时永远无法得到2,因此不会有primary产生,且原primary即使存活也会降级为secondary,这与mongo机制相关,与仲裁节点无关。

参考:

https://www.jianshu.com/p/bb3f721e1557

继续阅读