副本集介绍
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,节点配置可以为:
或:
两者的区别在于是否有仲裁节点,仲裁节点既不保存数据也不为客户端提供服务,只参与投票。如果资源有限或不想保存三份数据,可以使用仲裁节点代替一个备份节点。
数据同步
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