mongoDB高級(jí)篇-Mongo復(fù)制集實(shí)踐

前面我們學(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ù)制集中有PRIMARYSECONDARY兩種角色,其中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ù)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,818評(píng)論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,185評(píng)論 3 414
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 175,656評(píng)論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,647評(píng)論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,446評(píng)論 6 405
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 54,951評(píng)論 1 321
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,041評(píng)論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,189評(píng)論 0 287
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,718評(píng)論 1 333
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,602評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,800評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,316評(píng)論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,045評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,419評(píng)論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,671評(píng)論 1 281
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,420評(píng)論 3 390
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,755評(píng)論 2 371

推薦閱讀更多精彩內(nèi)容