MongoDB 副本集
一、副本集概念
單節點的 MongoDB 在數據的安全和冗余方面是比較低的,在生產環境中,我們為 MongoDB 配置副本集,這樣可以提高數據的高可用性和安全性。
副本集 :是一組 Mongod 維護相同數據集的實例。副本集可以包含多個數據承載點和多個仲裁點。在承載數據的節點中,僅有一個節點被視為主節點,其他節點稱為次節點。
副本集的節點角色:
- Primary 主節點,用於承擔
- Secondary 次節點
- Arbiter 仲裁節點,也是屬於次節點
主節點接收所有的數據寫入操作,主節點記錄數據的所有更改,即oplog。
副本集架構圖
- 一個 主節點 兩個次節點(Secondary )

- 一個主節點兩個次節點(一個Secondary 節點,一個Arbiter 節點)

將一個額外的mongod實例添加到副本集作為 仲裁節點。仲裁節點不維護數據集。仲裁節點的目的是通過響應其他副本集成員的心跳和選舉請求來維護副本集中的選舉。因為它們不存儲數據集,所以仲裁節點可以是提供副本集仲裁功能的好方法,其資源成本比具有數據集的全功能副本集成員更低。如果您的副本集具有偶數個成員,請添加仲裁者以避免腦裂出現。
- 主節點故障后重新選舉主節點

在主節點未與配置中的其它成員通信超過 10s(默認為10s)的話,則符合條件的次節點將推選自己為主節點。
在選舉成功完成之前,副本集無法處理寫入操作。
electionTimeoutMillis 默認值為10000(10s) ,我們可以根據自己的項目情況來升高或者降低該值,我們在更改該值的時候需要考慮到網絡延遲等因素。
默認情況下,副本集在選取新的主節點的等待時間不超過12秒(主要用於將原有主節點標記為不可用,並選舉出新的主節點),
副本集數據同步
為了保持次節點與主節點的數據同步,MongoDB 使用兩種方式進行數據的同步:
-
初始同步, 用於同步主節點的所有數據
初始同步將所有的數據從副本集的一個成員復制到另外一個成員
-
增量同步,在初始同步后不斷復制新的數據
在初始同步后不斷復制數據,次節點從主節點中同步復制 Oplog,並在異步過程中應用這些操作
Oplog 詳解
注意
副本集在部署前需要確定成員數據,副本集最多能有50個節點,但是只能有7個節點擁有被選舉權,副本集需要具有奇數個投票成員,如果有偶數個的話,可以添加一個 仲裁者,來保證有奇數個成員,避免腦裂情況發生,
盡量使用 主機名 來尋找對應的節點,而不是使用 ip 地址,避免 ip 改變導致配置需要更改。
二、副本集部署
部署准備
部署需要更改 /etc/hosts 文件,將主機名和 ip 地址對應好,不應該使用ip。
使用統一的端口。
創建數據儲存的位置和配置文件的位置。
確定好副本集的名稱
搭建架構選擇
-
三節點,一個主節點,一個次節點,一個仲裁節點

副本集部署
基本環境:
系統 CentOS 7
MongoDB 版本 4.0.8
3個節點
fymongodb001 內網IP:172.18.186.161 公網ip: 47.112.129.2 # 主節點
fymongodb002 內網IP: 172.18.186.162 公網ip: 47.112.131.231 # 次節點
fymongodb003 內網IP: 172.18.186.163 公網ip: 47.112.98.64 # 仲裁節點
副本集名稱: fymongodb
三個節點都需要執行
# 下載mongodb
cat <<EOF >>/etc/hosts
172.18.186.161 fymongodb001
172.18.186.162 fymongodb002
172.18.186.163 fymongodb003
EOF
cd /tmp && wget https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-4.0.8.tgz
tar -zxf mongodb-linux-x86_64-4.0.8.tgz
mv mongodb-linux-x86_64-4.0.8 /opt/mongodb
mkdir /opt/mongodb/{data,logs}
useradd mongodb
# 我們這里使用的配置文件 是YAML 格式的
wget https://raw.githubusercontent.com/tobewithyou1996/LinuxGuide/master/MongoDB/mongodb_rep.yaml -P /opt/mongodb/
chown -R mongodb:mongodb /opt/mongodb/
cat <<EOF >>/usr/lib/systemd/system/mongodb.service
[Unit]
Description= mongodb service manager
[Service]
# Other directives omitted
# (file size)
LimitFSIZE=infinity
# (cpu time)
LimitCPU=infinity
# (virtual memory size)
LimitAS=infinity
# (locked-in-memory size)
LimitMEMLOCK=infinity
# (open files)
LimitNOFILE=64000
# (processes/threads)
LimitNPROC=64000
Type=forking
User=mongodb
Group=mongodb
PIDFile=/opt/mongodb/logs/mongod.pid
ExecStart= /opt/mongodb/bin/mongod -f /opt/mongodb/mongodb_rep.yaml
ExecStop= /opt/mongodb/bin/mongod --shutdown --dbpath /opt/mongodb/data
Restart=always
[Install]
WantedBy=multi-user.target
EOF
# 添加環境變量
echo "export PATH=$PATH:/opt/mongodb/bin" >>/etc/profile
source /etc/profile
# 啟動
systemctl start mongodb
主節點操作
# 主節點操作
[root@fymongodb001 tmp]# mongo
MongoDB shell version v4.0.8
connecting to: mongodb://127.0.0.1:27017/?gssapiServiceName=mongodb
Implicit session: session { "id" : UUID("77f60167-1d37-4796-bf2a-60cfdc4b0526") }
MongoDB server version: 4.0.8
> use admin # 切換到 admin 數據庫
switched to db admin
# 初始化副本集,副本集名稱為 fymongodb ,第一個成員為自己本身。
> rs.initiate({_id:'fymongodb',members: [{ _id: 0 , host: "fymongodb001:27017"}]})
{ "ok" : 1 }
fymongodb:SECONDARY> # 接着回車,直到顯示這個節點為Primary主節點
fymongodb:PRIMARY> # 接着添加 次節點 fymongodb002
# 添加次節點
fymongodb:PRIMARY> rs.add('fymongodb002:27017')
{
"ok" : 1,
"operationTime" : Timestamp(1555663440, 1),
"$clusterTime" : {
"clusterTime" : Timestamp(1555663440, 1),
"signature" : {
"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
"keyId" : NumberLong(0)
}
}
}
# 添加仲裁節點
fymongodb:PRIMARY> rs.addArb("fymongodb003:27017")
{
"ok" : 1,
"operationTime" : Timestamp(1555663631, 1),
"$clusterTime" : {
"clusterTime" : Timestamp(1555663631, 1),
"signature" : {
"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
"keyId" : NumberLong(0)
}
}
}
# 查看當前配置
fymongodb:PRIMARY> rs.conf()
{
"_id" : "fymongodb",
"version" : 3,
"protocolVersion" : NumberLong(1),
"writeConcernMajorityJournalDefault" : true,
"members" : [
{
"_id" : 0,
"host" : "fymongodb001:27017",
"arbiterOnly" : false,
"buildIndexes" : true,
"hidden" : false,
"priority" : 1,
"tags" : {
},
"slaveDelay" : NumberLong(0),
"votes" : 1
},
{
"_id" : 1,
"host" : "fymongodb002:27017",
"arbiterOnly" : false,
"buildIndexes" : true,
"hidden" : false,
"priority" : 1,
"tags" : {
},
"slaveDelay" : NumberLong(0),
"votes" : 1
},
{
"_id" : 2,
"host" : "fymongodb003:27017",
"arbiterOnly" : true,
"buildIndexes" : true,
"hidden" : false,
"priority" : 0,
"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("5cb98534cd9d16ff3f4fffdc")
}
}
# rs.status() 查看各個節點的身份
當我們完成上面的操作的時,我們主節點更改的數據已經是會自動同步到次節點的。
次節點操作
fymongodb002次節點(Secondary)設置允許讀寫操作。
[root@fymongodb002 ~]# mongo
MongoDB shell version v4.0.8
connecting to: mongodb://127.0.0.1:27017/?gssapiServiceName=mongodb
Implicit session: session { "id" : UUID("f353edd5-d79a-4947-9844-7f9bde98a949") }
MongoDB server version: 4.0.8
fymongodb:SECONDARY> fymongodb:SECONDARY> show dbs; # 我們發現無法讀寫
2019-04-19T17:04:40.852+0800 E QUERY [js] Error: listDatabases failed:{
"operationTime" : Timestamp(1555664675, 1),
"ok" : 0,
"errmsg" : "not master and slaveOk=false",
"code" : 13435,
"codeName" : "NotMasterNoSlaveOk",
"$clusterTime" : {
"clusterTime" : Timestamp(1555664675, 1),
"signature" : {
"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
"keyId" : NumberLong(0)
}
}
} :
fymongodb:SECONDARY> rs.slaveOk() # 允許 次節點 進行讀取
fymongodb:SECONDARY> show dbs; # 我們就可以查看到次節點的數據了。
admin 0.000GB
config 0.000GB
djx 0.000GB
local 0.000GB
fymongodb:SECONDARY> use djx;
switched to db djx
fymongodb:SECONDARY> show collections; # 從主節點同步過來的數據。
djx
lsp
數據同步測試
測試數據的同步,我們在主節點創建一個集合test 並添加一條數據 'age':38 ,我們可以看到次節點也同步了該數據。

故障模擬測試
測試一
模擬主節點 fymongodb001 宕機了,然后查看次節點 fymongodb002 是否會被選舉成為主節點。

我們可以看到 fymongodb002 選舉為主節點.
測試二
當 fymongodb002 選舉為主節點后,fymongodb001 恢復了,fymongodb001 會作為次節點加入。

測試三
測試在次節點(Secondary)進行數據刪除。是刪除不了的。
fymongodb:SECONDARY> db.test.drop()
2019-04-19T21:48:38.140+0800 E QUERY [js] Error: drop failed: {
"operationTime" : Timestamp(1555681711, 1),
"ok" : 0,
"errmsg" : "not master",
"code" : 10107,
"codeName" : "NotMaster",
"$clusterTime" : {
"clusterTime" : Timestamp(1555681711, 1),
"signature" : {
"hash" : BinData(0,"I+dFkOHcdqW+La7xvy8JFij+5CY="),
"keyId" : NumberLong("6681517989155569665")
}
}
} :
_getErrorWithCode@src/mongo/shell/utils.js:25:13
DBCollection.prototype.drop@src/mongo/shell/collection.js:707:1
@(shell):1:1
配置副本集登錄驗證
副本集配置用戶和密碼,登錄主節點,添加admin 用戶。
> use admin
switched to db admin
> db.createUser({ user: "admin", pwd: "9toc7tpji8", roles: [{ role: "root", db: "admin" }] })
Successfully added user: {
"user" : "admin",
"roles" : [
{
"role" : "root",
"db" : "admin"
}
]
}
在主節點生成 keyflie 並復制到其它兩個節點
# 生成 keyFile
openssl rand -base64 90 -out /opt/mongodb/keyfile
#並復制到其它兩個節點
scp /opt/mongodb/keyfile fymongodb002:/opt/mongodb/
scp /opt/mongodb/keyfile fymongodb003:/opt/mongodb/
更改三個節點的 keyFile 文件權限和所有者
chmod 600 /opt/mongodb/keyfile # 一定要更改成 600 權限,否正會報錯
chown mongodb:mongodb /opt/mongodb/keyfile # 更改文件所有者
更改三個節點的 mongodb_rep.yaml 配置文件,將 security 的參數 authorization 設置為 enabled,並配置
keyFile 的路徑。
security:
authorization: "enabled"
keyFile: '/opt/mongodb/keyfile'
clusterAuthMode: "keyFile"
然后依次重啟 fymongodb001、 fymongodb002、 fymongodb003 。
# fymongodb001
systemctl restart mongodb
# fymongodb002
systemctl restart mongodb
# fymongodb003
systemctl restart mongodb
登陸 fymongodb001,我們使用 db.auth() 進行登陸驗證。
fymongodb:PRIMARY> show dbs;
fymongodb:PRIMARY> use admin
switched to db admin
fymongodb:PRIMARY> db.auth('admin','9toc7tpji8')
1
fymongodb:PRIMARY> show dbs;
admin 0.000GB
config 0.000GB
local 0.000GB
三 、副本集維護
副本集成員如何以單節點啟動
副本集當做單節點啟動需要更改配置文件,主要的配置文件有以下幾點:
- 注釋副本集名稱設置,
replSet=fymongodb - 更改端口
- 如果是分片儲存的,那么還需要注釋分片配置,添加 skipShardingConfigurationChecks = true。
然后我們執行維護完后,關閉節點
use admin
db.shutdownServer()
然后還原原來的配置,然后作為副本集成員加入副本集。
副本集設置節點的優先級
如果我們想讓某個節點成為主節點,或者是當主節點 down 了后,你想指定某個節點 優先級更高地成為 次節點。
cfg = rs.conf()
cfg.members[0].priority = 0.5
cfg.members[1].priority = 0.5
cfg.members[2].priority = 1
rs.reconfig(cfg)
副本集使用備份數據進行啟動
# 使用備份數據進行啟動
mongod --dbpath /data/db
# 刪除 local 數據庫
use local
db.dropDatabase()
# 指定副本集名稱並以備份數據 啟動
mongod --dbpath /data/db --replSet <replName>
# 啟動副本集
rs.initiate( {
_id : <replName>,
members: [ { _id : 0, host : <host:port> } ]
})
副本集數據量比較大時如何添加次節點
當副本集的數據量比較大的時候,我們添加新的節點的時候,如果使用初始化同步的話,會給主節點造成比較大的壓力。我們有以下選擇:
-
暫停當前副本集中的一個次節點,然后將 次節點的數據(data)復制到要新添加的節點的數據目錄。然后再將兩個節點啟動。(建議先測試)
如果要復制數據文件,請確保您的副本包含
local數據庫的內容。 -
指定同步節點
rs.syncFrom(hostportstr),指定同步節點為次節點,默認的是同步節點 是主節點。但是該設置在重新啟動節點,或者同步指定的新節點的連接被關閉了,是會失效的。(建議先測試)
-
如果我們決定還是從主節點進行同步數據。
副本集延遲節點
延遲節點在此未記錄,詳細見官方文檔。
副本集常用命令
# 副本集初始化
rs.initiate( {
_id : "rs0",
members: [
{ _id: 0, host: "mongodb0.example.net:27017" },
{ _id: 1, host: "mongodb1.example.net:27017" },
{ _id: 2, host: "mongodb2.example.net:27017" }
]
})
# 副本集添加成員
rs.add('mongodb3.example.net:27017')
# 副本集添加仲裁節點
rs.addArb('mongodb4.example.net:27017')
# 移除節點
rs.remove('hostportstr')
# 查看當前的配置
rs.conf()
# 查看各個節點狀態和身份
rs.status()
# 設定某個節點多少秒不可成為主節點
rs.freeze(secs)
# 設置次節點從指定節點同步數據
rs.syncFrom(hostportstr)
# 降低主節點為次節點,只能在主節點上運行
rs.stepDown([stepdownSecs, catchUpSecs])
# 查看幫助
rs.help()
# 次節點執行,表示允許次節點讀取數據
rs.slaveOk()
# 判斷當前節點是否是主節點
rs.isMaster()
# 查看 Oplog 信息
rs.printReplicationInfo()
# 查看副本集的次節點與主節點延遲
db.printSlaveReplicationInfo()
# 移除原有副本集命令
use local
db.system.replset.remove({})
# 關閉 mongodb進程服務
use admin
db.shutdownServer()
四、注意事項
建議將副本集至少部署在3個可用區。
配置副本集成員,使用的是主機名而不是 ip,因為 ip 可能變動。
副本集包含奇數個投票成員。
錯誤問題
報錯內容:
Failed global initialization: BadValue: replication.replSetName is not allowed when storage.indexBuildRetry is specified
storage 參數 indexBuildRetry 不能與 副本集共存,當開啟 副本集的時候,就需要將indexBuildRetry 參數注釋。官網鏈接
Changed in version 4.0: The setting
storage.indexBuildRetrycannot be used in conjunction withreplication.replSetName.
