一、MongoDB复制
1、mongodb复制简介
mongodb复制的实现有两种类型:
master/slave:和mysql主从复制非常近似,已经很少用了,
replica set:复制集或副本集,能自动实现故障转移
副本集
服务于同一数据集的一组mongodb实例
一个复制集只能有一个主节点,能读写,其它从节点只能读
主节点将数据修改操作保存至oplog(操作日志)中,各从节点通过oplog来复制数据并应用在本地
mongodb的复制至少需要两个节点,一般为3个节点或更多节点,即使只需要2个节点就够用时也应该使用令一个节点当做仲裁设备,可以不保存数据(如果只有一主一从的话,故障时不知道到底是主故障了还是从故障了)
副本集中各节点之间不断通过心跳信息传递来判断健康状态,默认心跳信息每隔2S传递一次,一旦和主节点与其它节点中断通信超过10S,副本集就会触发重新选举,选举一个从节点成为新的主节点
副本集特征:
奇数个节点的集群,应至少为3个节点,
任何节点可作为主节点,且只能有一个主节点
所有写入操作都在主节点上
自动故障转移,自动恢复
副本集中节点分类:
0优先级的节点:
冷备节点,不会被选举为主节点,但能参与选举过程,并持有数据集,能被客户端访问;常用于异地容灾
被隐藏的从节点:
首先得是0优先级的节点,且对客户端不可见
延迟复制的节点:
首先得是0优先级的节点,且复制时间落后于主节点一个固定时长
arbiter:
仲裁节点,不持有数据集
2、mongodb副本集
hearbeat:
实现心跳信息传递,并触发选举
oplog:
保存数据修改操作,是实现复制的基础工具
大小固定的文件,存储在local数据库中,复制集中各节点都有oplog,但只有主节点才会写oplog,并同步给从节点,
oplog具有幂等性,多次运行,结果不变
因为oplog的大小是固定的,不可能保持主节点的所有操作,所以从节点添加进复制集会先初始化:从节点先从主节点的数据集复制数据,并跟上主节点,然后才从oplog复制数据
> show dbs local 0.078125GB sb (empty) studnets (empty) test (empty) > use local switched to db local > show collections # 需要启用了复制集,初始化时,才会生成相关的集合 startup_log >
1个新从节点加入复制集合后的操作过程:
初始同步(initial sync)
回滚后追赶(post-rollback catch-up)
切分块迁移(sharding chunk migrations)
local数据库:
local数据库本身不参与复制(不会复制到别的节点去)
存放了副本集的所有元数据和oplog;用于存储oplog的是一个名为oplog.rs的collection(该节点加入了副本集后第一次启动时自动创建);
oplog.rs的大小依赖于OS及文件系统,默认为磁盘空间的5%(此值少于1G时为1G);但可以自定义其大小:使用oplogSize=N单位为M
3、Mongo的数据同步类型
1)初始同步
从节点没有任何数据时,但主节点已有数据
从节点丢失副本复制历史时
初始同步的步骤:
a、克隆所有数据库
b、应用数据集的所有改变:复制oplog并应用于本地
c、为所有collection构建索引
2)复制
二、副本集的配置
1、环境
Node2: CentOS6.5 x86_64
Node5: CentOS6.5 x86_64
Node7: CentOS6.5 x86_64
2、配置过程
应先关闭mongod
在配置文件中添加一下选项:
replSet=testSet # 副本集的名称,表示作为副本集中的节点
replIndexpPrefetch=id_only # 启用副本集索引预取(非必须),只在从节点有效
> 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([secs]) step down as primary (momentarily) (disconnects) rs.syncFrom(hostportstr) make a secondary to 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() shorthand for db.getMongo().setSlaveOk() db.isMaster() check who is primary db.printReplicationInfo() check oplog size and time range reconfiguration helpers disconnect from the database so the shell will display an error, even if the command succeeds. see also http://:28017/_replSet for additional diagnostic info > > rs.status() { "startupStatus" : 3, # 已运行的节点3个 "info" : "run rs.initiate(...) if not yet done for the set", # 提示应该初始化 "ok" : 0, "errmsg" : "can't get local.system.replset config from self or any seed (EMPTYCONFIG)" } > rs.initiate() # 初始化 { "info2" : "no configuration explicitly specified -- making one", "me" : "Node7:27017", "info" : "Config now saved locally. Should come online in about a minute.", "ok" : 1 } > rs.status() { "set" : "testSet", "date" : ISODate("2017-03-26T06:28:25Z"), "myState" : 1, "members" : [ # 副本集成员信息 { "_id" : 0, # 节点表示符 "name" : "Node7:27017", # 节点名称 "health" : 1, # 健康状态 "state" : 1, # 是否有状态信息 "stateStr" : "PRIMARY", # 状态字符串:主节点 "uptime" : 1387, # 已运行时间,单位为S "optime" : Timestamp(1490509672, 1), # oplog最近更新时间戳 "optimeDate" : ISODate("2017-03-26T06:27:52Z"), # 同上,时间 "self" : true # 是否是当前节点 } ], "ok" : 1 } testSet:PRIMARY> testSet:PRIMARY> rs.conf() { "_id" : "testSet", "version" : 1, "members" : [ { "_id" : 0, "host" : "Node7:27017" } ] } testSet:PRIMARY> show dbs local 1.078125GB testSet:PRIMARY> db test testSet:PRIMARY> use local switched to db local testSet:PRIMARY> show collections # 初始化后oplog.rs集合生成了 oplog.rs startup_log system.replset testSet:PRIMARY>
添加主机进副本集:
该主机需要监听在能副本集中其它节点通信的套接字上
在配置文件中设置了副本集的名称要和当前副本集一样
testSet:PRIMARY> rs.add("192.168.10.5") { "ok" : 1 } testSet:PRIMARY> rs.status() { "set" : "testSet", "date" : ISODate("2017-03-26T06:43:00Z"), "myState" : 1, "members" : [ { "_id" : 0, "name" : "Node7:27017", "health" : 1, "state" : 1, "stateStr" : "PRIMARY", "uptime" : 2262, "optime" : Timestamp(1490510577, 1), "optimeDate" : ISODate("2017-03-26T06:42:57Z"), "self" : true }, { "_id" : 1, "name" : "192.168.10.5:27017", "health" : 1, "state" : 6, "stateStr" : "UNKNOWN", # 可能正在初始化 "uptime" : 3, "optime" : Timestamp(0, 0), "optimeDate" : ISODate("1970-01-01T00:00:00Z"), "lastHeartbeat" : ISODate("2017-03-26T06:42:59Z"), "lastHeartbeatRecv" : ISODate("1970-01-01T00:00:00Z"), "pingMs" : 0, "lastHeartbeatMessage" : "still initializing" } ], "ok" : 1 } testSet:PRIMARY> rs.status() # 节点1初始化完成后的状态 { "set" : "testSet", "date" : ISODate("2017-03-26T06:49:56Z"), "myState" : 1, "members" : [ { "_id" : 0, "name" : "Node7:27017", "health" : 1, "state" : 1, "stateStr" : "PRIMARY", "uptime" : 2678, "optime" : Timestamp(1490510577, 1), "optimeDate" : ISODate("2017-03-26T06:42:57Z"), "self" : true }, { "_id" : 1, "name" : "192.168.10.5:27017", "health" : 1, "state" : 2, "stateStr" : "SECONDARY", "uptime" : 419, "optime" : Timestamp(1490510577, 1), "optimeDate" : ISODate("2017-03-26T06:42:57Z"), "lastHeartbeat" : ISODate("2017-03-26T06:49:56Z"), "lastHeartbeatRecv" : ISODate("2017-03-26T06:49:56Z"), "pingMs" : 0, "syncingTo" : "Node7:27017" } ], "ok" : 1 } testSet:PRIMARY> rs.conf() # 副本集配置信息 { "_id" : "testSet", "version" : 2, # 会自动增加 "members" : [ { "_id" : 0, "host" : "Node7:27017" }, { "_id" : 1, "host" : "192.168.10.5:27017" } ] }
连接从节点进行操作:
testSet:SECONDARY> show dbs admin (empty) local 1.078125GB testdb 0.203125GB # 可以看到已经复制了主节点的数据 testSet:SECONDARY> use testdb switched to db testdb testSet:SECONDARY> show collections Sun Mar 26 14:57:24.839 error: { "$err" : "not master and slaveOk=false", "code" : 13435 } at src/mongo/shell/query.js:128 # 报错信息提示:该节点不是主节点,也没有slaveOK,不允许查询;从节点需要运行slaveOK(),表示我已经准备好了,可以被查询 testSet:SECONDARY> rs.slaveOK() Sun Mar 26 15:05:49.818 TypeError: Object function () { return "try rs.help()"; } has no method 'slaveOK' testSet:SECONDARY> show collections students system.indexes testSet:SECONDARY> db.students.find() { "_id" : ObjectId("58d7658c369d571b4c80ee24"), "Name" : "xj", "Age" : 22 } testSet:SECONDARY> rs.isMaster() # 该节点是否为主节点 { "setName" : "testSet", "ismaster" : false, "secondary" : true, "hosts" : [ "192.168.10.5:27017", "Node7:27017" ], "primary" : "Node7:27017", "me" : "192.168.10.5:27017", "maxBsonObjectSize" : 16777216, "maxMessageSizeBytes" : 48000000, "localTime" : ISODate("2017-03-26T07:05:56.642Z"), "ok" : 1 }
让副本集中主节点“下台”:触发选举
testSet:PRIMARY> rs.stepDown() # 主节点“下台” Sun Mar 26 16:46:01.341 DBClientCursor::init call() failed Sun Mar 26 16:46:01.343 Error: error doing query: failed at src/mongo/shell/query.js:78 Sun Mar 26 16:46:01.343 trying reconnect to 127.0.0.1:27017 Sun Mar 26 16:46:01.344 reconnect 127.0.0.1:27017 ok testSet:SECONDARY> rs.status() # 10.5节点成了主节点 { "set" : "testSet", "date" : ISODate("2017-03-26T08:46:10Z"), "myState" : 2, "syncingTo" : "192.168.10.5:27017", "members" : [ { "_id" : 0, "name" : "Node7:27017", "health" : 1, "state" : 2, "stateStr" : "SECONDARY", "uptime" : 9652, "optime" : Timestamp(1490514359, 1), "optimeDate" : ISODate("2017-03-26T07:45:59Z"), "errmsg" : "syncing to: 192.168.10.5:27017", "self" : true }, { "_id" : 1, "name" : "192.168.10.5:27017", "health" : 1, "state" : 1, "stateStr" : "PRIMARY", "uptime" : 7393, "optime" : Timestamp(1490514359, 1), "optimeDate" : ISODate("2017-03-26T07:45:59Z"), "lastHeartbeat" : ISODate("2017-03-26T08:46:09Z"), "lastHeartbeatRecv" : ISODate("2017-03-26T08:46:09Z"), "pingMs" : 0, "syncingTo" : "Node7:27017" }, { "_id" : 2, "name" : "192.168.10.2:27017", "health" : 1, "state" : 2, "stateStr" : "SECONDARY", "uptime" : 3588, "optime" : Timestamp(1490514359, 1), "optimeDate" : ISODate("2017-03-26T07:45:59Z"), "lastHeartbeat" : ISODate("2017-03-26T08:46:10Z"), "lastHeartbeatRecv" : ISODate("1970-01-01T00:00:00Z"), "pingMs" : 0, "lastHeartbeatMessage" : "syncing to: 192.168.10.5:27017", "syncingTo" : "192.168.10.5:27017" } ], "ok" : 1 } testSet:SECONDARY>
查看副本集oplog的信息:
testSet:SECONDARY> db.printReplicationInfo() configured oplog size: 990MB log length start to end: 3782secs (1.05hrs) oplog first event time: Sun Mar 26 2017 14:42:57 GMT+0800 (CST) # 第一个事件产生的时间 oplog last event time: Sun Mar 26 2017 15:45:59 GMT+0800 (CST) now: Sun Mar 26 2017 16:49:07 GMT+0800 (CST)
3、选举
副本集的重新选举的影响条件:
心跳信息
优先级
默认为1,0优先级不能触发选举过程,高优先级的节点能触发选举让自己“上台”
optime
成员节点最近一次应用于本地oplog的条目的时间戳
网络连接
副本集出现分区时,“多数方“才能选举出主节点
网络分区
同上
选举机制:
触发选举的事件:
新副本集初始化时
从节点联系不到主节点时
主节点“下台”时
主节点收到stepDown()命令时
某从节点有更高的优先级且已经满足成主节点其它所有条件
主节点无法联系到副本集的“多数方”
4、修改副本集节点的配置
先使用一个变量保存当前副本集的配置:
testSet:SECONDARY> cfg=rs.conf() { "_id" : "testSet", "version" : 3, "members" : [ { "_id" : 0, "host" : "Node7:27017" }, { "_id" : 1, "host" : "192.168.10.5:27017" }, { "_id" : 2, "host" : "192.168.10.2:27017" } ] } testSet:SECONDARY> cfg { "_id" : "testSet", "version" : 3, "members" : [ { "_id" : 0, "host" : "Node7:27017" }, { "_id" : 1, "host" : "192.168.10.5:27017" }, { "_id" : 2, "host" : "192.168.10.2:27017" } ] }
设置节点的优先级:需要在主节点配置
testSet:PRIMARY> conf=rs.conf() { "_id" : "testSet", "version" : 3, "members" : [ { "_id" : 0, "host" : "Node7:27017" }, { "_id" : 1, "host" : "192.168.10.5:27017" }, { "_id" : 2, "host" : "192.168.10.2:27017" } ] } testSet:PRIMARY> conf.members[0].priority=3 3 testSet:PRIMARY> conf.members[1].priority=2 2 testSet:PRIMARY> conf { "_id" : "testSet", "version" : 3, "members" : [ { "_id" : 0, "host" : "Node7:27017", "priority" : 3 }, { "_id" : 1, "host" : "192.168.10.5:27017", "priority" : 2 }, { "_id" : 2, "host" : "192.168.10.2:27017" } ] } testSet:PRIMARY> rs.reconfig(conf) # 重新生成副本集配置文件 Sun Mar 26 17:51:47.440 DBClientCursor::init call() failed Sun Mar 26 17:51:47.446 trying reconnect to 127.0.0.1:27017 Sun Mar 26 17:51:47.451 reconnect 127.0.0.1:27017 ok reconnected to server after rs command (which is normal) testSet:SECONDARY> testSet:SECONDARY> rs.conf() { "_id" : "testSet", "version" : 4, "members" : [ { "_id" : 0, "host" : "Node7:27017", "priority" : 3 }, { "_id" : 1, "host" : "192.168.10.5:27017", "priority" : 2 }, { "_id" : 2, "host" : "192.168.10.2:27017" } ] }
添加仲裁节点:
在添加进副本集时,使用rs.addArb("hostportstr")
testSet:PRIMARY> rs.remove("192.168.10.2:27017") # 移除节点需要加端口号 Sun Mar 26 19:16:48.916 DBClientCursor::init call() failed Sun Mar 26 19:16:48.917 Error: error doing query: failed at src/mongo/shell/query.js:78 Sun Mar 26 19:16:48.918 trying reconnect to 127.0.0.1:27017 Sun Mar 26 19:16:48.919 reconnect 127.0.0.1:27017 ok testSet:PRIMARY> rs.status() { "set" : "testSet", "date" : ISODate("2017-03-26T11:16:57Z"), "myState" : 1, "members" : [ { "_id" : 0, "name" : "Node7:27017", "health" : 1, "state" : 1, "stateStr" : "PRIMARY", "uptime" : 3530, "optime" : Timestamp(1490527008, 1), "optimeDate" : ISODate("2017-03-26T11:16:48Z"), "self" : true }, { "_id" : 1, "name" : "192.168.10.5:27017", "health" : 1, "state" : 2, "stateStr" : "SECONDARY", "uptime" : 9, "optime" : Timestamp(1490527008, 1), "optimeDate" : ISODate("2017-03-26T11:16:48Z"), "lastHeartbeat" : ISODate("2017-03-26T11:16:56Z"), "lastHeartbeatRecv" : ISODate("2017-03-26T11:16:57Z"), "pingMs" : 0, "lastHeartbeatMessage" : "syncing to: Node7:27017", "syncingTo" : "Node7:27017" } ], "ok" : 1 } testSet:PRIMARY> rs.conf() { "_id" : "testSet", "version" : 8, "members" : [ { "_id" : 0, "host" : "Node7:27017", "priority" : 3 }, { "_id" : 1, "host" : "192.168.10.5:27017", "priority" : 2 } ] } testSet:PRIMARY> rs.addArb("192.168.10.2") # 添加仲裁节点 { "down" : [ "192.168.10.2:27017" ], "ok" : 1 } testSet:PRIMARY> rs.conf() { "_id" : "testSet", "version" : 9, "members" : [ { "_id" : 0, "host" : "Node7:27017", "priority" : 3 }, { "_id" : 1, "host" : "192.168.10.5:27017", "priority" : 2 }, { "_id" : 2, "host" : "192.168.10.2:27017", "arbiterOnly" : true } ] }
副本集中其它命令:
rs.freeze(secs):让节点在指定时间内不能被选举为主节点
rs.printSlaveReplicationInfo():查看从节点是否跟上了主节点,较低版本没有这个命令