Mongodb从0到1系列二:数据库与集合操作文档、增删改查
Mongodb从0到1系列四: Limit与Skip方法、排序、索引
11 复制集
复制集是一个带有故障转移的主从集群。是从现有的主从模式演变而来,增加了自动故障转移和节点成员自动恢复。复制集模式中没有固定的主结点,在启动后,多个服务节点间将自动选举产生一个主结点。该主结点被称为primary,一个或多个从结点被称为secondaries。primary结点基本上就是master结点,不同之处在于primary结点在不同时间可能是不同的服务器。如果当前的主结点失效了,复制集中的其余结点将会试图选出一个新的主结点。
复制集模式的好处是,一切自动化。首先,复制集模式本身做了大量的管理工作,自动管理从节点,确保数据不会不一致。其次,主节点挂掉后,会自动判断集群中的服务器并进行故障转移,推举新的主节点。
Primary 主节点,一个复制集有且仅有一台服务器处于Primary状态,只有主节点才对外提供读写服务。如果主节点挂掉,复制集将会投票选出一个备用节点成为新的主节点。
Secondary 备用节点,复制集允许有多台Secondary,每个备用节点的数据与主节点的数据是完全同步的。
Recovering 恢复中,当复制集中某台服务器挂掉或者掉线后数据无法同步,重新恢复服务后从其他成员复制数据,这时就处于恢复过程,数据同步后,该节点又回到备用状态。
Arbiter 仲裁节点,该类节点可以不用单独存在,如果配置为仲裁节点,就主要负责在复本集中监控其他节点状态,投票选出主节点。该节点将不会用于存放数据。如果没有仲裁节点,那么投票工作将由所有节点共同进行。
Down 无效节点,当服务器挂掉或掉线时就会处于该状态。
11.1 有仲裁节点
由于机器数量限制,只在一个节点上做测试,使用3个不同的dbpath和端口, 开3个sesion, 指定同一个replSetdb2a:~ # mkdir /data/repdb2
db2a:~ # mkdir /data/repdb3
db2a:~ # mongod --dbpath=/data/db --port 27017 --replSet rs1
db2a:~ # mongod --dbpath=/data/repdb2 --port 27018 --replSet rs1
db2a:~ # mongod --dbpath=/data/repdb3 --port 27019 --replSet rs1
新开一mongo shell Session:
//配置复制集,27019作为仲裁节点:
db2a:~ # mongo 192.168.37.1:27017
MongoDB Enterprise > config = {_id:"rs1", members:[{_id:0,host:"192.168.37.1:27017"}, {_id:1,host:"192.168.37.1:27018"}, {_id:2,host:"192.168.37.1:27019", arbiterOnly:true}]}
{
"_id" : "rs1",
"members" : [
{
"_id" : 0,
"host" : "192.168.37.1:27017"
},
{
"_id" : 1,
"host" : "192.168.37.1:27018"
},
{
"_id" : 2,
"host" : "192.168.37.1:27019",
"arbiterOnly" : true
}
]
}
MongoDB Enterprise > rs.initiate(config)
{ "ok" : 1 }
查看复制集配置:
MongoDB Enterprise rs1:SECONDARY> rs.conf()
{
"_id" : "rs1",
"version" : 1,
"protocolVersion" : NumberLong(1),
"members" : [
{
"_id" : 0,
"host" : "192.168.37.1:27017",
"arbiterOnly" : false,
"buildIndexes" : true,
"hidden" : false,
"priority" : 1,
"tags" : {
},
"slaveDelay" : NumberLong(0),
"votes" : 1
},
{
"_id" : 1,
"host" : "192.168.37.1:27018",
"arbiterOnly" : false,
"buildIndexes" : true,
"hidden" : false,
"priority" : 1,
"tags" : {
},
"slaveDelay" : NumberLong(0),
"votes" : 1
},
{
"_id" : 2,
"host" : "192.168.37.1:27019",
"arbiterOnly" : true,
"buildIndexes" : true,
"hidden" : false,
"priority" : 1,
"tags" : {
},
"slaveDelay" : NumberLong(0),
"votes" : 1
}
],
"settings" : {
"chainingAllowed" : true,
"heartbeatIntervalMillis" : 2000,
"heartbeatTimeoutSecs" : 10,
"electionTimeoutMillis" : 10000,
"catchUpTimeoutMillis" : 60000,
"getLastErrorModes" : {
},
"getLastErrorDefaults" : {
"w" : 1,
"wtimeout" : 0
},
"replicaSetId" : ObjectId("59740afc483597f1b29ddd33")
}
}
查看复制集状态,可以看到
27017的 "stateStr" : "PRIMARY"
27018的 "stateStr" : "SECONDARY"
27019的 "stateStr" : "ARBITER"
MongoDB Enterprise rs1:PRIMARY> rs.status()
{
"set" : "rs1",
"date" : ISODate("2017-07-23T02:38:06.598Z"),
"myState" : 1,
"term" : NumberLong(1),
"heartbeatIntervalMillis" : NumberLong(2000),
"optimes" : {
"lastCommittedOpTime" : {
"ts" : Timestamp(1500777485, 1),
"t" : NumberLong(1)
},
"appliedOpTime" : {
"ts" : Timestamp(1500777485, 1),
"t" : NumberLong(1)
},
"durableOpTime" : {
"ts" : Timestamp(1500777485, 1),
"t" : NumberLong(1)
}
},
"members" : [
{
"_id" : 0,
"name" : "192.168.37.1:27017",
"health" : 1,
"state" : 1,
"stateStr" : "PRIMARY",
"uptime" : 340,
"optime" : {
"ts" : Timestamp(1500777485, 1),
"t" : NumberLong(1)
},
"optimeDate" : ISODate("2017-07-23T02:38:05Z"),
"electionTime" : Timestamp(1500777223, 1),
"electionDate" : ISODate("2017-07-23T02:33:43Z"),
"configVersion" : 1,
"self" : true
},
{
"_id" : 1,
"name" : "192.168.37.1:27018",
"health" : 1,
"state" : 2,
"stateStr" : "SECONDARY",
"uptime" : 274,
"optime" : {
"ts" : Timestamp(1500777475, 1),
"t" : NumberLong(1)
},
"optimeDurable" : {
"ts" : Timestamp(1500777475, 1),
"t" : NumberLong(1)
},
"optimeDate" : ISODate("2017-07-23T02:37:55Z"),
"optimeDurableDate" : ISODate("2017-07-23T02:37:55Z"),
"lastHeartbeat" : ISODate("2017-07-23T02:38:05.508Z"),
"lastHeartbeatRecv" : ISODate("2017-07-23T02:38:05.068Z"),
"pingMs" : NumberLong(0),
"syncingTo" : "192.168.37.1:27017",
"configVersion" : 1
},
{
"_id" : 2,
"name" : "192.168.37.1:27019",
"health" : 1,
"state" : 7,
"stateStr" : "ARBITER",
"uptime" : 274,
"lastHeartbeat" : ISODate("2017-07-23T02:38:05.508Z"),
"lastHeartbeatRecv" : ISODate("2017-07-23T02:38:04.268Z"),
"pingMs" : NumberLong(0),
"configVersion" : 1
}
],
"ok" : 1
}
查看当前连接Server状态:发现是primary:
MongoDB Enterprise rs1:PRIMARY> rs.isMaster()
{
"hosts" : [
"192.168.37.1:27017",
"192.168.37.1:27018"
],
"arbiters" : [
"192.168.37.1:27019"
],
"setName" : "rs1",
"setVersion" : 1,
"ismaster" : true,
"secondary" : false,
"primary" : "192.168.37.1:27017",
"me" : "192.168.37.1:27017",
...
}
同时,可以分别在session1,2,3里看到以下信息:
Session1:
2017-07-23T10:33:43.277+0800 I REPL [ReplicationExecutor] transition to PRIMARY
------------
Session2:
2017-07-23T10:33:34.796+0800 I REPL [rsSync] transition to RECOVERING
2017-07-23T10:33:34.798+0800 I REPL [rsBackgroundSync] could not find member to sync from
2017-07-23T10:33:34.803+0800 I REPL [rsSync] transition to SECONDARY
------------
Session3:
2017-07-23T10:33:34.133+0800 I REPL [ReplicationExecutor] transition to ARBITER
连接到27018,查询状态,看到不是primary:
MongoDB Enterprise rs1:PRIMARY> exit
bye
db2a:~ # mongo 192.168.37.1:27018
MongoDB Enterprise rs1:SECONDARY> rs.isMaster()
{
"hosts" : [
"192.168.37.1:27017",
"192.168.37.1:27018"
],
"arbiters" : [
"192.168.37.1:27019"
],
"setName" : "rs1",
"setVersion" : 1,
"ismaster" : false,
"secondary" : true,
"primary" : "192.168.37.1:27017",
"me" : "192.168.37.1:27018",
...
}
查询数据,发现已经同步过来:
MongoDB Enterprise rs1:SECONDARY> rs.slaveOk()
MongoDB Enterprise rs1:SECONDARY> use test1
switched to db test1
MongoDB Enterprise rs1:SECONDARY> show collections
student
teacher
MongoDB Enterprise rs1:SECONDARY> db.student.find({},{"_id":0})
{ "name" : "Zhaoliu", "age" : 25, "course" : "Chinese" }
{ "name" : "Zhangsan", "age" : 22, "course" : "Chinese" }
{ "name" : "Lisi", "age" : 23, "course" : "computer" }
{ "name" : "Liuneng", "age" : 26, "course" : "English" }
{ "name" : "Wangwu", "age" : 24, "course" : "Chinese" }
{ "name" : "Xieguangkun", "age" : 42, "course" : "Art" }
连接到27019,并查询状态,可以看到是arbiter:
MongoDB Enterprise rs1:SECONDARY> exit
bye
db2a:~ # mongo 192.168.37.1:27019
MongoDB Enterprise rs1:ARBITER> rs.isMaster()
{
"hosts" : [
"192.168.37.1:27017",
"192.168.37.1:27018"
],
"arbiters" : [
"192.168.37.1:27019"
],
"setName" : "rs1",
"setVersion" : 1,
"ismaster" : false,
"secondary" : false,
"primary" : "192.168.37.1:27017",
"arbiterOnly" : true,
"me" : "192.168.37.1:27019",
..
}
现在把27017的服务关闭,看一下27018会不会自动变为PRIMARY, 把session1里的mongodb服务结束使用Ctrl+c结束掉,再次查询session2的isMaster(),发现变为primary了:
MongoDB Enterprise rs1:ARBITER> exit
bye
db2a:~ # mongo 192.168.37.1:27018
MongoDB Enterprise rs1:PRIMARY> rs.isMaster()
{
"hosts" : [
"192.168.37.1:27017",
"192.168.37.1:27018"
],
"arbiters" : [
"192.168.37.1:27019"
],
"setName" : "rs1",
"setVersion" : 1,
"ismaster" : true,
"secondary" : false,
"primary" : "192.168.37.1:27018",
"me" : "192.168.37.1:27018",
"electionId" : ObjectId("7fffffff0000000000000002"),
...
}
查看复制集状态,可以看到27017的状态已经变为"stateStr" : "(not reachable/healthy)"
MongoDB Enterprise rs1:PRIMARY> rs.status()
{
...
"members" : [
{
"_id" : 0,
"name" : "192.168.37.1:27017",
"health" : 0,
"state" : 8,
"stateStr" : "(not reachable/healthy)",
"uptime" : 0,
...
},
{
"_id" : 1,
"name" : "192.168.37.1:27018",
"health" : 1,
"state" : 1,
"stateStr" : "PRIMARY",
"uptime" : 2475,
...
},
{
"_id" : 2,
"name" : "192.168.37.1:27019",
"health" : 1,
"state" : 7,
"stateStr" : "ARBITER",
"uptime" : 2421,
....
}
],
"ok" : 1
}
再次在session1里启动mongodb服务之后,查看复制状态,发现27017变为SECONDARY:
MongoDB Enterprise rs1:PRIMARY> rs.status()
{
...
"members" : [
{
"_id" : 0,
"name" : "192.168.37.1:27017",
"health" : 1,
"state" : 2,
"stateStr" : "SECONDARY",
"uptime" : 29,
...
},
{
"_id" : 1,
"name" : "192.168.37.1:27018",
"health" : 1,
"state" : 1,
"stateStr" : "PRIMARY",
"uptime" : 2702,
...
},
{
"_id" : 2,
"name" : "192.168.37.1:27019",
"health" : 1,
"state" : 7,
"stateStr" : "ARBITER",
"uptime" : 2648,
...
}
],
"ok" : 1
}
11.2 无仲裁节点
理论上不是需要仲裁节点的,这次只用了两个节点,db2a上一个primary,db2b上一个secondary:db2a:~ # mkdir /data/repdb4
db2a:~ # mongod --dbpath=/data/repdb4 --port 27017 --replSet rs2
db2b:~ # mkdir /data/repdb1
db2b:~ # mongod --dbpath=/data/repdb1 --port 27017 --replSet rs2
db2a:
db2a:~ # mongo db2a:27017
> config = {_id:"rs2", members:[{_id:0,host:"db2a:27017"}, {_id:1,host:"db2b:27017"}]}
> rs.initiate(config)
rs2:PRIMARY> rs.status()
rs2:PRIMARY> rs.isMaster()
> use test1
> db.student.insert({name:'Zhaoliu',age:25,course:'Chinese'})
> db.student.insert({name:'Zhangsan',age:22,course:'Chinese'})
db2b:
db2b:~ # mongo db2b:27017
rs2:SECONDARY> rs.slaveOk()
rs2:SECONDARY> use test1
rs2:SECONDARY> db.student.find({},{"_id":0})
{ "name" : "Zhaoliu", "age" : 25, "course" : "Chinese" }
{ "name" : "Zhangsan", "age" : 22, "course" : "Chinese" }
但中断db2a的服务之后,db2b不会自动变成Primary,只有再次启动db2a服务之后,db2b才变成了primary。
再添加一个节点:
db2b上:
db2b:~ # mkdir /data/repdb2
db2b:~ # mongod --dbpath=/data/repdb2 --port 27018 --replSet rs2
在mongo shell里:
MongoDB Enterprise rs2:PRIMARY> rs.add('db2b:27018')
{ "ok" : 1 }
MongoDB Enterprise rs2:PRIMARY> rs.status()
{
..
"members" : [
{
"_id" : 0,
"name" : "db2a:27017",
"health" : 1,
"state" : 2,
"stateStr" : "SECONDARY",
...
},
{
"_id" : 1,
"name" : "db2b:27017",
"health" : 1,
"state" : 1,
"stateStr" : "PRIMARY",
...
},
{
"_id" : 2,
"name" : "db2b:27018",
"health" : 1,
"state" : 2,
"stateStr" : "SECONDARY",
"uptime" : 60,
...
}
],
"ok" : 1
}
这种情况下,如果将PRIMARY停掉(db2b:27017),那么剩下的两个节点会自动选出一个来作为PRIMARY