淺嘗輒止MongoDB:複製
目錄
一、複製基礎
副本集是一種建立多個MongoDB例項的方式,這些例項將擁有相同的資料(冗餘)和其它相關設定。主從複製、主主複製、複製對等方法都被副本集的概念所取代。在MongoDB中,副本集由一個主節點以及多個輔助或仲裁節點組成,一個副本集最少應該有3個成員。在MongoDB 3.0中,副本集最多可以有50個被動成員和7個主動成員。通常建議副本集有奇數個成員,這條規則主要是為了避免“腦裂”(split brain)問題,也就是說當網路出現問題時,有兩臺伺服器成為主伺服器的情況。
1. 主動成員與被動成員
副本集提供了主動成員與被動成員。當前的主伺服器不可用時,被動伺服器不會參與新的主伺服器的選舉,但它們可投票否決某個成員的主伺服器資格。
2. master
在副本集術語中,主伺服器是在特定時間內副本集的資料來源。它是副本集中唯一可以寫入的節點。所有其它節點都將從主伺服器複製出它們的資料。主伺服器由所有主動成員中的大多數投票產生,這被稱為法定人數(quorum)。
主伺服器的概念是(並且應該是)短暫的。理想情況下不應該固定地認為哪個節點是主伺服器。
3. secondary
輔助伺服器成員是具有資料的非主伺服器成員,理論上它可以成為主伺服器。它是一個只讀節點,同時它將以儘可能接近於實時的方式從主伺服器複製資料。預設情況下,如果連線到輔助伺服器但不使用任何讀偏好,就不能執行讀操作。這是因為讀取非主伺服器時,如果複製過程中存在延遲,讀取的可能是舊資料。可以使用rs.slaveOk()將當前連線設定為可從輔助伺服器讀取資料。或者如果使用的是某種語言的MongoDB驅動,那麼也可以設定讀偏好。
MongoDB的讀偏好是它選擇從哪個副本整合員讀取資料的方式。通過為驅動指定一個讀偏好,它將知道應該在副本集的哪個成員上執行查詢。如果設定了讀偏好,就意味著可能從輔助伺服器讀取資料。必須注意,得到的資料可能不是最新的。
4. arbiter
仲裁伺服器是不含資料的節點,如果副本集中的主動成員是偶數,它就用於提供額外的主動成員,決定哪個節點成為主伺服器。仲裁伺服器用於幫助避免“腦裂”。
5. oplog
oplog(操作日誌)是一個固定大小的集合,儲存主伺服器例項對資料庫做出修改的記錄,目的是在輔助伺服器重做這些操作,保證資料庫處於一致狀態。副本集的每個成員維護自己的oplog,並且輔助伺服器將查詢主伺服器(或者用過複製鏈進行其它資料更新的輔助伺服器)的oplog,從而獲得新條目,並應用到自己的資料庫副本中。
oplog將為每個條目建立一個時間戳。通過這種方式,輔助伺服器可以記錄從上一次讀取開始過去了多久,以及有多少oplog需要讀取。可將oplog看成主伺服器例項最近活動的視窗:如果視窗太小,那麼記錄中的某些操作可能在被應用到輔助伺服器之前丟失。如果當前例項的oplog尚未建立,那麼使用--oplogSize啟動選項可以設定oplog的大小(以MB為單位)。在64位Linux系統中,oplogSize預設設定為可以磁碟空間的5%,最小為1GB,最大為50GB。
計算oplog大小時,考慮主伺服器上所有資料庫的更新頻率非常關鍵。通過執行db.printReplicationInfo()命令可以得到一些oplog大小方面的參考,該命令將執行在主伺服器上:
testset:PRIMARY> db.printReplicationInfo()
configured oplog size: 1781.708984375MB
log length start to end: 83964secs (23.32hrs)
oplog first event time: Tue Oct 16 2018 15:04:31 GMT+0800 (CST)
oplog last event time: Wed Oct 17 2018 14:23:55 GMT+0800 (CST)
now: Wed Oct 17 2018 14:23:56 GMT+0800 (CST)
testset:PRIMARY>
該命令將顯示出oplog當前的大小,以及以當前的更新速率oplog被填滿所需的時間。從該資訊中可以估計出是需要增加還是減小oplog的大小。
二、配置副本集
1. 建立副本集
環境:
主動成員1:hdp4:27017
主動成員2:hdp3:27017
被動成員1:hdp2:27017
仲裁:hdp1:27017
(1)啟動副本整合員
因為啟用了auth,所以先要建立autokey檔案,否則初始化副本集時會報錯:
"errmsg" : "Quorum check failed because not enough voting nodes responded; required 2 but only the following 1 voting nodes responded: hdp4:27017; the following nodes did not respond affirmatively: hdp3:27017 failed with Authentication failed."
以下命令在hdp4上執行:
# 建立autokey檔案
openssl rand -base64 756 > autokey
# 修改讀寫模式
chmod 400 autokey
# 複製到副本整合員節點
scp autokey 172.16.1.126:/home/mongodb/mongodb-4.0.2/
scp autokey 172.16.1.125:/home/mongodb/mongodb-4.0.2/
scp autokey 172.16.1.124:/home/mongodb/mongodb-4.0.2/
準備配置檔案如下:
logpath = /home/mongodb/mongodb-4.0.2/data/mongodb.log
pidfilepath = /home/mongodb/mongodb-4.0.2/data/mongodb.pid
dbpath = /home/mongodb/mongodb-4.0.2/data/
auth = true
bind_ip_all = true
replSet = testset
keyFile = /home/mongodb/mongodb-4.0.2/autokey
在4臺伺服器上分別執行以下命令啟動節點:
mongod -f /home/mongodb/mongodb-4.0.2/mongodb.conf &
(2)初始化副本集
在hdp4例項中執行:
> rs.initiate();
{
"info2" : "no configuration specified. Using a default configuration for the set",
"me" : "hdp4:27017",
"ok" : 1,
"operationTime" : Timestamp(1539761031, 1),
"$clusterTime" : {
"clusterTime" : Timestamp(1539761031, 1),
"signature" : {
"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
"keyId" : NumberLong(0)
}
}
}
testset:SECONDARY>
2. 向副本集新增伺服器
在hdp4例項中執行:
testset:SECONDARY> rs.add("hdp3:27017");
{
"ok" : 1,
"operationTime" : Timestamp(1539761227, 1),
"$clusterTime" : {
"clusterTime" : Timestamp(1539761227, 1),
"signature" : {
"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
"keyId" : NumberLong(0)
}
}
}
testset:PRIMARY> rs.add("hdp2:27017");
{
"ok" : 1,
"operationTime" : Timestamp(1539761249, 1),
"$clusterTime" : {
"clusterTime" : Timestamp(1539761249, 1),
"signature" : {
"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
"keyId" : NumberLong(0)
}
}
}
testset:PRIMARY>
3. 設定輔助伺服器
在hdp4例項中執行以下命令,將hdp2設定為隱藏,並且優先順序為0:
testset:PRIMARY> conf = rs.conf();
{
"_id" : "testset",
"version" : 3,
"protocolVersion" : NumberLong(1),
"writeConcernMajorityJournalDefault" : true,
"members" : [
{
"_id" : 0,
"host" : "hdp4:27017",
"arbiterOnly" : false,
"buildIndexes" : true,
"hidden" : false,
"priority" : 1,
"tags" : {
},
"slaveDelay" : NumberLong(0),
"votes" : 1
},
{
"_id" : 1,
"host" : "hdp3:27017",
"arbiterOnly" : false,
"buildIndexes" : true,
"hidden" : false,
"priority" : 1,
"tags" : {
},
"slaveDelay" : NumberLong(0),
"votes" : 1
},
{
"_id" : 2,
"host" : "hdp2:27017",
"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("5bc6e3873c3e34249ed82765")
}
}
testset:PRIMARY> conf.members[2].hidden = true
true
testset:PRIMARY> conf.members[2].priority = 0
0
testset:PRIMARY> rs.reconfig(conf);
{
"ok" : 1,
"operationTime" : Timestamp(1539761492, 1),
"$clusterTime" : {
"clusterTime" : Timestamp(1539761492, 1),
"signature" : {
"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
"keyId" : NumberLong(0)
}
}
}
testset:PRIMARY>
這樣hdp2不會被選舉為主伺服器。
4. 向副本集新增仲裁伺服器
在hdp4例項中執行:
testset:PRIMARY> rs.addArb("hdp1:27017");
{
"ok" : 1,
"operationTime" : Timestamp(1539761665, 1),
"$clusterTime" : {
"clusterTime" : Timestamp(1539761665, 1),
"signature" : {
"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
"keyId" : NumberLong(0)
}
}
}
testset:PRIMARY>
5. 設定被動伺服器
現在副本集中有4個節點,需要使主動成員數為奇數,在hdp4例項中執行:
testset:PRIMARY> conf = rs.conf()
{
"_id" : "testset",
"version" : 5,
"protocolVersion" : NumberLong(1),
"writeConcernMajorityJournalDefault" : true,
"members" : [
{
"_id" : 0,
"host" : "hdp4:27017",
"arbiterOnly" : false,
"buildIndexes" : true,
"hidden" : false,
"priority" : 1,
"tags" : {
},
"slaveDelay" : NumberLong(0),
"votes" : 1
},
{
"_id" : 1,
"host" : "hdp3:27017",
"arbiterOnly" : false,
"buildIndexes" : true,
"hidden" : false,
"priority" : 1,
"tags" : {
},
"slaveDelay" : NumberLong(0),
"votes" : 1
},
{
"_id" : 2,
"host" : "hdp2:27017",
"arbiterOnly" : false,
"buildIndexes" : true,
"hidden" : true,
"priority" : 0,
"tags" : {
},
"slaveDelay" : NumberLong(0),
"votes" : 1
},
{
"_id" : 3,
"host" : "hdp1: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("5bc6e3873c3e34249ed82765")
}
}
testset:PRIMARY> conf.members[2].votes = 0
0
testset:PRIMARY> rs.reconfig(conf)
{
"ok" : 1,
"operationTime" : Timestamp(1539762432, 1),
"$clusterTime" : {
"clusterTime" : Timestamp(1539762432, 1),
"signature" : {
"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
"keyId" : NumberLong(0)
}
}
}
testset:PRIMARY>
hdp2的votes值設定為0,現在hdp2完全變成被動伺服器,它被客戶端看成副本集的一部分,並且不會參與選舉,也永遠不會變成主伺服器。
6. 在伺服器上檢查和執行操作
(1)副本集鏈
通常,副本整合員會嘗試從副本集的主伺服器同步資料。但這不是副本集的輔助伺服器同步資料的唯一伺服器:它們也可以從其它輔助伺服器同步資料。通過這種方式,所有輔助伺服器將組成一個“同步鏈”,每個節點都可以從副本集的其它輔助伺服器同步最新資料。副本集鏈在MongoDB中是預設行為,設定chainingAllowed:false,可以改變這種行為,如下:
cfg=rs.conf()
cfg.settings.chainingAllowed=false
rs.reconfig(cfg)
(2)管理副本集
操作和檢測副本集的命令:
命令 |
描述 |
rs.help() |
返回命令列表。 |
rs.status() |
返回副本集當前的狀態資訊。該命令列出了每個成員伺服器及其狀態資訊,包括最後聯絡時間。該呼叫可被用於提供整個叢集的簡單健康檢查。 |
rs.initiate() |
使用預設引數初始化副本集。 |
rs.initiate(replSetcfg) |
使用配置描述初始化副本集。 |
rs.add("host:port") |
使用含有主機名和特定埠(可選)的簡單字串向副本集中新增成員伺服器。 |
rs.add(membercfg) |
使用配置描述向副本集中新增成員伺服器。如果希望指定特定的屬性(如設定新成員伺服器的優先順序),那麼必須使用這種方法。 |
rs.addArb("host:port") |
新增新的成員伺服器作為仲裁者。該成員不需要使用—replSet選項:任何執行在可達機器上的mongod例項都可以執行該任務。注意該伺服器必須對副本集中的所有成員可達。 |
rs.stepDown() |
在副本集的主伺服器成員中使用該命令時,將使主伺服器放棄它的角色,並且在叢集中重新選舉新的主伺服器。注意只有主動輔助伺服器可用作主伺服器的候選,並且在60秒(預設)之內如果沒有出現其它可用的成員,那麼原有的主伺服器將重新成為主伺服器。 |
rs.syncFrom("host:port") |
使輔助伺服器從指定的成員同步資料,可用於組成同步鏈。 |
rs.freeze(secs) |
凍結指定的成員,並使它在指定秒數內無法成為主伺服器。 |
rs.remove("host:port") |
從副本集中刪除指定成員。 |
rs.slaveOk() |
通過該選項,可以允許從輔助伺服器讀取資料。 |
rs.conf() |
重新顯示當前副本集的配置結構。改配置結構可以被修改,然後用作rs.reconfig()的引數,從而修改結構的配置。 |
db.isMaster() |
該函式不只可作用於副本集:它是一個通用的複製支援函式。通過它,應用或驅動可以判斷出被連線的特定例項在複製拓撲結構中是否是主伺服器。 |
rs.status欄位的值:
值 |
描述 |
_id |
副本集中該成員的ID |
Name |
該成員的主機名 |
Health |
replSet的健康值 |
State |
狀態數值 |
StateStr |
副本集狀態的字串表示 |
Uptime |
該成員的執行時間 |
optime |
應用在該成員上最後一個操作的時間,格式為一個時間戳和一個整數值 |
optimeDate |
最後一個被應用操作的日期 |
lastHeartbeat |
最後一次傳送心跳的日期 |
lastHeartbeatRecv |
最後一次收到心跳的日期 |
configVersion |
這個成員使用的副本集配置的版本 |
syncingTo |
使用哪個副本整合員同步資料 |