概述
MongoDB 的副本集(replica set)是一組維護同一份數(shù)據(jù)集的 mongod 進程。副本集保障了冗余性和高可用,這基本是生產(chǎn)環(huán)境的基本要求。本篇將介紹副本集、組件及其架構(gòu)。
冗余性和數(shù)據(jù)可用性
復(fù)本提供了冗余,并且增加了數(shù)據(jù)可用性。通過不同數(shù)據(jù)庫服務(wù)器的多個數(shù)據(jù)副本,副本集相比單個數(shù)據(jù)庫服務(wù)器容錯性更高。
在某些情況下,因為客戶度可以向不同的服務(wù)器發(fā)送讀操作請求,副本可以提高讀取數(shù)據(jù)的并發(fā)量。在不同的數(shù)據(jù)中心維護數(shù)據(jù)副本對分布式應(yīng)用而言。能夠?qū)崿F(xiàn)數(shù)據(jù)異地備份和提高可用性。維護額外的副本也能滿足如災(zāi)難恢復(fù)、報表或備份等目的。
MongoDB 的復(fù)制
副本集其實就是維護同個數(shù)據(jù)集的 mongod 進程組。一個副本集包括了多個數(shù)據(jù)容錯節(jié)點和一個可選的仲裁節(jié)點。在數(shù)據(jù)容錯節(jié)點中,有且僅有一個節(jié)點被視為主節(jié)點,而其他節(jié)點是從節(jié)點。
主節(jié)點接收全部寫操作。一個副本集只能有一個主節(jié)點通過{w: "majority"}指令確認寫操作。在某些情況下,另外的 mongod 進程可能在短時間會自認為是主節(jié)點(出現(xiàn)多節(jié)點競爭主節(jié)點的時候會存在兩個節(jié)點有這種情況,但是只有完成寫操作的才是真正當(dāng)前的主節(jié)點)。主節(jié)點會在操作日志中記錄數(shù)據(jù)集的所有操作。

從節(jié)點會復(fù)制主節(jié)點的操作日志并且在他們的數(shù)據(jù)集執(zhí)行同樣的操作,從而保持和主節(jié)點的數(shù)據(jù)集一致。如果主節(jié)點不可用,一個有資格的從節(jié)點將會舉行選舉將自己提升為新的主節(jié)點。

在某些情況下(例如你有一個主節(jié)點和一個從節(jié)點,但是因為成本原因不能再增加從節(jié)點),我們會選擇一個 mongod 實例作為仲裁者。仲裁者僅參與選舉而不保有數(shù)據(jù)。正常情況下。仲裁者角色不會改變。然而在主節(jié)點出現(xiàn)下線的時候會變?yōu)閺墓?jié)點參與投票(僅參與投票),從而影響投票結(jié)果,選舉出另一個從節(jié)點為主節(jié)點。通常情況下用于示例數(shù)量為偶數(shù)時,這種情況可能出現(xiàn)選票一致的情況,這個時候需要仲裁者打破平衡。

異步復(fù)制
從節(jié)點使用異步的方式從主節(jié)點復(fù)制操作日志并將操作應(yīng)用到自身的數(shù)據(jù)集。由于從節(jié)點保留了主節(jié)點相同的數(shù)據(jù)集,因此盡管一個或多個節(jié)點掛掉,副本集還可以繼續(xù)正常運行。
從節(jié)點從主節(jié)點復(fù)制操作時需要耗費一定的時間,因此會導(dǎo)致復(fù)制時延。復(fù)制時延是指從節(jié)點從主節(jié)點復(fù)制寫操作所需要的時間。復(fù)制時延如果小通常是可接受的,但是復(fù)制時延如果增加的話會導(dǎo)致很嚴重的緊急問題,包括增加主節(jié)點的緩存構(gòu)建壓力。
從 MongoDB 4.2版本開始,管理員可以限制主節(jié)點寫操作的頻率,以保持最大的提交時延。這個時延可以通過flowControlTargetLagSeconds 配置,這就是流控。流控默認是開啟的。開啟流控后,一旦時延接近到了flowControlTargetLagSeconds,主節(jié)點在獲取到寫操作鎖之前必須獲取到排隊票。流控通過限制每秒產(chǎn)生的排隊票數(shù)量這種機制來試圖保持時延在設(shè)定的目標(biāo)時延以下。
自動故障處理
當(dāng)一個主節(jié)點在設(shè)定的時間范圍(electionTimeoutMillis,默認10秒)內(nèi)沒有與其他成員進行通信,就會被認為掛了。此時一個有資格的節(jié)點會舉行投票并提名自己為新的主節(jié)點。集群會試圖選舉新的主節(jié)點來恢復(fù)正常操作。

在選舉過程中,副本集無法處理寫操作,但是如果查詢配置未運行在從節(jié)點上,那么讀操作還是可以正常進行。通常,選舉出新的主節(jié)點不應(yīng)該超過12秒——這是副本集設(shè)置的默認時間。這包括了將主節(jié)點標(biāo)記為不可用,以及發(fā)起和完成選擇這個過程。可以通過修改 settings.electionTimeoutMillis來修改這個時間。而諸如網(wǎng)絡(luò)延遲等因素也可能延長這一時間。由于這些影響,集群可能會在沒有主節(jié)點的情況下工作。
將electionTimeoutMillis調(diào)低能夠快速檢測主節(jié)點失敗,但是這樣也可能導(dǎo)致更頻繁的主節(jié)點選舉——尤其是對于偶爾的網(wǎng)絡(luò)延遲而言,即便是主節(jié)點沒有宕機也可能發(fā)生這種情況。應(yīng)用連接邏輯應(yīng)當(dāng)考慮容忍自動故障處理以及接下來的選舉過程。從3.6版本以后,MongoDB 的驅(qū)動會檢測主節(jié)點的失敗,并且會自動嘗試一次寫操作,從而增加了額外的內(nèi)置自動故障和選舉處理。其中 4.2版本及以上會自動開啟該特性,而3.6-4.0版本需要在連接字符屬性中包括retryWrites=true。
從4.4版本后,MongoDB 提供了鏡像讀的方式來預(yù)熱參與選舉從節(jié)點,以緩存最近訪問的數(shù)據(jù)。預(yù)熱從節(jié)點的緩存能夠加快選舉后的恢復(fù)速度。
創(chuàng)建副本集
創(chuàng)建副本集的格式如下:
mongod --replSet "rs0" --bind_ip localhost,<hostname(s)|ip address(es)> --port <port>
也可以使用配置文件啟動:
replication:
replSetName: "rs0"
net:
bindIp: localhost,<hostname(s)|ip address(es)>
如果不指定 bind_ip,默認為本機,不指定 port 則默認是27017。例如在本機創(chuàng)建三個節(jié)點:
# 節(jié)點1(第一個節(jié)點會設(shè)置為主節(jié)點)
mongod --replSet s0 --dbpath mongodb --port 27017 --oplogSize 100
# 節(jié)點2
mongod --replSet s0 --dbpath mongodb1 --port 27018 --oplogSize 100
# 節(jié)點3
mongod --replSet s0 --dbpath mongodb2 --port 27018 --oplogSize 100
客戶端連接
可以使用 mongo --port <port>連接具體的實例,連接后會顯示當(dāng)前連接的主節(jié)點還是從節(jié)點。
# 主節(jié)點客戶端
s0: PRIMARY>
# 從節(jié)點客戶端
s0:SECONDARY>
如果我們在從節(jié)點執(zhí)行寫操作會提示:NotWritablePrimary。注意,從節(jié)點默認是不可讀的,讀取時會提示錯誤:NotPrimaryNoSecondaryOk,如果臨時開啟讀,需要在從節(jié)點的客戶端執(zhí)行(舊版本是 rs.slaveOk()):
rs.secondaryOk();
# 或下面的命令
db.getMongo().setSecondaryOk();
監(jiān)測節(jié)點狀態(tài):
var config = {"_id":"s0", "members":[]};
config.members.push({"_id": 0, "host": "localhost:27017"});
config.members.push({"_id": 1, "host": "localhost:27018"});
config.members.push({"_id": 2, "host": "localhost:27019"});
rs.initiate(config);
# 查看節(jié)點狀態(tài)
rs.status();
總結(jié)
本篇介紹了 MongoDB 副本集的基本概念和特性,并示例了如何創(chuàng)建副本和客戶端連接多節(jié)點的方式。