MongoDB 副本集(Replica Set)是有自动故障恢复功能的主從集群,有一個 Primary 節點和一個或者多個 Secondary 節點組成。副本集沒有固定的主節點,當主節點發生故障時,整個集群會選舉一個主節點為系統提供服務以保證系統的高可用。
副本集擁有三種節點:主節點、從節點、仲裁節點
1.主節點 負責處理客戶端請求,讀、寫數據。
2.從節點 從主節點中複製數據,並可以接收讀請求。在主節點故障時可通過投票選舉出新的主節點。
3.仲裁節點 不持有數據副本,只參與投票過程,決定哪個節點成為主節點。
本文使用 mongo5.0.8 作為範例
1.docker-compose.yaml#
這裡先直接給出單個 mongo 的 docker-compose.yaml
version: '3.0'
services:
mongo1:
hostname: mongo1
image: mongo:5.0.8
restart: unless-stopped
container_name: mongodb1
command: --replSet app --bind_ip_all --keyFile /data/mongodb/keyFile
environment:
TZ: 'Asia/Shanghai'
MONGO_INITDB_ROOT_USERNAME: 'admin'
MONGO_INITDB_ROOT_PASSWORD: 'password'
ports:
- 27020:27017
volumes:
- ./data1:/data/db
- ./keyFile:/data/mongodb/keyFile
networks:
- bridge_network
mongo2:
hostname: mongo2
image: mongo:5.0.8
restart: unless-stopped
container_name: mongodb2
command: --replSet app --bind_ip_all --keyFile /data/mongodb/keyFile
environment:
TZ: 'Asia/Shanghai'
MONGO_INITDB_ROOT_USERNAME: 'admin'
MONGO_INITDB_ROOT_PASSWORD: 'password'
ports:
- 27018:27017
volumes:
- ./data2:/data/db
- ./keyFile:/data/mongodb/keyFile
networks:
- bridge_network
mongo3:
hostname: mongo3
image: mongo:5.0.8
restart: unless-stopped
container_name: mongodb3
command: --replSet app --bind_ip_all --keyFile /data/mongodb/keyFile
environment:
TZ: 'Asia/Shanghai'
MONGO_INITDB_ROOT_USERNAME: 'admin'
MONGO_INITDB_ROOT_PASSWORD: 'password'
ports:
- 27019:27017
volumes:
- ./data3:/data/db
- ./keyFile:/data/mongodb/keyFile
networks:
- bridge_network
networks:
bridge_network:
driver: bridge
這裡可以選擇在一台機器上啟動 3 個容器,也可以在不同的機器上啟動。本例用上述文件分別在 IP 為 10.0.1.11 (主)、10.0.1.31 (從)、10.0.1.32 (從) 三台機器上啟動。
在 10.0.1.11 執行docker compose up -d mongo1
在 10.0.1.31 執行docker compose up -d mongo2
在 10.0.1.32 執行docker compose up -d mongo3
這裡 docker compose up -d 後的 mongo1、mongo2、mongo3 是指只啟動 yaml 文件中的一個服務。如果將 yaml 文件拆分成不同的文件分別在對應的機器裡啟動就不需要在命令後面加服務名稱了。
需要注意:
組成副本的每一台機器的 keyFile 文件必須一樣!!!mongodb 實例只有擁有正確的 keyfile 才可以加入副本集。keyFile 文件的具體操作方法可以參考【mongo 事務】使用 docker-compose 啟動 mongo,‘單副本模式‘實現支持事務
2. 容器啟動後#
在容器啟動後,我們使用 docker exec 命令進入作為主節點的容器,進入 mongo shell
mongo -u admin --authenticationDatabase admin
初始化 mongo 副本集
rs.initiate({
_id: "app",
members: [
{ _id: 0, host: "10.0.1.11:27020" },
{ _id: 1, host: "10.0.1.31:27018" },
{ _id: 2, host: "10.0.1.32:27019" }
]
})
注意,當初我在初始化時,誤以為可以直接使用 rs.initiate () 無參的初始化方法,然後再依次使用 rs.add (‘ip’) 將剩餘的節點加入副本集。但是這是錯誤的,因為在初始化時,如果使用無參的初始化方法,mongo 會直接獲取宿主機 (這時對於 mongo 來說宿主機是 docker 容器) 的 hostname + 端口組成 mongo 的 host 並寫入配置,但是由於 mongo 的宿主機其實是容器,所以會獲取到容器的 hostname 以及 mongo 在容器裡的端口。
顯然這個當多個不同機器的 mongo 副本相互通信時,這個 host 是無效的。
所以在初始化時最好直接指定好具體的機器。
當初始化完成以後可以使用rs.status()查看副本集狀態
截取部分內容
"members" : [
{
"_id" : 0,
"name" : "10.0.1.11:27020",
"health" : 1,
"state" : 1,
"stateStr" : "PRIMARY",
"uptime" : 2299,
"optime" : {
"ts" : Timestamp(1717560117, 1),
"t" : NumberLong(1)
},
"optimeDate" : ISODate("2024-06-05T04:01:57Z"),
"lastAppliedWallTime" : ISODate("2024-06-05T04:01:57.808Z"),
"lastDurableWallTime" : ISODate("2024-06-05T04:01:57.808Z"),
"syncSourceHost" : "",
"syncSourceId" : -1,
"infoMessage" : "",
"electionTime" : Timestamp(1717559887, 1),
"electionDate" : ISODate("2024-06-05T03:58:07Z"),
"configVersion" : 1,
"configTerm" : 1,
"self" : true,
"lastHeartbeatMessage" : ""
},
{
"_id" : 1,
"name" : "10.0.1.31:27018",
"health" : 1,
"state" : 2,
"stateStr" : "SECONDARY",
"uptime" : 249,
"optime" : {
"ts" : Timestamp(1717560117, 1),
"t" : NumberLong(1)
},
"optimeDurable" : {
"ts" : Timestamp(1717560117, 1),
"t" : NumberLong(1)
},
"optimeDate" : ISODate("2024-06-05T04:01:57Z"),
"optimeDurableDate" : ISODate("2024-06-05T04:01:57Z"),
"lastAppliedWallTime" : ISODate("2024-06-05T04:01:57.808Z"),
"lastDurableWallTime" : ISODate("2024-06-05T04:01:57.808Z"),
"lastHeartbeat" : ISODate("2024-06-05T04:02:05.988Z"),
"lastHeartbeatRecv" : ISODate("2024-06-05T04:02:05.581Z"),
"pingMs" : NumberLong(1),
"lastHeartbeatMessage" : "",
"syncSourceHost" : "10.0.1.11:27020",
"syncSourceId" : 0,
"infoMessage" : "",
"configVersion" : 1,
"configTerm" : 1
},
{
"_id" : 2,
"name" : "10.0.1.32:27019",
"health" : 1,
"state" : 2,
"stateStr" : "SECONDARY",
"uptime" : 249,
"optime" : {
"ts" : Timestamp(1717560117, 1),
"t" : NumberLong(1)
},
"optimeDurable" : {
"ts" : Timestamp(1717560117, 1),
"t" : NumberLong(1)
},
"optimeDate" : ISODate("2024-06-05T04:01:57Z"),
"optimeDurableDate" : ISODate("2024-06-05T04:01:57Z"),
"lastAppliedWallTime" : ISODate("2024-06-05T04:01:57.808Z"),
"lastDurableWallTime" : ISODate("2024-06-05T04:01:57.808Z"),
"lastHeartbeat" : ISODate("2024-06-05T04:02:05.988Z"),
"lastHeartbeatRecv" : ISODate("2024-06-05T04:02:05.629Z"),
"pingMs" : NumberLong(1),
"lastHeartbeatMessage" : "",
"syncSourceHost" : "10.0.1.11:27020",
"syncSourceId" : 0,
"infoMessage" : "",
"configVersion" : 1,
"configTerm" : 1
}
],
這裡可以看到執行初始化的機器為主節點,剩下的為從節點
關於 stateStr 字段的各個含義:
STARTUP:成員正在啟動。
PRIMARY:成員是主節點。
SECONDARY:成員是次節點。
RECOVERING:成員正在恢復。
ARBITER:成員是仲裁節點。
DOWN:成員無法與大多數節點通信。
UNKNOWN:成員的狀態未知。
ROLLBACK:成員正在進行回滾操作。
REMOVED:成員已經被副本集移除。
最後如果有後續需要加入的新節點可以使用 rs.add (‘ip’) 加入。如果需要加入仲裁節點 (不推薦)
可以使用 rs.add (‘ip’,true) 加入
關於節點的數量
mongodb 官方說當節點超過 2 個時,推薦副本集成員為奇數個成員,而不使用仲裁節點。所以在實際運用中盡可能的保證奇數個節點而不使用仲裁節點。如果迫不得已正常主從節點總數為偶數,這時仲裁節點就可以加入副本集(仲裁節點不參與存儲等業務,只參與投票,所以仲裁節點對於性能的要求很小,加一個仲裁節點的難度不會很大),這樣擁有投票權的節點總數又為奇數了。
具體為什麼需要奇數個節點,這需要投票選舉的算法以及實際部署的情況來解釋(腦裂問題)。