前面我們學(xué)習(xí)了mongo的環(huán)境搭建到文檔基礎(chǔ)操作,以及高級(jí)查詢處理等常見操作,但是一直都是處理的單機(jī)服務(wù)器,在我們實(shí)際生產(chǎn)中,使用單機(jī)風(fēng)險(xiǎn)會(huì)很高,如果是服務(wù)崩潰了或者不可訪問怎么辦,那么至少有一段時(shí)間不可用,如果是硬件出了問題,那么數(shù)據(jù)可能還要轉(zhuǎn)移到其他機(jī)器上。但是無論是什么樣的故障都可能或多或少帶來體驗(yàn)的問題,甚至可能造成數(shù)據(jù)丟失等風(fēng)險(xiǎn)。而MongoDB自身是支持復(fù)制集操作的,即將數(shù)據(jù)保存在多個(gè)服務(wù)上,副本集本身就是一組服務(wù),其中一個(gè)是主服務(wù),用于對(duì)外公開使用,處理客戶端的請(qǐng)求,除此之外還有多個(gè)備份服務(wù),如果主服務(wù)奔潰,那么則會(huì)通過選舉算法選出一個(gè)新的主服務(wù)。
復(fù)制集初體驗(yàn)
由于復(fù)制集需要不止一個(gè)mongo實(shí)例,那么我們可以考慮在當(dāng)前機(jī)器中啟動(dòng)多個(gè)mongo實(shí)例來模擬多個(gè)mongo服務(wù)器組建復(fù)制集。首先我們先把mongo.conf文件復(fù)制多份,用于不同的實(shí)例啟動(dòng),由于不記得當(dāng)前的每個(gè)配置的路徑,我們的mongo是啟動(dòng)狀態(tài),因此我們可以通過查看配置找到其他的目錄:
systemctl status mongod
可以看到當(dāng)前mongo的啟動(dòng)參數(shù):
* mongod.service - MongoDB Database Server
Loaded: loaded (/usr/lib/systemd/system/mongod.service; enabled; vendor preset: disabled)
Active: active (running) since ?? 2021-01-12 23:35:12 CST; 1h 16min ago
Docs: https://docs.mongodb.org/manual
Process: 1276 ExecStart=/usr/bin/mongod $OPTIONS (code=exited, status=0/SUCCESS)
Process: 1268 ExecStartPre=/usr/bin/chmod 0755 /var/run/mongodb (code=exited, status=0/SUCCESS)
Process: 1263 ExecStartPre=/usr/bin/chown mongod:mongod /var/run/mongodb (code=exited, status=0/SUCCESS)
Process: 1260 ExecStartPre=/usr/bin/mkdir -p /var/run/mongodb (code=exited, status=0/SUCCESS)
Main PID: 1554 (mongod)
CGroup: /system.slice/mongod.service
`-1554 /usr/bin/mongod -f /etc/mongod.conf
最后面我們可以看到當(dāng)前的mongo文件目錄在/usr/bin/目錄,而啟動(dòng)使用的配置文件則是在/etc目錄下,現(xiàn)在我們?nèi)tc目錄下,cp兩份配置文件:
cp mongod.conf mongod2.conf;
cp mongod.conf mongod3.conf;
創(chuàng)建完畢以后,我們將配置文件中的log存儲(chǔ)目錄,以及數(shù)據(jù)庫存儲(chǔ)目錄和啟動(dòng)端口pid文件修改,并且將啟動(dòng)的端口修改,其中需要注意一點(diǎn),我們需要在配置文件中指定當(dāng)前的replication參數(shù),即指定復(fù)制集名稱:
# mongod.conf
# for documentation of all options, see:
# http://docs.mongodb.org/manual/reference/configuration-options/
# where to write logging data.
systemLog:
destination: file
logAppend: true
#修改mongo日志名稱為/var/log/mongodb/mongod2.log和/var/log/mongodb/mongod3.log
path: /var/log/mongodb/mongod.log
# Where and how to store data.
storage:
#修改mongo數(shù)據(jù)庫存儲(chǔ)的目錄為/var/lib/mongo2和/var/lib/mongo3
dbPath: /var/lib/mongo
journal:
enabled: true
# engine:
# wiredTiger:
# how the process runs
processManagement:
fork: true # fork and run in background
#修改pid存放文件路徑名稱為/var/run/mongodb/mongod2.pid和/var/run/mongodb/mongod3.pid
pidFilePath: /var/run/mongodb/mongod.pid
timeZoneInfo: /usr/share/zoneinfo
# network interfaces
net:
#修改啟動(dòng)端口分別為27018和27019
port: 27020
bindIp: 0.0.0.0
#security:
#operationProfiling:
#replication:
replication:
replSetName: test-replication
全部修改完畢以后,我們需要檢查一下數(shù)據(jù)庫存放的目錄和日志目錄是否存在,由于日志是和原來的mongo進(jìn)程使用的同一個(gè)目錄,僅僅是文件名不同,因此不需要重新創(chuàng)建,而數(shù)據(jù)庫目錄/var/lib/mongo2和/var/lib/mongo3則是之前沒有的,我們需要先創(chuàng)建出來,否則無法啟動(dòng):
cd /var/lib;
mkdir mongo2/ mongo3/;
這些做完以后,我們?cè)偃?dòng)兩個(gè)mongo進(jìn)程:
/usr/bin/mongod -f /etc/mongod2.conf;
/usr/bin/mongod -f /etc/mongod3.conf;
可以看到輸出內(nèi)容:
about to fork child process, waiting until server is ready for connections.
forked process: 25498
child process started successfully, parent exiting
代表mongo進(jìn)程啟動(dòng)成功,這時(shí)候我們查詢一下當(dāng)前啟動(dòng)進(jìn)程列表進(jìn)行確認(rèn):
ps -ef|grep mongo
可以看到三個(gè)mongo進(jìn)程已經(jīng)存在:
mongod 1554 1 1 1??12 ? 00:01:11 /usr/bin/mongod -f /etc/mongod.conf
root 25498 1 1 1??12 ? 00:00:55 ./mongod -f /etc/mongod2.conf
root 25696 1 1 1??12 ? 00:00:54 ./mongod -f /etc/mongod3.conf
root 107395 2401 0 01:08 pts/0 00:00:00 grep --color=auto mongo
現(xiàn)在我們可以開始搭建復(fù)制集了,首先我們需要使用shell連接我們想要指定為主服務(wù)的mongo機(jī)器:
/usr/bin/mongo --port 28020;
接著我們初始化復(fù)制集:
rs.initiate({ _id:"test-replication", members:[{ _id:0, host:"192.168.1.130:27020" },{ _id:1, host:"192.168.1.130:27018" },{ _id:2, host:"192.168.1.130:27019" }] })
可以看到如下的響應(yīng):
{
"ok" : 1,
"$clusterTime" : {
"clusterTime" : Timestamp(1610473584, 1),
"signature" : {
"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
"keyId" : NumberLong(0)
}
},
"operationTime" : Timestamp(1610473584, 1)
}
代表當(dāng)前復(fù)制集創(chuàng)建成功,我們查看當(dāng)前復(fù)制集的狀態(tài):
rs.status();
可以看到一大堆關(guān)于復(fù)制集的內(nèi)容,其中每一個(gè)服務(wù)的實(shí)例都在上面,這個(gè)時(shí)候我們可以看到,光標(biāo)的顯示已經(jīng)變成了剛才設(shè)置的復(fù)制集的名稱了:
test-replication:PRIMARY>
這里可以看到前面是復(fù)制集的名稱,后面是代表當(dāng)前實(shí)例的角色,在復(fù)制集中有PRIMARY
和SECONDARY
兩種角色,其中PRIMARY
角色代表當(dāng)前是復(fù)制集中的主服務(wù),而SECONDARY
角色則代表當(dāng)前的mongo服務(wù)實(shí)例是備份服務(wù)。
復(fù)制集寫入數(shù)據(jù)/讀取數(shù)據(jù) 關(guān)閉復(fù)制集
復(fù)制集啟動(dòng)完畢以后,我們可以嘗試在主服務(wù)中寫入數(shù)據(jù),然后在備份服務(wù)中讀取剛才主服務(wù)中寫入的數(shù)據(jù)查看復(fù)制集是否正常工作
db.test.insert({"abc":"test"});
//在主服務(wù)中查看剛剛插入的數(shù)據(jù)
db.test.find();
{ "_id" : ObjectId("5ffde4aad555549b75c807f6"), "abc" : "test" }
//再去啟動(dòng)一個(gè)shell連接,連接到其中任意一個(gè)備份服務(wù)
/usr/bin/mongo --port 27018 --host 192.168.1.130
//需要注意的時(shí)候,當(dāng)前的會(huì)話第一次連接備份服務(wù)器,需要先調(diào)用rs.slaveOk();進(jìn)行初始化,否則默認(rèn)情況下當(dāng)前備份服務(wù)器會(huì)報(bào)錯(cuò),errmsg:not master and slaveOk=false
test-replication:SECONDARY> rs.slaveOk();
//接著查詢
use replication;
db.test.find();
//可以看到我們剛剛插入的數(shù)據(jù)已經(jīng)能查詢出來了
{ "_id" : ObjectId("5ffde4aad555549b75c807f6"), "abc" : "test" }
查看當(dāng)前主服務(wù)節(jié)點(diǎn)是否為我們指定的28020端口節(jié)點(diǎn):
rs.isMaster();
//響應(yīng)結(jié)果為
{
"hosts" : [
"192.168.1.130:27020",
"192.168.1.130:27018",
"192.168.1.130:27019"
],
"setName" : "test-replication",
"setVersion" : 1,
"ismaster" : false,
"secondary" : true,
"primary" : "192.168.1.130:27020",
"me" : "192.168.1.130:27018",
"lastWrite" : {
"opTime" : {
"ts" : Timestamp(1610477545, 1),
"t" : NumberLong(1)
},
"lastWriteDate" : ISODate("2021-01-12T18:52:25Z"),
"majorityOpTime" : {
"ts" : Timestamp(1610477545, 1),
"t" : NumberLong(1)
},
"majorityWriteDate" : ISODate("2021-01-12T18:52:25Z")
},
"maxBsonObjectSize" : 16777216,
"maxMessageSizeBytes" : 48000000,
"maxWriteBatchSize" : 100000,
"localTime" : ISODate("2021-01-12T18:52:35.359Z"),
"logicalSessionTimeoutMinutes" : 30,
"connectionId" : 28,
"minWireVersion" : 0,
"maxWireVersion" : 8,
"readOnly" : false,
"ok" : 1,
"$clusterTime" : {
"clusterTime" : Timestamp(1610477545, 1),
"signature" : {
"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
"keyId" : NumberLong(0)
}
},
"operationTime" : Timestamp(1610477545, 1)
}
可以看到當(dāng)前復(fù)制集的機(jī)器ip集合以及當(dāng)前服務(wù)并非主服務(wù)節(jié)點(diǎn)("ismaster" : false),同時(shí)也可以看到主服務(wù)節(jié)點(diǎn)的ip信息為:("primary" : "192.168.1.130:27020")
故障轉(zhuǎn)移
mongo的復(fù)制集自身特性就支持自動(dòng)故障轉(zhuǎn)移
功能,即如果我們使用過程中mongo的主節(jié)點(diǎn)掛了,導(dǎo)致不可用了,這個(gè)時(shí)候mongo就會(huì)從備份節(jié)點(diǎn)中選舉一個(gè)成為最新的主節(jié)點(diǎn),為了驗(yàn)證這個(gè)功能,我們選擇將目前的主節(jié)點(diǎn)服務(wù)關(guān)閉:
ps -ef|grep mongo
//所有的mongo列表
root 3156 1 1 18:06 ? 00:04:45 /usr/bin/mongod -f /etc/mongod1.conf
root 3358 1 1 18:06 ? 00:05:01 /usr/bin/mongod -f /etc/mongod2.conf
root 3596 1 1 18:07 ? 00:04:58 /usr/bin/mongod -f /etc/mongod3.conf
可以看到mongod1.conf啟動(dòng)的服務(wù)pid為3156,我們將其關(guān)閉掉:
//模擬mongo服務(wù)異常奔潰(直接強(qiáng)制kill)
kill -9 3156
//再次查看當(dāng)前的mongo服務(wù)列表
ps -ef|grep mongo
//可以看到現(xiàn)在只有兩個(gè)了
root 3358 1 1 18:06 ? 00:05:11 /usr/bin/mongod -f /etc/mongod2.conf
root 3596 1 1 18:07 ? 00:05:06 /usr/bin/mongod -f /etc/mongod3.conf
root 97710 2414 0 23:22 pts/0 00:00:00 grep --color=auto mongo
我們現(xiàn)在隨便連接這剩下兩個(gè)的任何一個(gè),去查看現(xiàn)在的集群主節(jié)點(diǎn)是哪個(gè):
/usr/bin/mongo --host 192.168.1.130 --port 27018;
//這里比較幸運(yùn),隨便登錄了一個(gè),正好是選出來的新的主節(jié)點(diǎn)
test-replication:PRIMARY> db.isMaster();
//這里前面的primary已經(jīng)表明了當(dāng)前節(jié)點(diǎn)是主服務(wù),不過我們依然選擇通過isMaster函數(shù)查看具體信息
{
"hosts" : [
"192.168.1.130:27020",
"192.168.1.130:27018",
"192.168.1.130:27019"
],
"setName" : "test-replication",
"setVersion" : 1,
"ismaster" : true,
"secondary" : false,
"primary" : "192.168.1.130:27018",
"me" : "192.168.1.130:27018",
"electionId" : ObjectId("7fffffff0000000000000004"),
"lastWrite" : {
"opTime" : {
"ts" : Timestamp(1610724217, 1),
"t" : NumberLong(4)
},
"lastWriteDate" : ISODate("2021-01-15T15:23:37Z"),
"majorityOpTime" : {
"ts" : Timestamp(1610724217, 1),
"t" : NumberLong(4)
},
"majorityWriteDate" : ISODate("2021-01-15T15:23:37Z")
},
"maxBsonObjectSize" : 16777216,
"maxMessageSizeBytes" : 48000000,
"maxWriteBatchSize" : 100000,
"localTime" : ISODate("2021-01-15T15:23:38.444Z"),
"logicalSessionTimeoutMinutes" : 30,
"connectionId" : 80,
"minWireVersion" : 0,
"maxWireVersion" : 8,
"readOnly" : false,
"ok" : 1,
"$clusterTime" : {
"clusterTime" : Timestamp(1610724217, 1),
"signature" : {
"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
"keyId" : NumberLong(0)
}
},
"operationTime" : Timestamp(1610724217, 1)
}
可以看到"primary" : "192.168.1.130:27018",現(xiàn)在的主服務(wù)節(jié)點(diǎn)已經(jīng)變更為了27018端口,而如果我們正常使用過程中,其實(shí)是無感知的,因?yàn)檫B接的是整個(gè)復(fù)制集,并不知道其主節(jié)點(diǎn)崩潰以及變更操作,并且可以在很短的時(shí)間內(nèi)完成轉(zhuǎn)移主服務(wù)節(jié)點(diǎn),繼續(xù)對(duì)外提供服務(wù)。
修改復(fù)制集配置
在我們的復(fù)制集啟動(dòng)以后,正常運(yùn)行過程中,往往是不會(huì)將整個(gè)復(fù)制集重啟,那么如果不支持動(dòng)態(tài)修改配置的話,對(duì)于復(fù)制集的維護(hù)勢(shì)必很困難,不過還好,mongo自身考慮到了這一點(diǎn),支持我們?cè)谶\(yùn)行中動(dòng)態(tài)修改部分復(fù)制集的配置。
動(dòng)態(tài)添加/減少副本服務(wù)
在使用過程中,我們可以隨時(shí)根據(jù)情況調(diào)整復(fù)制集的大小,進(jìn)行動(dòng)態(tài)的新增或者刪除節(jié)點(diǎn)服務(wù),為了演示效果,我們來復(fù)制一份配置文件,改名為mongod4.conf,將日志文件以及數(shù)據(jù)存放目錄,啟動(dòng)端口等進(jìn)行修改,(別忘記檢查這些目錄是否存在,不存在要?jiǎng)?chuàng)建一下):
mkdir /var/lib/mongo4;
//啟動(dòng)新的mongo實(shí)例
/usr/bin/mongod -f /etc/mongod4.conf;
//檢查是否已經(jīng)啟動(dòng)成功
ps -ef|grep mongod;
//可以看到已經(jīng)啟動(dòng)了
root 40069 1 6 00:27 ? 00:00:01 /usr/bin/mongod -f /etc/mongod4.conf
接著我們連接上主節(jié)點(diǎn)服務(wù),開始添加剛剛啟動(dòng)的服務(wù)實(shí)例:
test-replication:PRIMARY> rs.add("192.168.1.130:27021");
//輸出如下
{
"ok" : 1,
"$clusterTime" : {
"clusterTime" : Timestamp(1610730168, 1),
"signature" : {
"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
"keyId" : NumberLong(0)
}
},
"operationTime" : Timestamp(1610730168, 1)
}
//我們?cè)俅螜z查當(dāng)前復(fù)制集的所有節(jié)點(diǎn)信息
test-replication:PRIMARY> rs.status();
//可以看到members中已經(jīng)有我們剛剛指定的27021端口的mongo實(shí)例
{
"set" : "test-replication",
"date" : ISODate("2021-01-15T17:04:19.209Z"),
"myState" : 1,
"term" : NumberLong(4),
"syncingTo" : "",
"syncSourceHost" : "",
"syncSourceId" : -1,
"heartbeatIntervalMillis" : NumberLong(2000),
"majorityVoteCount" : 3,
"writeMajorityCount" : 3,
"optimes" : {
"lastCommittedOpTime" : {
"ts" : Timestamp(1610730258, 1),
"t" : NumberLong(4)
},
"lastCommittedWallTime" : ISODate("2021-01-15T17:04:18.456Z"),
"readConcernMajorityOpTime" : {
"ts" : Timestamp(1610730258, 1),
"t" : NumberLong(4)
},
"readConcernMajorityWallTime" : ISODate("2021-01-15T17:04:18.456Z"),
"appliedOpTime" : {
"ts" : Timestamp(1610730258, 1),
"t" : NumberLong(4)
},
"durableOpTime" : {
"ts" : Timestamp(1610730258, 1),
"t" : NumberLong(4)
},
"lastAppliedWallTime" : ISODate("2021-01-15T17:04:18.456Z"),
"lastDurableWallTime" : ISODate("2021-01-15T17:04:18.456Z")
},
"lastStableRecoveryTimestamp" : Timestamp(1610730238, 1),
"lastStableCheckpointTimestamp" : Timestamp(1610730238, 1),
"electionCandidateMetrics" : {
"lastElectionReason" : "electionTimeout",
"lastElectionDate" : ISODate("2021-01-15T15:22:27.376Z"),
"electionTerm" : NumberLong(4),
"lastCommittedOpTimeAtElection" : {
"ts" : Timestamp(1610724136, 1),
"t" : NumberLong(3)
},
"lastSeenOpTimeAtElection" : {
"ts" : Timestamp(1610724136, 1),
"t" : NumberLong(3)
},
"numVotesNeeded" : 2,
"priorityAtElection" : 1,
"electionTimeoutMillis" : NumberLong(10000),
"numCatchUpOps" : NumberLong(0),
"newTermStartDate" : ISODate("2021-01-15T15:22:27.386Z"),
"wMajorityWriteAvailabilityDate" : ISODate("2021-01-15T15:22:28.549Z")
},
"electionParticipantMetrics" : {
"votedForCandidate" : true,
"electionTerm" : NumberLong(3),
"lastVoteDate" : ISODate("2021-01-15T10:07:01.837Z"),
"electionCandidateMemberId" : 0,
"voteReason" : "",
"lastAppliedOpTimeAtElection" : {
"ts" : Timestamp(1610651352, 1),
"t" : NumberLong(2)
},
"maxAppliedOpTimeInSet" : {
"ts" : Timestamp(1610651352, 1),
"t" : NumberLong(2)
},
"priorityAtElection" : 1
},
"members" : [
{
"_id" : 0,
"name" : "192.168.1.130:27020",
"health" : 0,
"state" : 8,
"stateStr" : "(not reachable/healthy)",
"uptime" : 0,
"optime" : {
"ts" : Timestamp(0, 0),
"t" : NumberLong(-1)
},
"optimeDurable" : {
"ts" : Timestamp(0, 0),
"t" : NumberLong(-1)
},
"optimeDate" : ISODate("1970-01-01T00:00:00Z"),
"optimeDurableDate" : ISODate("1970-01-01T00:00:00Z"),
"lastHeartbeat" : ISODate("2021-01-15T17:04:18.412Z"),
"lastHeartbeatRecv" : ISODate("2021-01-15T15:22:16.398Z"),
"pingMs" : NumberLong(0),
"lastHeartbeatMessage" : "Error connecting to 192.168.1.130:27020 :: caused by :: Connection refused",
"syncingTo" : "",
"syncSourceHost" : "",
"syncSourceId" : -1,
"infoMessage" : "",
"configVersion" : -1
},
{
"_id" : 1,
"name" : "192.168.1.130:27018",
"health" : 1,
"state" : 1,
"stateStr" : "PRIMARY",
"uptime" : 25044,
"optime" : {
"ts" : Timestamp(1610730258, 1),
"t" : NumberLong(4)
},
"optimeDate" : ISODate("2021-01-15T17:04:18Z"),
"syncingTo" : "",
"syncSourceHost" : "",
"syncSourceId" : -1,
"infoMessage" : "",
"electionTime" : Timestamp(1610724147, 1),
"electionDate" : ISODate("2021-01-15T15:22:27Z"),
"configVersion" : 2,
"self" : true,
"lastHeartbeatMessage" : ""
},
{
"_id" : 2,
"name" : "192.168.1.130:27019",
"health" : 1,
"state" : 2,
"stateStr" : "SECONDARY",
"uptime" : 25032,
"optime" : {
"ts" : Timestamp(1610730248, 1),
"t" : NumberLong(4)
},
"optimeDurable" : {
"ts" : Timestamp(1610730248, 1),
"t" : NumberLong(4)
},
"optimeDate" : ISODate("2021-01-15T17:04:08Z"),
"optimeDurableDate" : ISODate("2021-01-15T17:04:08Z"),
"lastHeartbeat" : ISODate("2021-01-15T17:04:18.402Z"),
"lastHeartbeatRecv" : ISODate("2021-01-15T17:04:18.385Z"),
"pingMs" : NumberLong(0),
"lastHeartbeatMessage" : "",
"syncingTo" : "192.168.1.130:27018",
"syncSourceHost" : "192.168.1.130:27018",
"syncSourceId" : 1,
"infoMessage" : "",
"configVersion" : 2
},
{
"_id" : 3,
"name" : "192.168.1.130:27021",
"health" : 1,
"state" : 2,
"stateStr" : "SECONDARY",
"uptime" : 90,
"optime" : {
"ts" : Timestamp(1610730248, 1),
"t" : NumberLong(4)
},
"optimeDurable" : {
"ts" : Timestamp(1610730248, 1),
"t" : NumberLong(4)
},
"optimeDate" : ISODate("2021-01-15T17:04:08Z"),
"optimeDurableDate" : ISODate("2021-01-15T17:04:08Z"),
"lastHeartbeat" : ISODate("2021-01-15T17:04:18.403Z"),
"lastHeartbeatRecv" : ISODate("2021-01-15T17:04:17.661Z"),
"pingMs" : NumberLong(1),
"lastHeartbeatMessage" : "",
"syncingTo" : "192.168.1.130:27019",
"syncSourceHost" : "192.168.1.130:27019",
"syncSourceId" : 2,
"infoMessage" : "",
"configVersion" : 2
}
],
"ok" : 1,
"$clusterTime" : {
"clusterTime" : Timestamp(1610730258, 1),
"signature" : {
"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
"keyId" : NumberLong(0)
}
},
"operationTime" : Timestamp(1610730258, 1)
}
同理,我們也可以移除添加的實(shí)例節(jié)點(diǎn):
test-replication:PRIMARY> rs.remove("192.168.1.130:27021");
//輸出如下
{
"ok" : 1,
"$clusterTime" : {
"clusterTime" : Timestamp(1610730771, 1),
"signature" : {
"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
"keyId" : NumberLong(0)
}
},
"operationTime" : Timestamp(1610730771, 1)
}
這個(gè)時(shí)候我們?cè)偃z查復(fù)制集狀態(tài),可以看到27021端口的mongo已經(jīng)被踢出了復(fù)制集。
需要注意的一點(diǎn)是,在我們做動(dòng)態(tài)配置的時(shí)候,有可能會(huì)看到shell連接上提示一堆無法連接數(shù)據(jù)庫等錯(cuò)誤,這種是正常的,其原理是在我們修改動(dòng)態(tài)配置的時(shí)候,復(fù)制集中的主節(jié)點(diǎn)會(huì)把所有的連接給強(qiáng)制關(guān)閉。然后將主節(jié)點(diǎn)退化成普通備份節(jié)點(diǎn),用于方便接受配置,然后在接受完配置以后,會(huì)恢復(fù)為主節(jié)點(diǎn)的狀態(tài),一切即可照舊使用。
重新加載配置config/reconfig函數(shù)
在使用過程中,有時(shí)也會(huì)遇到需求,將一部分mongo的配置信息修改掉,假設(shè)我們這里獲取到的復(fù)制集節(jié)點(diǎn)配置信息里的所有服務(wù)都是ip+端口號(hào)的方式,突然要我們修改為域名 + 端口的方式,那么這個(gè)時(shí)候我們最好的解決方式就是重新加載config:
//獲取當(dāng)前節(jié)點(diǎn)配置信息
rs.config();
//輸出如下
{
"_id" : "test-replication",
"version" : 3,
"protocolVersion" : NumberLong(1),
"writeConcernMajorityJournalDefault" : true,
"members" : [
{
"_id" : 0,
"host" : "192.168.1.130:27020",
"arbiterOnly" : false,
"buildIndexes" : true,
"hidden" : false,
"priority" : 1,
"tags" : {
},
"slaveDelay" : NumberLong(0),
"votes" : 1
},
{
"_id" : 1,
"host" : "192.168.1.130:27018",
"arbiterOnly" : false,
"buildIndexes" : true,
"hidden" : false,
"priority" : 1,
"tags" : {
},
"slaveDelay" : NumberLong(0),
"votes" : 1
},
{
"_id" : 2,
"host" : "192.168.1.130:27019",
"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("5ffde070640dd6d0d8040a36")
}
}
//我們來修改members中每一個(gè)host的信息
var config = rs.config();
config.members[0].host = "localhost:27020";
config.members[1].host = "localhost:27018";
config.members[2].host = "localhost:27019";
rs.reconfig(config);
即可完成重新配置的config,同理我們也可以通過此種方式添加或者刪除一些新的節(jié)點(diǎn)
復(fù)制集必須知道的其他概念
在我們?cè)O(shè)計(jì)一個(gè)復(fù)制集的時(shí)候,有些概念我們必須知道,這些概念也會(huì)或多或少的影響我們最終設(shè)計(jì)復(fù)制集。
大多數(shù)節(jié)點(diǎn)可用
我們知道,mongo中復(fù)制集節(jié)點(diǎn),在主節(jié)點(diǎn)出現(xiàn)故障或者無法響應(yīng)以后,其他的副本集會(huì)進(jìn)行選舉,最終選出來一個(gè)新的主節(jié)點(diǎn),但是我們要知道m(xù)ongoDB的副本集是遵循一個(gè)大多數(shù)節(jié)點(diǎn)的概念的,即:
選擇主節(jié)點(diǎn)時(shí)需要由大多數(shù)決定,主節(jié)點(diǎn)只有在得到大多數(shù)支持時(shí)才能繼續(xù)作為主節(jié)點(diǎn),寫操作被復(fù)制到大多數(shù)成員時(shí)這個(gè)寫操作就是安全的。
假設(shè)說這里我們有一個(gè)五個(gè)節(jié)點(diǎn)組成的復(fù)制集,但是主節(jié)點(diǎn)和其中兩個(gè)備份節(jié)點(diǎn)因?yàn)楣收蠈?dǎo)致宕機(jī)了,這個(gè)時(shí)候僅有兩個(gè)備份節(jié)點(diǎn)可用,這種情況下,由于兩個(gè)節(jié)點(diǎn)不滿足大多數(shù)節(jié)點(diǎn)的原則,因此無法選出新的主節(jié)點(diǎn),而是兩個(gè)備份節(jié)點(diǎn)和三個(gè)不可用節(jié)點(diǎn),整個(gè)復(fù)制集無法對(duì)外提供服務(wù)了??赡軙?huì)提出質(zhì)疑,這種設(shè)計(jì)會(huì)影響服務(wù)使用呀,為什么MongoDB會(huì)選擇這種選舉策略呢?我們想一下,三個(gè)不可用的節(jié)點(diǎn)服務(wù),是真的宕機(jī)了嗎?其實(shí)也未必,這種不可用的可能性有很多,比如網(wǎng)絡(luò)故障,或者網(wǎng)絡(luò)延遲等,都有可能導(dǎo)致這個(gè)問題的出現(xiàn)。而且我們可以想象一下,如果進(jìn)行選舉的話,大部分節(jié)點(diǎn)推舉即可成為主節(jié)點(diǎn),那么這種情況下,不可用的三個(gè)節(jié)點(diǎn)服務(wù)有可能互相能連接上,對(duì)于它們來說不可用的服務(wù)節(jié)點(diǎn)是另外兩臺(tái),反過來也是一樣,這個(gè)時(shí)候彼此之間都進(jìn)行選舉操作,如果沒有大多數(shù)的限制的話,就有可能出現(xiàn)兩邊各自選舉出了一個(gè)主節(jié)點(diǎn),這樣整個(gè)復(fù)制集就會(huì)出現(xiàn)多個(gè)主節(jié)點(diǎn),如果是多個(gè)主節(jié)點(diǎn)我們?cè)谑褂玫臅r(shí)候就有可能出現(xiàn),一個(gè)主節(jié)點(diǎn)剛剛寫入,另一個(gè)主節(jié)點(diǎn)可以刪除的現(xiàn)象,導(dǎo)致整個(gè)復(fù)制集的數(shù)據(jù)不一致的情況,也給開發(fā)以及數(shù)據(jù)同步帶來了很大的困難,因此mongoDB選擇了很多中間件都用的方案,主節(jié)點(diǎn)選舉必須是大部分節(jié)點(diǎn)服務(wù)都同意才可以,防止出現(xiàn)多個(gè)主節(jié)點(diǎn)的情況。
仲裁節(jié)點(diǎn)
在mongoDB中,數(shù)據(jù)節(jié)點(diǎn)除了我們說的主節(jié)點(diǎn)服務(wù)和備份節(jié)點(diǎn)服務(wù)以外,還有一個(gè)角色--仲裁節(jié)點(diǎn)
,這個(gè)節(jié)點(diǎn)與其他兩個(gè)完全不同。仲裁節(jié)點(diǎn)
既不會(huì)像備份節(jié)點(diǎn)一樣,參與主節(jié)點(diǎn)的數(shù)據(jù)備份和數(shù)據(jù)同步,也不會(huì)和主節(jié)點(diǎn)一樣,可以對(duì)外提供讀寫服務(wù),實(shí)際上,仲裁節(jié)點(diǎn)
的作用只有一個(gè),即參與投票選舉新的主節(jié)點(diǎn)。
由于仲裁者并不需要履行傳統(tǒng)mongod服務(wù)器的責(zé)任,所以可以將仲裁者作為輕量級(jí)進(jìn)程,運(yùn)行在配置比較差的服務(wù)器上,也可以在我們不需要多份備份的時(shí)候,節(jié)省服務(wù)器內(nèi)存空間的時(shí)候使用,將部分服務(wù)節(jié)點(diǎn)變?yōu)橹俨霉?jié)點(diǎn)。
仲裁節(jié)點(diǎn)的創(chuàng)建
我們想要給復(fù)制集中創(chuàng)建一個(gè)仲裁節(jié)點(diǎn),有兩種方式,第一種則是我們?cè)趩?dòng)復(fù)制集的時(shí)候,通過rs.initiate
函數(shù)將復(fù)制集節(jié)點(diǎn)信息傳入作為復(fù)制集配置文件啟動(dòng)的方式,此種方式中,我們只需要在傳入的config中指定需要設(shè)置為仲裁節(jié)點(diǎn)的服務(wù)信息,添加屬性arbiterOnly:true
即可,例如:
rs.initiate({ _id:"test-replication", members:[{ _id:0, host:"192.168.1.130:27020" },{ _id:1, host:"192.168.1.130:27018" },{ _id:2, host:"192.168.1.130:27019",arbiterOnly:true}] })
這樣我們啟動(dòng)的復(fù)制集節(jié)點(diǎn)的時(shí)候,27019端口的節(jié)點(diǎn)就會(huì)變成仲裁節(jié)點(diǎn)了,當(dāng)然,如果我們復(fù)制集已經(jīng)在運(yùn)行過程中,這個(gè)時(shí)候我們需要添加一個(gè)仲裁節(jié)點(diǎn)的話,也可以使用rs.addArb
輔助函數(shù)添加一個(gè)仲裁節(jié)點(diǎn),使用如下:
rs.addArb("192.168.1.130:27022");
當(dāng)然此函數(shù)的作用與我們直接使用rs.add()
函數(shù),在配置中指定arbiterOnly:true的效果是一樣的
注意:仲裁節(jié)點(diǎn)會(huì)給我們開發(fā)帶來一定的好處,比如當(dāng)我們復(fù)制集中現(xiàn)有的節(jié)點(diǎn)是偶數(shù)的時(shí)候,可能這個(gè)時(shí)候選舉無法選出主節(jié)點(diǎn),或者防止出現(xiàn)兩個(gè)節(jié)點(diǎn)選票一致的情況,這個(gè)時(shí)候我們可以添加一個(gè)仲裁節(jié)點(diǎn)上去,輔助完成選舉,當(dāng)然,在實(shí)際使用中最多僅允許出現(xiàn)一個(gè)仲裁節(jié)點(diǎn),而且仲裁節(jié)點(diǎn)本身也不是一定就需要的,最好只在節(jié)點(diǎn)數(shù)在偶數(shù)的時(shí)候使用,如果是奇數(shù)情況下建議不要使用仲裁節(jié)點(diǎn),因?yàn)榧僭O(shè)當(dāng)前是三個(gè)節(jié)點(diǎn)的復(fù)制集,如果加入了一個(gè)仲裁節(jié)點(diǎn),那么就代表我們的復(fù)雜度提升了1/3,本來只要67%的可用就可以繼續(xù)使用復(fù)制集,現(xiàn)在需要維持在75%可用。而且節(jié)點(diǎn)越多,選舉的時(shí)間就會(huì)成指數(shù)的增長(zhǎng),因此mongoDB官方也有數(shù)量的上限,復(fù)制集最多不能超過五十個(gè)節(jié)點(diǎn)。
優(yōu)先級(jí)與隱藏節(jié)點(diǎn)
如果說我們?cè)诔跏蓟瘡?fù)制集之前,僅僅指定了多個(gè)mongo服務(wù)的信息,然后初始化整個(gè)復(fù)制集,接著我們?nèi)ミB接其中任意一個(gè)節(jié)點(diǎn),這個(gè)時(shí)候如果是選舉完了,可以看到當(dāng)前的角色,正常情況下,我們會(huì)發(fā)現(xiàn)在沒有其他配置的情況下,我們的主節(jié)點(diǎn)一般會(huì)是指定的復(fù)制集節(jié)點(diǎn)列表的第一個(gè)服務(wù),當(dāng)然這個(gè)和mongo的選舉機(jī)制有關(guān)系,同時(shí)也和一個(gè)參數(shù)有關(guān),即priority (優(yōu)先級(jí))
,在我們沒有指定這個(gè)參數(shù)的時(shí)候,默認(rèn)每個(gè)備份節(jié)點(diǎn)的優(yōu)先級(jí)都是1,這也是為什么在都相同的情況下,不會(huì)出現(xiàn)其他節(jié)點(diǎn)成為主節(jié)點(diǎn)的原因。
這里需要知道一點(diǎn),優(yōu)先級(jí)的取值范圍是 0-100,默認(rèn)情況下的值為1,值越小理論上越不活躍,即被選為主節(jié)點(diǎn)的可能性也是越低,但是當(dāng)我們把一個(gè)備份節(jié)點(diǎn)設(shè)置為0優(yōu)先級(jí)的時(shí)候,就會(huì)導(dǎo)致這個(gè)節(jié)點(diǎn)永遠(yuǎn)是個(gè)備份節(jié)點(diǎn),永遠(yuǎn)不可能被選為主節(jié)點(diǎn),這種節(jié)點(diǎn)被稱之為被動(dòng)成員節(jié)點(diǎn)。因此我們也可以理解為,在數(shù)據(jù)等其他因素都一樣的情況下,如果我們給某節(jié)點(diǎn)指定了更高的優(yōu)先級(jí),那么在復(fù)制集初始化以后(重新選舉也是一樣),會(huì)是優(yōu)先級(jí)更大的先被選為主節(jié)點(diǎn)(只要超過大半的其他節(jié)點(diǎn)在選舉中投票同意即可)。
指定優(yōu)先級(jí)用法
我們?cè)诔跏蓟瘡?fù)制集的時(shí)候可以顯式指定優(yōu)先級(jí):
rs.initiate({ _id:"test-replication", members:[{ _id:0, host:"192.168.1.130:27020",priority : 100},{ _id:1, host:"192.168.1.130:27018" },{ _id:2, host:"192.168.1.130:27019" }] })
同樣也可以使用rs.add
以及修改config等方式去指定優(yōu)先級(jí)
隱藏節(jié)點(diǎn)
需要注意的是,客戶端在訪問復(fù)制集的時(shí)候,只會(huì)訪問開放的主節(jié)點(diǎn)和備份節(jié)點(diǎn),而如果節(jié)點(diǎn)是隱藏的,是不會(huì)被請(qǐng)求的,不管是讀取還是寫入請(qǐng)求都不會(huì)訪問隱藏節(jié)點(diǎn)。而且絕大多數(shù)情況下,隱藏節(jié)點(diǎn)是不會(huì)被選舉成為主節(jié)點(diǎn)的,但是自身是會(huì)參與投票選舉的,將節(jié)點(diǎn)隱藏的首要條件是當(dāng)前節(jié)點(diǎn)的優(yōu)先級(jí)為0,并且不為主節(jié)點(diǎn)才可以進(jìn)行隱藏
如果想要將某個(gè)節(jié)點(diǎn)設(shè)置為隱藏節(jié)點(diǎn),我們可以將其自身的config添加 members[n].hidden=true
,當(dāng)然在此之前我們也需要先將其的優(yōu)先級(jí)設(shè)置為0,即members[n].priority=0
,如下:
var cfg = rs.conf();
cfg.members[0].priority = 0
cfg.members[0].hidden = true
rs.reconfig(cfg);
即可完成對(duì)1號(hào)節(jié)點(diǎn)的隱藏操作,這個(gè)時(shí)候我們?cè)偃ナ褂?code>rs.status()或者db.isMaster()
函數(shù)查詢,會(huì)發(fā)現(xiàn)第一個(gè)節(jié)點(diǎn)的信息已經(jīng)查詢不到了
延遲備份節(jié)點(diǎn)
在實(shí)際使用過程中,有時(shí)為了防止出現(xiàn)數(shù)據(jù)因?yàn)橐馔饣蛘呷藶閷?dǎo)致的損壞,有時(shí)候會(huì)選擇定期備份的方式,但除了這種方式以外我們還可以給部分節(jié)點(diǎn)設(shè)置為延遲備份節(jié)點(diǎn),只需要在配置中指定slaveDelay : <seconds>
即可,單位是秒,假設(shè)我們?cè)O(shè)置的是延遲3600s,當(dāng)前時(shí)間是9.53分,那么被設(shè)置為延遲備份節(jié)點(diǎn)的服務(wù),最新的數(shù)據(jù)則只有8.53之前的,修改方式如下:
var cfg = rs.conf();
cfg.members[0].priority = 0;
cfg.members[0].hidden = true;
cfg.members[0].slaveDelay = 3600;
rs.reconfig(cfg);
配置為延遲備份節(jié)點(diǎn)的服務(wù)優(yōu)先級(jí)方面必須是0,由于此部分?jǐn)?shù)據(jù)是延遲的,存在數(shù)據(jù)不準(zhǔn)確的情況,一般不建議開放出去,通常也會(huì)配置為隱藏節(jié)點(diǎn),防止被客戶端請(qǐng)求訪問到獲取臟數(shù)據(jù)