副本集
在生產(chǎn)環(huán)境中,我們不建議使用單機版的MongoDB服務(wù)器,因為:
- 單機版的MongoDB無法保證可靠性,一旦進(jìn)程發(fā)生故障或者服務(wù)器宕機,業(yè)務(wù)將直接不可使用。
- 一旦服務(wù)器上磁盤損壞,數(shù)據(jù)會直接丟失,而此時也沒有任何副本可以用。
對于生產(chǎn)環(huán)境的數(shù)據(jù)庫至少要保證一個或一個以上的可用副本。
Replication Set
對于MongoDB來說,數(shù)據(jù)高可用是通過副本集架構(gòu)(Replication Set)實現(xiàn)的,由一個主節(jié)點(Primary)和若干個子節(jié)點(Secondary)組成,其架構(gòu)圖如下:

客戶端通過數(shù)據(jù)庫主節(jié)點寫入數(shù)據(jù),由備份節(jié)點進(jìn)行復(fù)制同步,這樣所有的節(jié)點都會同時擁有這些業(yè)務(wù)數(shù)據(jù)的副本。
早期版本中MongoDB使用了一種Master-Slave的架構(gòu),該做法在MongoDB 3.4版本之后已廢棄。
集群選舉
Raft選舉算法
MongoDB的副本選舉是通過Raft算法來實現(xiàn)的。
Raft是一種強一致性、去中心化、高可用的分布式共識算法。所謂共識,就是多個節(jié)點對某個事情達(dá)成一致的看法,即使實在部分節(jié)點故障、網(wǎng)絡(luò)延時、網(wǎng)絡(luò)分割的情況下。
協(xié)議中的角色
- Leader:領(lǐng)導(dǎo)者,Leader會向其他節(jié)點發(fā)送心跳,同時負(fù)責(zé)處理客戶端的讀寫操作,包括將數(shù)據(jù)同步到其他節(jié)點。
- Follower:追隨者,響應(yīng)來自Leader和Candidate的投票請求,如果在一定時間內(nèi)沒有收到Leader的心跳,則會傳喚為Candidate。
- Candidate:候選者,F(xiàn)ollower主動選舉轉(zhuǎn)換成Candidate,獲得投票后會成為Leader。
選舉的流程
在開始的時候,所有的節(jié)點都是Follower,此時大家都沒有辦法收到Leader的心跳。接下來,A節(jié)點先是給自己投一票,然后接著向其他節(jié)點發(fā)送投票請求,一旦A節(jié)點獲得了集群中大多數(shù)節(jié)點的投票,那么A節(jié)點將成為Leader節(jié)點,同時向其他節(jié)點廣播心跳,此時來申明自己的Leader角色。這個過程如下:

在投票中會伴隨著一些沖突或者異常出現(xiàn),為了保證達(dá)成最終的一致性,Raft協(xié)議還加入了以下的細(xì)節(jié)。
- 在同一任期內(nèi),每個節(jié)點最多只能給一個Candidate投票(節(jié)點內(nèi)部進(jìn)行記錄),任期內(nèi)投票采用先到先得的原則。
- 節(jié)點在收到Candidate的投票請求時,只有當(dāng)對方的任期、操作日志時間至少與自己的一樣新時,才會給它投票。
- Candidate發(fā)起投票后,如果一直沒有得到大多數(shù)票,則會一直保持這個狀態(tài)直到超時,此后將繼續(xù)發(fā)起新一輪任期的選舉(Term自增),如果在投票期間檢測到了Leader的心跳,則會判斷Leader的任期是否至少和自己一樣新,如果是則降級為Follower,并承認(rèn)對方的Leader角色,否則不予理會。
- 無論是Candidate節(jié)點還是Leader節(jié)點,一旦發(fā)現(xiàn)了其他節(jié)點有更新的任期(Term值),都會自動降級為Follower。
開始搭建
本次試驗中我們采用docker容器搭建
拉取Mongo鏡像
docker pull mongo:latest

我們用的MongoDB版本號是5.0
啟動MongoDB容器
啟動三個MongoDB實例,其中mongo_master 為主節(jié)點, mongo_node1、mongo_node2為從節(jié)點。
主節(jié)點負(fù)責(zé)讀寫操作,從節(jié)點可讀但不可寫。
docker run -itd --name mongo_master -p 27017:27017 mongo --replSet "re0"
docker run -itd --name mongo_node1 -p 27018:27017 mongo --replSet "re0"
docker run -itd --name mongo_node2 -p 27019:27017 mongo --replSet "re0"

查看MongoDB實例IP地址
使用docker inspect 命令查看容器信息
docker inspect mongo_master

這個地址是docker內(nèi)部虛擬網(wǎng)絡(luò)地址,容器之間可以訪問,外部是訪問不到的,在配置副本集的時候需要這個地址。
本次安裝的地址為:
172.17.0.2
172.17.0.3
172.17.0.4
172.17.0.2地址為Master地址,另外兩個為node節(jié)點。
副本集配置
進(jìn)入master容器,進(jìn)行集群配置
docker exec -it mongo_master mongo
進(jìn)入之后大概是這個樣子

初始化副本集配置
使用rs.initiate()方法初始化副本集配置
var config = {
_id:"re0",
members:[
{_id:0,host:"172.17.0.2:27017"},
{_id:1,host:"172.17.0.3:27017"},
{_id:2,host:"172.17.0.4:27017"}
]
}
rs.initiate(config)
config._id: 這個是docker啟動時 replSet 設(shè)置的值。
members_id: 服務(wù)器標(biāo)識。

查看副本集配置
使用rs.conf()方法查看配置信息
rs.conf()
執(zhí)行結(jié)果如下
re0:PRIMARY> rs.conf()
{
"_id" : "re0",
"version" : 1,
"protocolVersion" : NumberLong(1),
"members" : [
{
"_id" : 0,
"host" : "172.17.0.2:27017",
"arbiterOnly" : false,
"buildIndexes" : true,
"hidden" : false,
"priority" : 1,
"tags" : {
},
"slaveDelay" : NumberLong(0),
"votes" : 1
},
{
"_id" : 1,
"host" : "172.17.0.3:27017",
"arbiterOnly" : false,
"buildIndexes" : true,
"hidden" : false,
"priority" : 1,
"tags" : {
},
"slaveDelay" : NumberLong(0),
"votes" : 1
},
{
"_id" : 2,
"host" : "172.17.0.4: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" : 60000,
"getLastErrorModes" : {
},
"getLastErrorDefaults" : {
"w" : 1,
"wtimeout" : 0
},
"replicaSetId" : ObjectId("6106384c55f1745e07001366")
}
}
到這里,副本集基本上就配置好了,現(xiàn)在讓我們插入一條數(shù)據(jù)試試
測試
- 插入數(shù)據(jù)
use order
db.user.insert({
username:"zhouhc",
nickname:"呢稱",
password: "123123"
})
執(zhí)行結(jié)果如下:
re0:PRIMARY> use order
switched to db order
re0:PRIMARY> db.user.insert({
... username:"zhouhc",
... nickname:"呢稱",
... password: "123123"
... })
WriteResult({ "nInserted" : 1 })
re0:PRIMARY> db.user.find()
{ "_id" : ObjectId("61064400ca2a42d1044bd372"), "username" : "zhouhc", "nickname" : "呢稱", "password" : "123123" }
連接node節(jié)點查看以下
re0:SECONDARY> exit
bye
root@6b5043311fe9:/# mongo --host 172.17.0.3:27017
MongoDB shell version v3.4.9
connecting to: mongodb://172.17.0.3:27017/
MongoDB server version: 3.4.9
Server has startup warnings:
2021-08-01T05:41:32.227+0000 I STORAGE [initandlisten]
2021-08-01T05:41:32.227+0000 I STORAGE [initandlisten] ** WARNING: Using the XFS filesystem is strongly recommended with the WiredTiger storage engine
2021-08-01T05:41:32.227+0000 I STORAGE [initandlisten] ** See http://dochub.mongodb.org/core/prodnotes-filesystem
2021-08-01T05:41:32.259+0000 I CONTROL [initandlisten]
2021-08-01T05:41:32.259+0000 I CONTROL [initandlisten] ** WARNING: Access control is not enabled for the database.
2021-08-01T05:41:32.259+0000 I CONTROL [initandlisten] ** Read and write access to data and configuration is unrestricted.
2021-08-01T05:41:32.259+0000 I CONTROL [initandlisten]
2021-08-01T05:41:32.259+0000 I CONTROL [initandlisten]
2021-08-01T05:41:32.259+0000 I CONTROL [initandlisten] ** WARNING: /sys/kernel/mm/transparent_hugepage/enabled is 'always'.
2021-08-01T05:41:32.259+0000 I CONTROL [initandlisten] ** We suggest setting it to 'never'
2021-08-01T05:41:32.259+0000 I CONTROL [initandlisten]
re0:SECONDARY> use order
switched to db order
re0:SECONDARY> db.user.find()
Error: error: {
"ok" : 0,
"errmsg" : "not master and slaveOk=false",
"code" : 13435,
"codeName" : "NotMasterNoSlaveOk"
}
re0:SECONDARY>
上述查詢出現(xiàn)“not master and slaveOk = false "錯誤, 這很正常,默認(rèn)情況下SECONDARY是不允許讀寫的,需要設(shè)置slaveOk
例如
re0:SECONDARY> db.getMongo().setSlaveOk()
re0:SECONDARY> db.user.find()
{ "_id" : ObjectId("61064400ca2a42d1044bd372"), "username" : "zhouhc", "nickname" : "呢稱", "password" : "123123" }
db.getMongo().setSlaveOk()只是臨時的,一旦退出在進(jìn)入Mongo也會報錯。如果需要永久性設(shè)置可以參考:MONGODB副本集的從庫永久性設(shè)置SETSLAVEOK
宕機恢復(fù)
假設(shè)業(yè)務(wù)場景Master節(jié)點宕機了,那么將從Node1和Node2中選舉一個新的PRIMARY節(jié)點
如
C:\Users\zhc12>docker stop mongo_master
mongo_master
C:\Users\zhc12>docker exec -it mongo_node2 mongo
MongoDB shell version v3.4.9
connecting to: mongodb://127.0.0.1:27017
MongoDB server version: 3.4.9
Welcome to the MongoDB shell.
For interactive help, type "help".
For more comprehensive documentation, see
http://docs.mongodb.org/
Questions? Try the support group
http://groups.google.com/group/mongodb-user
Server has startup warnings:
2021-08-01T05:42:06.068+0000 I STORAGE [initandlisten]
2021-08-01T05:42:06.068+0000 I STORAGE [initandlisten] ** WARNING: Using the XFS filesystem is strongly recommended with the WiredTiger storage engine
2021-08-01T05:42:06.068+0000 I STORAGE [initandlisten] ** See http://dochub.mongodb.org/core/prodnotes-filesystem
2021-08-01T05:42:06.099+0000 I CONTROL [initandlisten]
2021-08-01T05:42:06.099+0000 I CONTROL [initandlisten] ** WARNING: Access control is not enabled for the database.
2021-08-01T05:42:06.099+0000 I CONTROL [initandlisten] ** Read and write access to data and configuration is unrestricted.
2021-08-01T05:42:06.099+0000 I CONTROL [initandlisten]
2021-08-01T05:42:06.099+0000 I CONTROL [initandlisten]
2021-08-01T05:42:06.099+0000 I CONTROL [initandlisten] ** WARNING: /sys/kernel/mm/transparent_hugepage/enabled is 'always'.
2021-08-01T05:42:06.099+0000 I CONTROL [initandlisten] ** We suggest setting it to 'never'
2021-08-01T05:42:06.099+0000 I CONTROL [initandlisten]
re0:PRIMARY>
這個時候NODE2就是副本集的PRIMARY節(jié)點。
這個時候重新啟動Mongo_master節(jié)點
C:\Users\zhc12>docker start mongo_master
mongo_master
進(jìn)入Mongo_master節(jié)點查看以下
C:\Users\zhc12>docker exec -it mongo_master mongo
MongoDB shell version v3.4.9
connecting to: mongodb://127.0.0.1:27017
MongoDB server version: 3.4.9
Server has startup warnings:
2021-08-01T09:15:04.910+0000 I STORAGE [initandlisten]
2021-08-01T09:15:04.910+0000 I STORAGE [initandlisten] ** WARNING: Using the XFS filesystem is strongly recommended with the WiredTiger storage engine
2021-08-01T09:15:04.910+0000 I STORAGE [initandlisten] ** See http://dochub.mongodb.org/core/prodnotes-filesystem
2021-08-01T09:15:05.256+0000 I CONTROL [initandlisten]
2021-08-01T09:15:05.256+0000 I CONTROL [initandlisten] ** WARNING: Access control is not enabled for the database.
2021-08-01T09:15:05.256+0000 I CONTROL [initandlisten] ** Read and write access to data and configuration is unrestricted.
2021-08-01T09:15:05.256+0000 I CONTROL [initandlisten]
2021-08-01T09:15:05.257+0000 I CONTROL [initandlisten]
2021-08-01T09:15:05.257+0000 I CONTROL [initandlisten] ** WARNING: /sys/kernel/mm/transparent_hugepage/enabled is 'always'.
2021-08-01T09:15:05.257+0000 I CONTROL [initandlisten] ** We suggest setting it to 'never'
2021-08-01T09:15:05.257+0000 I CONTROL [initandlisten]
re0:SECONDARY>
MongoBD已經(jīng)成為了SECONDARY節(jié)點。到這里Mongo副本集已經(jīng)基本布置好了,更多Mongo操作會在之后的博客中補充。
參考
MongoDB進(jìn)階與實戰(zhàn):微服務(wù)整合、性能優(yōu)化、架構(gòu)管理
MONGODB副本集的從庫永久性設(shè)置SETSLAVEOK