Mongodb副本集本地測試案例

副本集

副本集是一組服務器,其中有一個主服務器(primary),用於處理客戶端請求;還有多個備份服務器(secondary),用於保存主服務器的數據副本。如果主服務器崩潰了,備份服務器會自動將其中一個成員升級爲新的主服務器。使用複製功能時,如果有一臺服務器宕機了,仍然可以從副本集的其他服務器上訪問數據。如果服務器上的數據損壞或者不可訪問,可以從副本集的某個成員中創建一份新的數據副本。

在本地機器上搭建測試副本集

1.進入mongodb安裝目錄,使用–nodb選項啓動一個mongo shell,這樣可以啓動shell但是不連接到任何mongod

cd C:\Program Files\MongoDB\Server\3.4\bin

mongo –nodb


2.創建一個三個成員的副本集:

replicaSet = new ReplSetTest({“nodes” : 3})

創建過程中會輸出一堆信息,其中包好了端口號,副本集名稱等,這裏截取了部分信息:

 "name" : "testReplSet",//副本集名稱
 "useHostName" : true,
 "host" : "��վ-�޺���",
 "oplogSize" : 40,
 "useSeedList" : false,
 "keyFile" : undefined,
 "protocolVersion" : undefined,
 "nodeOptions" : {
 "n0" : undefined,
 "n1" : undefined,
 "n2" : undefined
 },
 "ports" : [//端口號
 20000,
 20001,
 20002
 ],
 "nodes" : [ ]

3.啓動副本集

replicaSet.startSet()

啓動副本集是可能會報,如下錯誤:

提示該錯誤的原因是因爲副本集的相關數據是存在C:/data/db/目錄下的,所以應該在運行這個指令之前確保這個目錄存在,而且確保當前用戶對這個目錄擁有寫權限,如果遇到這個問題只需要新建好這個目錄重新運行該指令就好。

4.副本集正常啓動之後,需要對副本集進行配置,

//設置配置參數
config = {
  "_id" : "testReplSet",//副本集名稱
  "members" : [//調用new ReplSetTest({"nodes" : 3})分配的端口號
   {"_id" : 0, "host" : "localhost:20000"},  
   {"_id" : 1, "host" : "localhost:20001"}, 
   {"_id" : 2, "host" : "localhost:20002"}   
 ]}
//開始配置
replicaSet.initiate(config)

至此測試副本集已經配置完成了,接下來開始副本集的測試。

副本集測試

1.使用isMaster()指令查看當前節點是否爲主節點,具體步驟和輸出信息如下:

C:\Program Files\MongoDB\Server\3.4\bin>mongo --nodb
MongoDB shell version v3.4.5
> conn1 = new Mongo("localhost:20000")//連接到某一個節點上
connection to localhost:20000
> db1 = conn1.getDB("test")//獲取數據庫,這裏的db名可以是已存在的,也可以是不存在的,這個無所謂
test
> db1.isMaster()
{
"hosts" : [//副本集成員
"localhost:20000",
"localhost:20001",
"localhost:20002"
],
"setName" : "testReplSet",//副本集名稱
"setVersion" : 2,
"ismaster" : true,//是否爲主節點
"secondary" : false,//是否爲備份節點
"primary" : "localhost:20000",//主節點信息
"me" : "localhost:20000",//當前節點信息
"electionId" : ObjectId("7fffffff0000000000000001"),
"lastWrite" : {
"opTime" : {
"ts" : Timestamp(1503476784, 1),
"t" : NumberLong(1)
},
"lastWriteDate" : ISODate("2017-08-23T08:26:24Z")
},
"maxBsonObjectSize" : 16777216,
"maxMessageSizeBytes" : 48000000,
"maxWriteBatchSize" : 1000,
"localTime" : ISODate("2017-08-23T08:52:53.828Z"),
"maxWireVersion" : 5,
"minWireVersion" : 0,
"readOnly" : false,
"ok" : 1
}

2.備份節點可能會落後於主節點,可能沒有最新寫入的數據,所以備份節點在默認情況下會拒絕讀取請求,以防止應用程序意外拿到過期的數據。
下面對這一規則進行驗證,具體步驟和輸出如下:

> conn2 = new Mongo("localhost:20001")
connection to localhost:20001
> db2 = conn2.getDB("test")
test
> db2.coll.find()
Error: error: {
"ok" : 0,
"errmsg" : "not master and slaveOk=false",
"code" : 13435,
"codeName" : "NotMasterNoSlaveOk"
}

由以上的錯誤信息”not master and slaveOk=false”可以得知是因爲該節點不是主節點的原因,但是slaveOK這個是什麼呢?繼續往下看。

3.使用slaveOK()設置備份節點可以訪問數據

//這裏沿用上面的數據庫連接
> conn2.setSlaveOk()//設置該連接可以訪問數據
> db2.coll.find()
> db2.coll.count()
0

調用setSlaveOk()方法時候在該備份節點上就可以正常的訪問數據了。

4.測試自動故障轉移(auto-matic failover)

//首先連上主節點,然後調用.adminCommand("shutdown":1)方法斷開該節點,然後連上原爲備份節點的20001端口調用isMaster()查看相關信息
C:\Program Files\MongoDB\Server\3.4\bin>mongo --nodb
MongoDB shell version v3.4.5
> conn1 = new Mongo("localhost:20000")
connection to localhost:20000
> db1 = conn1.getDB("test")
test
> db1.isMaster()
{
"hosts" : [
"localhost:20000",
"localhost:20001",
"localhost:20002"
],
"setName" : "testReplSet",
"setVersion" : 2,
"ismaster" : true,
"secondary" : false,
"primary" : "localhost:20000",//原有的primary是20000端口
"me" : "localhost:20000",
"electionId" : ObjectId("7fffffff0000000000000004"),
"lastWrite" : {
"opTime" : {
"ts" : Timestamp(1503554403, 1),
"t" : NumberLong(4)
},
"lastWriteDate" : ISODate("2017-08-24T06:00:03Z")
},
"maxBsonObjectSize" : 16777216,
"maxMessageSizeBytes" : 48000000,
"maxWriteBatchSize" : 1000,
"localTime" : ISODate("2017-08-24T06:00:04.244Z"),
"maxWireVersion" : 5,
"minWireVersion" : 0,
"readOnly" : false,
"ok" : 1
}
> db1.adminCommand({"shutdown":1})//端口20000端口
2017-08-24T14:04:09.529+0800 E QUERY[thread1] Error: error doing query: fail
ed: network error while attempting to run command 'shutdown' on host 'localhost:
20000'  :
DB.prototype.runCommand@src/mongo/shell/db.js:132:1
DB.prototype.adminCommand@src/mongo/shell/db.js:150:16
@(shell):1:1
> conn2 = new Mongo("localhost:20001")//連接原爲備份節點的20001端口
connection to localhost:20001
> db2 = conn2.getDB("test")
test
> db2.isMaster()//查看副本集節點信息
{
"hosts" : [
"localhost:20000",
"localhost:20001",
"localhost:20002"
],
"setName" : "testReplSet",
"setVersion" : 2,
"ismaster" : true,
"secondary" : false,
"primary" : "localhost:20001",//可以看到主節點已經自動變成了20001端口
"me" : "localhost:20001",
"electionId" : ObjectId("7fffffff0000000000000005"),
"lastWrite" : {
"opTime" : {
"ts" : Timestamp(1503554791, 1),
"t" : NumberLong(5)
},
"lastWriteDate" : ISODate("2017-08-24T06:06:31Z")
},
"maxBsonObjectSize" : 16777216,
"maxMessageSizeBytes" : 48000000,
"maxWriteBatchSize" : 1000,
"localTime" : ISODate("2017-08-24T06:06:36.564Z"),
"maxWireVersion" : 5,
"minWireVersion" : 0,
"readOnly" : false,
"ok" : 1
}

由以上測試案例可以發現,以上這種情況自動故障轉移是可以正常運作的,然而,以下情況不會故障轉移

//將被選爲主節點的20001端口斷開
> db2.adminCommand({"shutdown":1})
2017-08-24T14:20:15.703+0800 E QUERY[thread1] Error: error doing query: fail
ed: network error while attempting to run command 'shutdown' on host 'localhost:
20001'  :
DB.prototype.runCommand@src/mongo/shell/db.js:132:1
DB.prototype.adminCommand@src/mongo/shell/db.js:150:16
@(shell):1:1
> conn3 = new Mongo("localhost:20002")//連接上副本集中僅剩的一個備份節點
connection to localhost:20002
> db3 = conn3.getDB("test")
test
> db3.isMaster()//查看當前副本集的節點情況
{
"hosts" : [
"localhost:20000",
"localhost:20001",
"localhost:20002"
],
"setName" : "testReplSet",
"setVersion" : 2,
"ismaster" : false,//這裏可以發現該節點始終還是備份節點,而且primary節點信息沒有了
"secondary" : true,
"me" : "localhost:20002",
"lastWrite" : {
"opTime" : {
"ts" : Timestamp(1503555611, 1),
"t" : NumberLong(5)
},
"lastWriteDate" : ISODate("2017-08-24T06:20:11Z")
},
"maxBsonObjectSize" : 16777216,
"maxMessageSizeBytes" : 48000000,
"maxWriteBatchSize" : 1000,
"localTime" : ISODate("2017-08-24T06:21:09.324Z"),
"maxWireVersion" : 5,
"minWireVersion" : 0,
"readOnly" : false,
"ok" : 1
}
>

以上測試發現,如果副本集的成員只有兩個的時候主節點斷開了,備份節點不會被選舉爲主節點,這和副本集的自動故障轉移的選舉機制有關。

選舉機制:當一個成員無法到達主節點時,它就會申請被選舉爲主節點。希望被選舉爲主節點的成員,會向它能到達的所有成員發送通知。如果這個成員不符合候選人要求,其他成員可能會知道相關原因:這個成員的數據落後於副本集,或者是已經有一個運行中的主節點(那個力求被選舉成爲主節點的成員無法到達這個主節點)。在這些情況下,其他成員不會允許進行選舉。假如沒有反對的理由,其他成員就會對這個成員進行選舉投票。如果這個成員得到副本集中“大多數”贊成票,它就選舉成功,會轉換到主節點狀態。如果達不到“大多數”的要求,那麼選舉失敗,它仍然處於備份節點狀態,之後還可以再次申請被選舉爲主節點。主節點會一直處於主節點狀態,除非它由於不再滿足“大多數”的要求或者掛了而退位,另外,副本集被重新配置也會導致主節點退位。

根據選舉機制可以發現,備份節點可以發起投票,當獲得“大多數”贊成票時纔會被選舉爲主節點,而且不可以自己投給自己。分析以上的情況可知該備份節點有發起投票請求,但是由於得不到“贊成票”(因爲其他兩個節點已經停止了),因此它永遠也無法成爲主節點。

爲了是選舉機制能正常運作,下面嘗試重新啓動其中一個節點,看看會發生什麼?

//啓動20001端口的節點,啓動時需要制定端口號,對應數據庫路徑,副本集名稱,如果不指定的話,該數據庫進程不會添加到副本集中

mongod –port 20001 –dbpath C:\data\db\testReplSet-1 –replSet testReplSet

//在原來連接着20002端口的窗口中查看節點信息

> db3.isMaster()
{
"hosts" : [
"localhost:20000",
"localhost:20001",
"localhost:20002"
],
"setName" : "testReplSet",
"setVersion" : 2,
"ismaster" : true,
"secondary" : false,
"primary" : "localhost:20002",
"me" : "localhost:20002",
"electionId" : ObjectId("7fffffff0000000000000006"),
"lastWrite" : {
"opTime" : {
"ts" : Timestamp(1503556774, 1),
"t" : NumberLong(6)
},
"lastWriteDate" : ISODate("2017-08-24T06:39:34Z")
},
"maxBsonObjectSize" : 16777216,
"maxMessageSizeBytes" : 48000000,
"maxWriteBatchSize" : 1000,
"localTime" : ISODate("2017-08-24T06:39:35.141Z"),
"maxWireVersion" : 5,
"minWireVersion" : 0,
"readOnly" : false,
"ok" : 1
}

根據以上的輸出信息可以清楚看到20002端口被順利選舉爲主節點了,這是因爲啓動了20001端口後滿足了“大多數”原則。

5.rs輔助函數的使用,包括rs.status()、rs.config()、rs.add()等,具體可以查閱官方文檔

//以下是rs.status()的使用實例
C:\Program Files\MongoDB\Server\3.4\bin>mongo --port 20001//連接到某個節點的mongodb shell上
MongoDB shell version v3.4.5
connecting to: mongodb://127.0.0.1:20001/
MongoDB server version: 3.4.5
MongoDB Enterprise testReplSet:SECONDARY> rs.status()//查看副本集狀態,可以看到各個節點的運行情況等信息
{
"set" : "testReplSet",
"date" : ISODate("2017-08-24T06:51:57.159Z"),
"myState" : 2,
"term" : NumberLong(6),
"syncingTo" : "localhost:20002",
"heartbeatIntervalMillis" : NumberLong(2000),
"optimes" : {
"lastCommittedOpTime" : {
"ts" : Timestamp(1503557514, 1),
"t" : NumberLong(6)
},
"appliedOpTime" : {
"ts" : Timestamp(1503557514, 1),
"t" : NumberLong(6)
},
"durableOpTime" : {
"ts" : Timestamp(1503557514, 1),
"t" : NumberLong(6)
}
},
"members" : [
{
"_id" : 0,
"name" : "localhost:20000",
"health" : 0,
"state" : 8,
"stateStr" : "(not reachable/healthy)",
"uptime" : 0,
"optime" : {
"ts" : Timestamp(0, 0),
"t" : NumberLong(-1)
},
"optimeDurable" : {
"ts" : Timestamp(0, 0),
"t" : NumberLong(-1)
},
"optimeDate" : ISODate("1970-01-01T00:00:00Z"),
"optimeDurableDate" : ISODate("1970-01-01T00:00:00Z"),
"lastHeartbeat" : ISODate("2017-08-24T06:51:56.592Z"),
"lastHeartbeatRecv" : ISODate("1970-01-01T00:00:00Z"),
"pingMs" : NumberLong(0),
"lastHeartbeatMessage" : "����Ŀ�����������ܾ����޷����ӡ�",

"configVersion" : -1
},
{
"_id" : 1,
"name" : "localhost:20001",
"health" : 1,
"state" : 2,
"stateStr" : "SECONDARY",
"uptime" : 752,
"optime" : {
"ts" : Timestamp(1503557514, 1),
"t" : NumberLong(6)
},
"optimeDate" : ISODate("2017-08-24T06:51:54Z"),
"syncingTo" : "localhost:20002",
"configVersion" : 2,
"self" : true
},
{
"_id" : 2,
"name" : "localhost:20002",
"health" : 1,
"state" : 1,
"stateStr" : "PRIMARY",
"uptime" : 743,
"optime" : {
"ts" : Timestamp(1503557514, 1),
"t" : NumberLong(6)
},
"optimeDurable" : {
"ts" : Timestamp(1503557514, 1),
"t" : NumberLong(6)
},
"optimeDate" : ISODate("2017-08-24T06:51:54Z"),
"optimeDurableDate" : ISODate("2017-08-24T06:51:54Z"),
"lastHeartbeat" : ISODate("2017-08-24T06:51:55.639Z"),
"lastHeartbeatRecv" : ISODate("2017-08-24T06:51:56.348Z"
),
"pingMs" : NumberLong(0),
"electionTime" : Timestamp(1503556772, 1),
"electionDate" : ISODate("2017-08-24T06:39:32Z"),
"configVersion" : 2
}
],
"ok" : 1
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章