一.replica sets介绍
一个复制集是一组包含相同数据集的mongod实例.一个复制集只能有一个是primary节点,其它的节点为secondary节点.
和主从复制的原理一样,复制集也是通过读取oplog来进行数据传输.oplog是一个capped collection即固定表,创建表的时候可以指定其大小,当oplog满的时候会删除旧的数据.
所以设置oplog的大小非常重要,如果oplog在primary节点被覆盖而尚未被secondary节点读取的话就要重新resync.
一般的使用replica sets复制集使用如下架构,一主一备,还有一个仲裁负责进行failover.仲裁主机不存放数据.
复制集使用的异步同步方式,复制集成员直接每隔2s发送一次hearbeat(pings).当主节点与其它成员通信超时10s后,一个secondary节点将会被选举为primary节点.在新的版本中,如果存在多个secondary节点,当第一个节点被选举为primary后,其它的secondary节点将从它开始复制数据.
二.创建replica sets
下面是搭建一个一主两从三节点replica sets的具体步骤
(1).添加replSet参数
在你所有节点的mongodb实例添加replSet参数,在一个group中的replSet参数需要一致,有点类似于oracle dg中的db_name参数.
然后启动所有节点的mongodb实例
例如我的主机名为mongodb1的配置文件:
[root@mongodb1 ~]# cat /etc/mongod.conf
port=27017
dbpath=/data/db
logpath=/data/log/mongod.log
fork = true
oplogSize=500
replSet = rs0
然后启动所有的实例:
[root@mongodb1 ~]# mongod -f /etc/mongod.conf
about to fork child process, waiting until server is ready for connections.
forked process: 2722
child process started successfully, parent exiting
(2).初始化replica set
使用
rs.initiate()来初始化复制集,在一个节点而且只能在一个节点进行初始化,例如我们这里在mongodb1上进行初始化,那么这个节点将会成为primary节点.
这里我们没有配置config参数文件,那么系统会使用默认的配置文件.
> rs.initiate()
{
"info2" : "no configuration specified. Using a default configuration for the set",
"me" : "mongodb1:27017",
"ok" : 1
}
rs0:OTHER>
当然你也可以先配置一个配置文件,然后使用rs.initiate(rsconf)来初始化,例如:
rsconf = {
_id: "rs0",
members: [
{
_id: 0,
host: "<hostname>:27017"
}
]
}
(3).检查初始化配置文件
使用
rs.conf()来查看初始化配置文件:
rs0:OTHER> rs.conf()
{
"_id" : "rs0",
"version" : 1,
"protocolVersion" : NumberLong(1),
"members" : [
{
"_id" : 0,
"host" : "mongodb1:27017",
"arbiterOnly" : false,
"buildIndexes" : true,
"hidden" : false,
"priority" : 1,
"tags" : {
},
"slaveDelay" : NumberLong(0),
"votes" : 1
}
],
"settings" : {
"chainingAllowed" : true,
"heartbeatIntervalMillis" : 2000,
"heartbeatTimeoutSecs" : 10,
"electionTimeoutMillis" : 10000,
"getLastErrorModes" : {
},
"getLastErrorDefaults" : {
"w" : 1,
"wtimeout" : 0
},
"replicaSetId" : ObjectId("575647b35e9005faa0e8d690")
}
}
可以看到当前的members只有一个,即_id为0的,即我们刚刚初始化的这个节点.
(4).将剩下的成员添加到复制集
使用
rs.add()方法将成员添加到复制集.你必须连接到primary节点来执行添加成员的操作.你可以使用
rs.status()查看主节点信息:
rs0:PRIMARY> rs.status()
{
"set" : "rs0",
"date" : ISODate("2016-06-07T04:16:20.542Z"),
"myState" : 1,
"term" : NumberLong(1),
"heartbeatIntervalMillis" : NumberLong(2000),
"members" : [
{
"_id" : 0,
"name" : "mongodb1:27017",
"health" : 1,
"state" : 1,
"stateStr" : "PRIMARY",
"uptime" : 1184,
"optime" : {
"ts" : Timestamp(1465272244, 1),
"t" : NumberLong(1)
},
"optimeDate" : ISODate("2016-06-07T04:04:04Z"),
"electionTime" : Timestamp(1465272243, 2),
"electionDate" : ISODate("2016-06-07T04:04:03Z"),
"configVersion" : 1,
"self" : true
}
],
"ok" : 1
}
添加成员:
rs0:PRIMARY> rs.add("mongodb2:27017")
{ "ok" : 1 }
rs0:PRIMARY> rs.add("mongodb3:27017")
{ "ok" : 1 }
再次查看复制集配置,可以看到members有三个成员了.
rs0:PRIMARY> rs.conf()
{
"_id" : "rs0",
"version" : 3,
"protocolVersion" : NumberLong(1),
"members" : [
{
"_id" : 0,
"host" : "mongodb1:27017",
"arbiterOnly" : false,
"buildIndexes" : true,
"hidden" : false,
"priority" : 1,
"tags" : {
},
"slaveDelay" : NumberLong(0),
"votes" : 1
},
{
"_id" : 1,
"host" : "mongodb2:27017",
"arbiterOnly" : false,
"buildIndexes" : true,
"hidden" : false,
"priority" : 1,
"tags" : {
},
"slaveDelay" : NumberLong(0),
"votes" : 1
},
{
"_id" : 2,
"host" : "mongodb3:27017",
"arbiterOnly" : false,
"buildIndexes" : true,
"hidden" : false,
"priority" : 1,
"tags" : {
},
"slaveDelay" : NumberLong(0),
"votes" : 1
}
],
"settings" : {
"chainingAllowed" : true,
"heartbeatIntervalMillis" : 2000,
"heartbeatTimeoutSecs" : 10,
"electionTimeoutMillis" : 10000,
"getLastErrorModes" : {
},
"getLastErrorDefaults" : {
"w" : 1,
"wtimeout" : 0
},
"replicaSetId" : ObjectId("575647b35e9005faa0e8d690")
}
}
查看复制集状态,可以看到mongodb1是primary节点,其它两个是secondary节点:
rs0:PRIMARY> rs.status()
{
"set" : "rs0",
"date" : ISODate("2016-06-07T04:28:12.721Z"),
"myState" : 1,
"term" : NumberLong(1),
"heartbeatIntervalMillis" : NumberLong(2000),
"members" : [
{
"_id" : 0,
"name" : "mongodb1:27017",
"health" : 1,
"state" : 1,
"stateStr" : "PRIMARY",
"uptime" : 1896,
"optime" : {
"ts" : Timestamp(1465273627, 1),
"t" : NumberLong(1)
},
"optimeDate" : ISODate("2016-06-07T04:27:07Z"),
"electionTime" : Timestamp(1465272243, 2),
"electionDate" : ISODate("2016-06-07T04:04:03Z"),
"configVersion" : 3,
"self" : true
},
{
"_id" : 1,
"name" : "mongodb2:27017",
"health" : 1,
"state" : 2,
"stateStr" : "SECONDARY",
"uptime" : 73,
"optime" : {
"ts" : Timestamp(1465273627, 1),
"t" : NumberLong(1)
},
"optimeDate" : ISODate("2016-06-07T04:27:07Z"),
"lastHeartbeat" : ISODate("2016-06-07T04:28:11.197Z"),
"lastHeartbeatRecv" : ISODate("2016-06-07T04:28:12.206Z"),
"pingMs" : NumberLong(0),
"syncingTo" : "mongodb1:27017",
"configVersion" : 3
},
{
"_id" : 2,
"name" : "mongodb3:27017",
"health" : 1,
"state" : 2,
"stateStr" : "SECONDARY",
"uptime" : 65,
"optime" : {
"ts" : Timestamp(1465273627, 1),
"t" : NumberLong(1)
},
"optimeDate" : ISODate("2016-06-07T04:27:07Z"),
"lastHeartbeat" : ISODate("2016-06-07T04:28:11.198Z"),
"lastHeartbeatRecv" : ISODate("2016-06-07T04:28:09.224Z"),
"pingMs" : NumberLong(0),
"configVersion" : 3
}
],
"ok" : 1
}
我们到mongodb2节点查看日志,发现我们在进行rs.add()的时候,系统自动进行了初始化:
2016-06-07T12:26:57.846+0800 I REPL [ReplicationExecutor] This node is mongodb2:27017 in the config
2016-06-07T12:26:57.846+0800 I REPL [ReplicationExecutor] transition to STARTUP2
2016-06-07T12:26:57.846+0800 I REPL [rsSync] ******
2016-06-07T12:26:57.846+0800 I REPL [rsSync] creating replication oplog of size: 500MB...
2016-06-07T12:26:57.846+0800 I REPL [ReplicationExecutor] Member mongodb1:27017 is now in state PRIMARY
2016-06-07T12:26:57.858+0800 I STORAGE [rsSync] Starting WiredTigerRecordStoreThread local.oplog.rs
2016-06-07T12:26:57.858+0800 I STORAGE [rsSync] The size storer reports that the oplog contains 0 records totaling to 0 bytes
2016-06-07T12:26:57.858+0800 I STORAGE [rsSync] Scanning the oplog to determine where to place markers for truncation
2016-06-07T12:26:57.922+0800 I REPL [rsSync] ******
2016-06-07T12:26:57.922+0800 I REPL [rsSync] initial sync pending
2016-06-07T12:26:57.940+0800 I REPL [ReplicationExecutor] syncing from: mongodb1:27017
2016-06-07T12:26:57.943+0800 I REPL [rsSync] initial sync drop all databases
2016-06-07T12:26:57.943+0800 I STORAGE [rsSync] dropAllDatabasesExceptLocal 2
2016-06-07T12:26:57.966+0800 I REPL [rsSync] initial sync clone all databases
2016-06-07T12:26:57.967+0800 I REPL [rsSync] initial sync cloning db: suq
...
...
...
2016-06-07T12:27:00.234+0800 I REPL [rsSync] oplog sync 3 of 3
2016-06-07T12:27:00.235+0800 I REPL [rsSync] initial sync finishing up
2016-06-07T12:27:00.235+0800 I REPL [rsSync] set minValid=(term: 1, timestamp: Jun 7 12:26:58:1)
2016-06-07T12:27:00.240+0800 I REPL [rsSync] initial sync done
2016-06-07T12:27:00.241+0800 I REPL [ReplicationExecutor] transition to RECOVERING
2016-06-07T12:27:00.242+0800 I REPL [ReplicationExecutor] transition to SECONDARY
此时登录到mongodb2上发现提示符也变成了secondary,查看数据也全部同步过来了:
rs0:SECONDARY> use suq
switched to db suq
rs0:SECONDARY> rs.slaveOk()
rs0:SECONDARY> show collections
t1
test2
test3
test4
test5
test6
test7
到此replica sets的搭建就结束了,还是蛮简单的.
三.复制集参数
(1)参数介绍
这里介绍rs.conf()里的参数,例如下面这个config内容:
rs0:PRIMARY> rs.conf()
{
"_id" : "rs0",
"version" : 3,
"protocolVersion" : NumberLong(1),
"members" : [
{
"_id" : 0,
"host" : "mongodb1:27017",
"arbiterOnly" : false,
"buildIndexes" : true,
"hidden" : false,
"priority" : 1,
"tags" : {
},
"slaveDelay" : NumberLong(0),
"votes" : 1
},
{
"_id" : 1,
"host" : "mongodb2:27017",
"arbiterOnly" : false,
"buildIndexes" : true,
"hidden" : false,
"priority" : 1,
"tags" : {
},
"slaveDelay" : NumberLong(0),
"votes" : 1
},
{
"_id" : 2,
"host" : "mongodb3:27017",
"arbiterOnly" : false,
"buildIndexes" : true,
"hidden" : false,
"priority" : 1,
"tags" : {
},
"slaveDelay" : NumberLong(0),
"votes" : 1
}
],
"settings" : {
"chainingAllowed" : true,
"heartbeatIntervalMillis" : 2000,
"heartbeatTimeoutSecs" : 10,
"electionTimeoutMillis" : 10000,
"getLastErrorModes" : {
},
"getLastErrorDefaults" : {
"w" : 1,
"wtimeout" : 0
},
"replicaSetId" : ObjectId("575647b35e9005faa0e8d690")
}
}
_id:是用来标识复制集,参数内容和数据库启动的时候设置的rplSet参数一致
version:用来表示config参数的新旧,每次修改了config然后使用rs.reconfig重新配置的时候version的值会自动加1
protocolversion:是协议版本
members是一个数组,数组成员表示每个节点的信息.下面结束members的主要内容.
_id:用来标识节点号,唯一
host:表示节点的地址和端口信息
arbiterOnly:这是一个bool型,默认为false,用来表示这个节点是否是arbiter节点,只是用来投票
buildIndexes:这也是一个bool型,默认为true.用来表示同步的时候是否同步索引.一般设置为true.如果要设置为false,则必须将priority设置为0
hidden:这也是bool型,默认问false,用来表示这个节点是否为隐藏节点,如果是隐藏节点将不对外服务,只是单纯的同步信息,而且如果设置为了隐藏节点,使用rs.isMaster()方法将无法查看到隐藏节点的信息,但是可以使用rs.status()查看.设置隐藏节点必须首先将节点的priority设置为0
priority:表示权重,默认为1,如果将priority设置为0那么这个节点将永远无法成为primary节点,现在新的版本可以设置为超过1的数
slaveDelay:复制延迟,这个是整数,单位为秒,用来设置复制的延时.一般用来防止误操作,延迟节点必须优先级设置为0,hidden设置为true,然后设置slaveDelay值,
votes:表示这个节点是否有权利进行投票.
tags:表示标记,例如可以标记这个节点的作用等
settings是一些配置信息
chainingAllowed:表示是否允许链式复制,即某个secondary可以作为其它的secondary的源,默认是true.
heartbeatIntervalMillis:表示heartbeat的间隔时间,默认是没个两秒钟发送一个hearbeat包.
heartbeatTimeoutSecs:表示心跳检测超时时间,默认是10秒.
electionTimeoutMillis:表示选举超时时间,默认是10秒.
(2)参数修改
修改复制集的参数一般分为三步:
1.将当前的参数进行复制
rs0:PRIMARY> rsconfig=rs.conf()
2.修改参数
例如我要将节点_id为2的主机的权限设置为0.5
rs0:PRIMARY> rsconfig.members[2].priority = 0.5
0.5
3.重新reconfig设置
rs0:PRIMARY> rs.reconfig(rsconfig)
{ "ok" : 1 }
最后重新查看配置文件:
{
"_id" : 2,
"host" : "mongodb3:27017",
"arbiterOnly" : false,
"buildIndexes" : true,
"hidden" : false,
"priority" : 0.5,
"tags" : {
},
"slaveDelay" : NumberLong(0),
"votes" : 1
}
四.复制集管理
Replicate methods方法
可以使用rs.help()来查看所有的复制方法
rs0:SECONDARY> rs.help()
rs.status() { replSetGetStatus : 1 } checks repl set status
rs.initiate() { replSetInitiate : null } initiates set with default settings
rs.initiate(cfg) { replSetInitiate : cfg } initiates set with configuration cfg
rs.conf() get the current configuration object from local.system.replset
rs.reconfig(cfg) updates the configuration of a running replica set with cfg (disconnects)
rs.add(hostportstr) add a new member to the set with default attributes (disconnects)
rs.add(membercfgobj) add a new member to the set with extra attributes (disconnects)
rs.addArb(hostportstr) add a new member which is arbiterOnly:true (disconnects)
rs.stepDown([stepdownSecs, catchUpSecs]) step down as primary (disconnects)
rs.syncFrom(hostportstr) make a secondary sync from the given member
rs.freeze(secs) make a node ineligible to become primary for the time specified
rs.remove(hostportstr) remove a host from the replica set (disconnects)
rs.slaveOk() allow queries on secondary nodes
rs.printReplicationInfo() check oplog size and time range
rs.printSlaveReplicationInfo() check replica set members and replication lag
db.isMaster() check who is primary
reconfiguration helpers disconnect from the database so the shell will display
an error, even if the command succeeds.
下面的一些修改操作一般都必须在primary节点上执行.
(1)rs.conf查看配置文件
前面已经介绍过了,使用
rs.conf()来查看复制集参数
(2)rs.status查看复制集状态
前面已经介绍了一个使用
rs.status()查看.
还可以使用
rs.isMaster()查看节点是否是master节点:
rs0:SECONDARY> rs.isMaster()
{
"hosts" : [
"mongodb1:27017",
"mongodb2:27017",
"mongodb3:27017"
],
"setName" : "rs0",
"setVersion" : 4,
"ismaster" : false,
"secondary" : true,
"primary" : "mongodb1:27017",
"me" : "mongodb3:27017",
"maxBsonObjectSize" : 16777216,
"maxMessageSizeBytes" : 48000000,
"maxWriteBatchSize" : 1000,
"localTime" : ISODate("2016-06-07T12:08:44.914Z"),
"maxWireVersion" : 4,
"minWireVersion" : 0,
"ok" : 1
}
(3)rs.add添加节点
使用rs.add([host],[arbiterOnly])方法来添加节点到复制集,这个方法有两个参数,第一个参数是host,第二个参数是arbiterOnly,默认为false,当为true的时候表示添加的节点只是用来作为仲裁节点.
在3.0以前的版本你需要手工指定_id,3.0以后的版本则不需要,系统会自动添加,下面是常用的语句:
rs.add('mongodb3.example.net:27017')
rs.add({ host: "mongodb3:27017", priority: 0 })
rs.add("mongodb3:27010", true)
rs0:PRIMARY> rs.add({"host":"mongodb3:27017","priority":0,"hidden":true})
{ "ok" : 1 }
(4)rs.remove删除节点
rs.remove()就一个参数hostname:
rs0:PRIMARY> rs.remove("mongodb3:27017")
{ "ok" : 1 }
(5)rs.addArb添加投票节点
rs.addArb()同样可以添加投票节点,也只有一个参数为hostname:
rs0:PRIMARY> rs.addArb("mongodb3:27017")
{ "ok" : 1 }
(6)rs.freeze()将节点冻结
rs.freeze()就一个参数,为时间单位秒,表示在多长时间内将此节点冻结,即无法成为primary节点
(7)查看复制延时
rs.printSlaveReplicationInfo()
rs0:PRIMARY> rs.printReplicationInfo()
configured oplog size: 500MB
log length start to end: 30418secs (8.45hrs)
oplog first event time: Tue Jun 07 2016 12:04:03 GMT+0800 (CST)
oplog last event time: Tue Jun 07 2016 20:31:01 GMT+0800 (CST)
now: Tue Jun 07 2016 20:39:09 GMT+0800 (CST)
rs.printSlaveReplicationInfo()
rs0:SECONDARY> rs.printSlaveReplicationInfo()
source: mongodb2:27017
syncedTo: Tue Jun 07 2016 20:31:01 GMT+0800 (CST)
0 secs (0 hrs) behind the primary
结果输出都比较直观,记录了oplog的时间戳,同步延时等信息.
(8)rs.reconfig重新配置参数
rs.reconfig(configuration, force),上面已经介绍了重新配置参数的方法,需要注意的是,在某些情况下执行rs.reconfig()有可能触发将当前的primary降级为secondary节点.
当primary降级为secondary的的时候会强制关闭所有的客户端连接,然后花费几分钟时间将secondary节点选举为primary节点,在重新配置期间,数据库将处于不可操作状态.
特别需要注意的是当你指定的force为true的时候有可能会将一个已经提交的写操作回滚掉,因此需要谨慎的使用.
(9)rs.stepDown降级primary
rs.stepDown(stepDownSecs, secondaryCatchUpPeriodSecs)方法将primary节点降级为secondary节点,secondary节点会推选出一个升级为primary节点.此方法有两个参数,
第一个参数表示在多少时间内将降级primary,也表示这在此期间此节点无法变为primary,如果你指定了一个非number类型,则默认为60s
第二个参数表示mongod将等待多长时间让一个secondary提升为primary.默认为10s,第一个参数必须大于第二个参数.
rs0:PRIMARY> rs.stepDown()
2016-06-07T20:58:53.657+0800 E QUERY [thread1] Error: error doing query: failed: network error while attempting to run command 'replSetStepDown' on host '127.0.0.1:27017' :
DB.prototype.runCommand@src/mongo/shell/db.js:135:1
DB.prototype.adminCommand@src/mongo/shell/db.js:153:16
rs.stepDown@src/mongo/shell/utils.js:1181:12
@(shell):1:1
2016-06-07T20:58:53.660+0800 I NETWORK [thread1] trying reconnect to 127.0.0.1:27017 (127.0.0.1) failed
2016-06-07T20:58:53.662+0800 I NETWORK [thread1] reconnect 127.0.0.1:27017 (127.0.0.1) ok
rs0:SECONDARY>
rs0:SECONDARY>
需要注意的是stepDown的过程中所有的客户端连接都会断开,会终止一些用户操作,例如创建索引.为了避免回滚,设置第二个参数,让mongod等待一段时间.
如果没有可用的secondary,那么会抛出一个异常.
(10)rs.syncFrom()
rs.syncFrom()可以让管理员里手工设置默认的复制源.参数为["hostname:port"]
在3.2版本中一个没有投票权的节点无法作为一个拥有投票权的节点的源.是否拥有投票权查看rs.conf的vote参数.
在rs.status状态中有一个状态为syncingTO表示此节点是从那个节点同步数据,例如:
{
"_id" : 2,
"name" : "mongodb3:27017",
"health" : 1,
"state" : 2,
"stateStr" : "SECONDARY",
"uptime" : 33316,
"optime" : {
"ts" : Timestamp(1465304797, 1),
"t" : NumberLong(2)
},
"optimeDate" : ISODate("2016-06-07T13:06:37Z"),
"syncingTo" : "mongodb2:27017",
"configVersion" : 14,
"self" : true
}
可以看到原来_id为2的节点是从mongodb2(primary)同步数据,我们可以修改他从其它的secondary(mongodb1)来同步数据:
rs0:SECONDARY> rs.syncFrom("mongodb1:27017")
{
"syncFromRequested" : "mongodb1:27017",
"prevSyncTarget" : "mongodb2:27017",
"ok" : 1
}
而且可以可以在日志里面看到如下信息:
2016-06-07T21:10:55.416+0800 I REPL [ReplicationExecutor] syncing from: mongodb1:27017 by request
2016-06-07T21:10:55.418+0800 I REPL [SyncSourceFeedback] setting syncSourceFeedback to mongodb1:27017
2016-06-07T21:10:55.419+0800 I ASIO [NetworkInterfaceASIO-BGSync-0] Successfully connected to mongodb1:27017 2016-06-07T21:11:17.124+0800 I ASIO [NetworkInterfaceASIO-BGSync-0] Successfully connected to mongodb1:27017
复制维护
(1)修改oplog大小
具体方法见文档:
基本思路就是将一个secondary变为单机模式,然后重建local数据库下的oplog.rs集合,在重建的时候需要注意保留最新数据,方法是使用临时表做中间表.
(2)强制将节点转换为primary
具体方法见文档:
基本思路就是将节点的优先级调高,然后reconfig
(3)重新同步节点
可以使用resync命令:
rs0:SECONDARY> db.runCommand({"resync":1})
{ "ok" : 1 }
如果有多个secondary,可以拷贝其它正常的secondary数据.
还可以先将节点remove,然后删除dbpath下的文件,然后重新add,那么系统会自动进行resync.