副本集介紹
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