前一部分的文章:
MongoDB 快速入門實(shí)戰(zhàn)教程基礎(chǔ)篇 一 :文檔的 CR操作
MongoDB 快速入門實(shí)戰(zhàn)教程基礎(chǔ)篇 一 :文檔的 UD操作
MongoDB 快速入門實(shí)戰(zhàn)教程基礎(chǔ)篇 二: 流式聚合操作
MongoDB 快速入門實(shí)戰(zhàn)教程基礎(chǔ)篇 三:執(zhí)行計(jì)劃與索引
MongoDB 快速入門實(shí)戰(zhàn)教程進(jìn)階篇 一:數(shù)據(jù)模型
進(jìn)階篇 二 提高數(shù)據(jù)服務(wù)可用性的復(fù)制集
MongoDB 中的復(fù)制指的是將數(shù)據(jù)同步在多個(gè)服務(wù)器。復(fù)制操作將會(huì)在多個(gè)服務(wù)器上建立數(shù)據(jù)副本,這些副本的集合稱為復(fù)制集,它們存儲(chǔ)的內(nèi)容與主服務(wù)器上的內(nèi)容一致。建立了復(fù)制集之后,就可以在主服務(wù)器出現(xiàn)故障或無(wú)法連接的情況下保證數(shù)據(jù)服務(wù)可用。
復(fù)制集成員
MongoDB 的復(fù)制集可以支持多個(gè)節(jié)點(diǎn),但要求至少有兩個(gè)節(jié)點(diǎn)。任何情況下都只有一個(gè)主節(jié)點(diǎn)(即主服務(wù)器),它負(fù)責(zé)處理客戶端發(fā)出的命令。除主節(jié)點(diǎn)之外的所有節(jié)點(diǎn)都稱為從節(jié)點(diǎn),它們會(huì)主動(dòng)同步主節(jié)點(diǎn)中的數(shù)據(jù)。在 MongoDB 中,主節(jié)點(diǎn)稱為 Primary,從節(jié)點(diǎn)稱為 Secondarie。
主節(jié)點(diǎn) Primary
主節(jié)點(diǎn)是復(fù)制集中唯一一個(gè)可以接收寫操作的成員。MongoDB 在主節(jié)點(diǎn)上執(zhí)行寫操作,并將操作記錄在主節(jié)點(diǎn)的 oplog 中,從節(jié)點(diǎn)會(huì)復(fù)制主節(jié)點(diǎn)的操作記錄,然后執(zhí)行相同操作以實(shí)現(xiàn)數(shù)據(jù)同步的目的。下圖描述了由三個(gè)成員組成的復(fù)制集的成員關(guān)系:這個(gè)復(fù)制集中有兩個(gè)從節(jié)點(diǎn)和一個(gè)主節(jié)點(diǎn)。主節(jié)點(diǎn)接收客戶端發(fā)起的寫操作,從節(jié)點(diǎn)通過(guò) oplog 實(shí)現(xiàn)數(shù)據(jù)同步。要注意的是,復(fù)制集中的所有從節(jié)點(diǎn)都可以接收讀操作,但默認(rèn)情況下讀取操作依然是交給主節(jié)點(diǎn)。如果想要更改讀取規(guī)則,可以查閱官方文檔 Read Preference。要注意的是,每個(gè)復(fù)制集最多可擁有 50 名成員。
從節(jié)點(diǎn) Secondary
從節(jié)點(diǎn)中存儲(chǔ)的是主節(jié)點(diǎn)的數(shù)據(jù)副本。從節(jié)點(diǎn)將主節(jié)點(diǎn)中的 oplog 操作應(yīng)用于自身,從而實(shí)現(xiàn)數(shù)據(jù)同步。要注意的是,這個(gè)“數(shù)據(jù)同步”的操作是異步進(jìn)行的。下圖描述了由三個(gè)成員組成的復(fù)制集的數(shù)據(jù)同步關(guān)系:子節(jié)點(diǎn)會(huì)同步主節(jié)點(diǎn)上的操作,以實(shí)現(xiàn)數(shù)據(jù)同步。另外,各節(jié)點(diǎn)之間通過(guò)心跳(Heartbeat)來(lái)判斷是否可用,假如某個(gè)節(jié)點(diǎn)在 10 秒內(nèi)沒(méi)有相應(yīng)其他節(jié)點(diǎn)發(fā)出的 Heartbeat,那么它將會(huì)被標(biāo)記為“掉線”,意為不可用或不可訪問(wèn)。
復(fù)制的基石—操作日志
上面提到,從節(jié)點(diǎn)的數(shù)據(jù)同步操作其實(shí)是執(zhí)行主節(jié)點(diǎn)中執(zhí)行過(guò)的操作。所有從節(jié)點(diǎn)都會(huì)拷貝主節(jié)點(diǎn)上的 local.oplog.rs 文件,即 oplog。oplog 記錄主節(jié)點(diǎn)中的改動(dòng)操作,但不記錄讀取操作。oplog 是一個(gè)特殊的上限集合,它支持基于順序插入和檢索文檔的高吞吐操作。上限集合的大小是固定的,在達(dá)到最大記錄數(shù)之后,如果再有新的記錄傳入,它會(huì)覆蓋掉最早的記錄。
從 MongoDB4.0 開(kāi)始,我們可以使用 oplogSizeMB 在創(chuàng)建時(shí)設(shè)置 oplog 的大小,或者使用 replSetResizeOplog 使其能夠突破上限集合的限制。假設(shè)我們想要將 oplog 的大小設(shè)置為 16000 兆字節(jié),對(duì)應(yīng)命令如下:
db.adminCommand({replSetResizeOplog: 1, size: 16000})
當(dāng)?shù)谝淮螁⒂脧?fù)制集,且未指定 oplog 大小時(shí),MongoDB 將會(huì)創(chuàng)建一個(gè)默認(rèn)大小的 oplog。oplog 的大小與操作系統(tǒng)和存儲(chǔ)引擎相關(guān),以下默認(rèn)大小規(guī)則適用于類 Unix 操作系統(tǒng)和 Windows 操作系統(tǒng):
| 存儲(chǔ)引擎 | 默認(rèn)的 oplog 大小 | 下限 | 上限 |
|---|---|---|---|
| 內(nèi)存存儲(chǔ)引擎 | 物理內(nèi)存的 5% | 50 MB | 50 GB |
| WiredTiger 存儲(chǔ)引擎 | 可用磁盤空間的 5% | 990 MB | 50 GB |
| MMAPv1 存儲(chǔ)引擎 | 可用磁盤空間的 5% | 990 MB | 50 GB |
對(duì)于 64 位的 macOS,oplog 的大小是 192M 的物理內(nèi)存或磁盤空間,上述三種存儲(chǔ)引擎的默認(rèn)值均相同。
主節(jié)點(diǎn)故障的應(yīng)對(duì)機(jī)制
在主節(jié)點(diǎn)出現(xiàn)故障時(shí),整個(gè)系統(tǒng)應(yīng)該有一個(gè)應(yīng)對(duì)機(jī)制。MongoDB 為復(fù)制集提供了主節(jié)點(diǎn)選舉和數(shù)據(jù)回滾來(lái)確保數(shù)據(jù)服務(wù)可用和避免數(shù)據(jù)丟失。
主節(jié)點(diǎn)選舉
當(dāng)主節(jié)點(diǎn)被標(biāo)記為“掉線”,那么就意味著復(fù)制集需要一個(gè)新的主節(jié)點(diǎn),否則將會(huì)導(dǎo)致服務(wù)不可用。這種情況下,復(fù)制集通過(guò)選舉的方式來(lái)確定哪個(gè)成員會(huì)成為主節(jié)點(diǎn)。除了被標(biāo)記為“掉線”之外,會(huì)觸發(fā)選舉的情況還有以下幾種:
- 復(fù)制集中添加了新的節(jié)點(diǎn);
- 初始化復(fù)制集;
- 使用
rs.stepDown()或者rs.reconfig()等方法維護(hù)復(fù)制集。
綠色背景的節(jié)點(diǎn)表示節(jié)點(diǎn)可用,灰色背景表示節(jié)點(diǎn)“掉線”。votes 代表票權(quán),對(duì)應(yīng)的數(shù)值代表初始票數(shù)。如果投票成員數(shù)量為偶數(shù),就有可能會(huì)造成多個(gè)節(jié)點(diǎn)的票數(shù)相同,甚至陷入無(wú)限選舉的泥潭。為了避免這種情況,我們就需要增加一個(gè)仲裁(Arbiter)節(jié)點(diǎn)。仲裁節(jié)點(diǎn)擁有投票權(quán),但它沒(méi)有存儲(chǔ)數(shù)據(jù)副本,也不能成為主節(jié)點(diǎn)。新增仲裁節(jié)點(diǎn)后,票數(shù)就會(huì)從偶數(shù)變成奇數(shù)。
默認(rèn)情況下,從標(biāo)記主節(jié)點(diǎn)“掉線”到選舉出新的主節(jié)點(diǎn)的時(shí)間不會(huì)超過(guò) 12 秒,但 MongoDB 也提供了可修改的配置來(lái)調(diào)整該時(shí)間。
數(shù)據(jù)回滾
當(dāng)主節(jié)點(diǎn)發(fā)生故障,并選舉出新的主節(jié)點(diǎn)時(shí),MongoDB 將會(huì)在之前的主節(jié)點(diǎn)上執(zhí)行回滾寫操作。當(dāng)“掉線”的主節(jié)點(diǎn)重新連接時(shí),它將會(huì)以從節(jié)點(diǎn)的身份加入到復(fù)制集中,并回滾寫操作,以便與其他成員的數(shù)據(jù)保持一致。MongoDB 4.0 版本對(duì)數(shù)據(jù)回滾進(jìn)行了一些調(diào)整:
- 回滾操作會(huì)在后臺(tái)索引構(gòu)建完成后進(jìn)行;
- 不限制回滾的數(shù)據(jù)量;
- 回滾時(shí)間默認(rèn)為 24 小時(shí),且可以配置。
4.0 版本之前,回滾的最大數(shù)據(jù)量為 300 兆字節(jié),超過(guò)上限的數(shù)據(jù)量需要進(jìn)行手動(dòng)干預(yù);回滾時(shí)間默認(rèn)為 30 分鐘,且不可配置。
復(fù)制集部署實(shí)踐
我們將介紹由三個(gè)成員組成的復(fù)制集部署過(guò)程,本次部署演示使用 MongoDB 官方的 Docker 鏡像,并且不啟用訪問(wèn)控制。如果想要了解有訪問(wèn)控制的復(fù)制集部署知識(shí),可查閱官方文檔 Deploy New Replica Set With Keyfile Access Control。
注意:本次部署演示將在同一臺(tái)計(jì)算機(jī)上啟動(dòng)多個(gè) Docker 鏡像,但實(shí)際工作中則是在多臺(tái)不同的云服務(wù)器上部署 MongoDB。
部署復(fù)制集
在開(kāi)始學(xué)習(xí)本節(jié)之前,請(qǐng)確保按照附章 Docker 官方文檔 的指引安裝 Docker。
首先,我們需要從 DockerHub 中拉取 MongoDB 官方提供的 mongo 服務(wù)鏡像。在版本選擇方面,我們選擇最新版,即 latest。對(duì)應(yīng)命令如下:
$ docker pull mongo:latest
將鏡像拉取到本地后,分別使用 run 命令啟動(dòng)三個(gè)容器。啟動(dòng)時(shí),我們需要為容器指定名稱,以便后期使用,同時(shí)還需要指定復(fù)制集的名稱,并設(shè)置容器的 bind_ip 。對(duì)應(yīng)命令如下:
$ docker run --name mongoFir -d mongo:latest --replSet "mongoRepas" --bind_ip_all
$ docker run --name mongoSec -d mongo:latest --replSet "mongoRepas" --bind_ip_all
$ docker run --name mongoThr -d mongo:latest --replSet "mongoRepas" --bind_ip_all
這里將容器分別命名為 mongoFir、mongoSec 和 mongoThr,復(fù)制集的名稱指定為 mongoRepas,并解除 mongo 對(duì)于bind_ip 的限制。由于在啟動(dòng)時(shí)未綁定 IP,所以我們需要使用 grep 命令找到每個(gè)容器對(duì)應(yīng)的 IP。對(duì)應(yīng)命令如下:
$ docker inspect mongoFir | grep IPAddress
命令執(zhí)行后,終端返回信息如下:
"SecondaryIPAddresses": null,
"IPAddress": "172.17.0.2",
"IPAddress": "172.17.0.2"
返回結(jié)果說(shuō)明容器 mongoFir 綁定的 IP 為 172.17.0.2,mongo 服務(wù)的默認(rèn)端口為 27017,所以名為 mongoFir 的容器中的 mongo 服務(wù)完整地址為 172.17.0.2:27017。接著依次查找容器 mongoSec 和 mongoThr 對(duì)應(yīng)的 mongo 服務(wù)地址。最終,三個(gè)容器對(duì)應(yīng)的 mongo 服務(wù)地址依次如下:
172.17.0.2:27017
172.17.0.3:27017
172.17.0.4:27017
容器啟動(dòng)成功后,就可以開(kāi)始初始化復(fù)制集的工作了。首先,連接任意一個(gè)容器的 MongoShell,例如容器 mongoFir。對(duì)應(yīng)命令如下:
$ docker exec -it mongoFir mongo
命令執(zhí)行后,就會(huì)連接上容器 mongoFir 的 MongoShell。然后在 MongoShell 中執(zhí)行復(fù)制集初始化的命令:
> rs.initiate({
_id: "mongoRepas",
members:[
{_id: 0, host: "172.17.0.2"},
{_id: 1, host: "172.17.0.3"},
{_id: 2, host: "172.17.0.4"}
]
})
在初始化復(fù)制集的時(shí)候指定了復(fù)制集的名稱,并制定了成員的 _id 和對(duì)應(yīng)的 IP 地址。命令執(zhí)行后,MongoShell 輸出如下文檔:
{
"ok" : 1,
"operationTime" : Timestamp(1564287051, 1),
"$clusterTime" : {
"clusterTime" : Timestamp(1564287051, 1),
"signature" : {
"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
"keyId" : NumberLong(0)
}
}
}
返回結(jié)果中的 ok: 1 代表復(fù)制集初始化成功。此時(shí),MongoShell 的命令行標(biāo)識(shí)符從 > 變?yōu)?mongoRepas:SECONDARY>,即復(fù)制集的 Shell。在復(fù)制集 Shell 中使用 rs.status() 命令查看當(dāng)前復(fù)制集的狀態(tài)信息,命令執(zhí)行后輸出如下內(nèi)容: