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
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章