目录
MongoDB复制是
将数据同步在多个服务器的过程。
复制提供了数据的冗余备份,并在多个服务器上存储数据副本,提高数据的
可用性,并可以保证数据的
安全性。
复制还允许你从硬件故障和服务中断中恢复数据。
一、什么是复制?
-
保障数据的安全性
-
数据的高可用性(24*7)
-
灾难恢复
-
无需停机维护(如备份、重建索引、压缩)
-
分布式读取数据,可配置读写分离,主节点负责写操作,从节点负责读操作,将读写压力分开,提高系统稳定性
二、Mongodb复制原理
Mongodb的复制至少需要两个节点:一个主节点,负责处理客户端请求,其余的都是从节点,负责复制主节点的数据。
Mongodb各个节点常见的搭配方式:一主一从、一主多从
主节点记录在其上的所有操作oplog(operation log,它被存储在MongoDB的 local 数据库中,oplog 中的每个文档都代表主节点上执行的一个操作。需要重点强调的是oplog只记录改变数据库状态的操作),从节点定期轮询主节点获取这些操作,然后对自己的数据副本执行这些操作,从而保证从节点的数据与主节点一致。
MongoDB复制结构图如下所示:
以上结构图中,客户端从主节点读取数据,在客户端写入数据到主节点时, 主节点与从节点进行数据交互保障数据的一致性
副本集概念:
MongoDB副本集(Replica Set)其实就是具有自动故障恢复功能的主从集群,和主从复制最大的区别就是在副本集中没有固定的“主节点;整个副本集会选出一个节点作为“主节点”,当其挂掉后,再在剩下的从节点中选举一个节点成为新的“主节点”,在副本集中总有一个主节点(primary)和一个或多个备份节点(secondary)。
MongoDB的副本集与我们常见的主从有所不同,主从在主机宕机后所有服务将停止,而副本集在主机宕机后,副本会接管主节点成为主节点,不会出现宕机的情况。
副本集特征:
-
N个节点的集群
-
任何节点都可以作为主节点
-
所有写入操作都在主节点上
-
自动故障转移
-
自动恢复
三、MongoDB副本集设置
1、启动一个名为rs0的Mongodb实例
mongod --port 27017 --replSet rs0
在Mongo客户端使用命令rs.initiate()来启动一个新的副本集。我们可以使用rs.conf()来查看副本集的配置; 查看副本集状态使用 rs.status() 命令
2、多台服务器--副本集添加新成员
rs.add(HOST_NAME:PORT)
MongoDB中你只能通过主节点将Mongo服务添加到副本集中, 判断当前运行的Mongo服务是否为主节点可以使用命令rs.isMaster() 。
rs.remove(HOST_NAME:PORT) 从副本集移除成员
rs.assArb(HOST_NAME:PORT) 向副本集添加仲裁
db.printSecondaryReplicationInfo() 查看备份节点的复制信息

四、docker中部署MongoDB副本集
4.1、基本信息
服务器地址:192.168.11.45
副本集名称:rs0
容器节点及端口映射:
mongo1 27018:27017
mongo2 27019:27017
mongo3 27020:27017
4.2、启动三个节点
docker run -d -p 27018:27017 --name mongo1 mongo --replSet "rs0"
docker run -d -p 27019:27017 --name mongo2 mongo --replSet "rs0"
docker run -d -p 27020:27017 --name mongo3 mongo --replSet "rs0"
4.3、选择在主节点进行副本集配置
docker exec -it 65 mongo # 进入主节点mongo
# 进行副本集配置
> var config={
_id:"rs0",
members:[
{_id:0,host:"192.168.11.45:27018"},
{_id:1,host:"192.168.11.45:27019"},
{_id:2,host:"192.168.11.45:27020"}
]};
# 启动新的副本集
> rs.initiate(config)
{ "ok" : 1 }
此时命令提示符已经发生变化,由原来的 > 变成了 rs:SECONDARY>
# 查看副本集配置信息
rs0:SECONDARY> rs.conf()
{
"_id" : "rs0", # 副本集名称
"version" : 1,
"term" : 1,
"protocolVersion" : NumberLong(1),
"writeConcernMajorityJournalDefault" : true,
"members" : [
{
"_id" : 0,
"host" : "192.168.11.45:27018",
"arbiterOnly" : false,
"buildIndexes" : true,
"hidden" : false, # 是否为隐藏节点
"priority" : 1, # 优先级
"tags" : {
},
"slaveDelay" : NumberLong(0), # 延迟,单位秒
"votes" : 1 # 1:可以投票,0:不能投票
},
{
"_id" : 1,
"host" : "192.168.11.45:27019",
"arbiterOnly" : false,
"buildIndexes" : true,
"hidden" : false,
"priority" : 1,
"tags" : {
},
"slaveDelay" : NumberLong(0),
"votes" : 1
},
{
"_id" : 2,
"host" : "192.168.11.45:27020",
"arbiterOnly" : false,
"buildIndexes" : true,
"hidden" : false,
"priority" : 1,
"tags" : {
},
"slaveDelay" : NumberLong(0),
"votes" : 1
}
],
"settings" : {
"chainingAllowed" : true,
"heartbeatIntervalMillis" : 2000,
"heartbeatTimeoutSecs" : 10,
"electionTimeoutMillis" : 10000,
"catchUpTimeoutMillis" : -1,
"catchUpTakeoverDelayMillis" : 30000,
"getLastErrorModes" : {
},
"getLastErrorDefaults" : {
"w" : 1,
"wtimeout" : 0
},
"replicaSetId" : ObjectId("616fb8d3123d0cb36cfa6d4e")
}
}
# 查看副本集状态
rs0:PRIMARY> rs.status()
{
"set" : "rs0",
"date" : ISODate("2021-10-20T06:37:05.142Z"),
"myState" : 1,
"term" : NumberLong(1),
"syncSourceHost" : "",
"syncSourceId" : -1,
"heartbeatIntervalMillis" : NumberLong(2000),
"majorityVoteCount" : 2,
"writeMajorityCount" : 2,
"votingMembersCount" : 3,
"writableVotingMembersCount" : 3,
"optimes" : {
"lastCommittedOpTime" : {
"ts" : Timestamp(1634711824, 1),
"t" : NumberLong(1)
},
"lastCommittedWallTime" : ISODate("2021-10-20T06:37:04.306Z"),
"readConcernMajorityOpTime" : {
"ts" : Timestamp(1634711824, 1),
"t" : NumberLong(1)
},
"readConcernMajorityWallTime" : ISODate("2021-10-20T06:37:04.306Z"),
"appliedOpTime" : {
"ts" : Timestamp(1634711824, 1),
"t" : NumberLong(1)
},
"durableOpTime" : {
"ts" : Timestamp(1634711824, 1),
"t" : NumberLong(1)
},
"lastAppliedWallTime" : ISODate("2021-10-20T06:37:04.306Z"),
"lastDurableWallTime" : ISODate("2021-10-20T06:37:04.306Z")
},
"lastStableRecoveryTimestamp" : Timestamp(1634711774, 3),
"electionCandidateMetrics" : {
"lastElectionReason" : "electionTimeout",
"lastElectionDate" : ISODate("2021-10-20T06:36:14.279Z"),
"electionTerm" : NumberLong(1),
"lastCommittedOpTimeAtElection" : {
"ts" : Timestamp(0, 0),
"t" : NumberLong(-1)
},
"lastSeenOpTimeAtElection" : {
"ts" : Timestamp(1634711763, 1),
"t" : NumberLong(-1)
},
"numVotesNeeded" : 2,
"priorityAtElection" : 1,
"electionTimeoutMillis" : NumberLong(10000),
"numCatchUpOps" : NumberLong(0),
"newTermStartDate" : ISODate("2021-10-20T06:36:14.297Z"),
"wMajorityWriteAvailabilityDate" : ISODate("2021-10-20T06:36:15.001Z")
},
"members" : [
{
"_id" : 0,
"name" : "192.168.11.45:27018",
"health" : 1,
"state" : 1,
"stateStr" : "PRIMARY", # 节点类型
"uptime" : 683,
"optime" : {
"ts" : Timestamp(1634711824, 1),
"t" : NumberLong(1)
},
"optimeDate" : ISODate("2021-10-20T06:37:04Z"),
"syncSourceHost" : "",
"syncSourceId" : -1,
"infoMessage" : "",
"electionTime" : Timestamp(1634711774, 1),
"electionDate" : ISODate("2021-10-20T06:36:14Z"),
"configVersion" : 1,
"configTerm" : 1,
"self" : true,
"lastHeartbeatMessage" : ""
},
{
"_id" : 1,
"name" : "192.168.11.45:27019",
"health" : 1,
"state" : 2,
"stateStr" : "SECONDARY", # 节点类型
"uptime" : 61,
"optime" : {
"ts" : Timestamp(1634711814, 1),
"t" : NumberLong(1)
},
"optimeDurable" : {
"ts" : Timestamp(1634711814, 1),
"t" : NumberLong(1)
},
"optimeDate" : ISODate("2021-10-20T06:36:54Z"),
"optimeDurableDate" : ISODate("2021-10-20T06:36:54Z"),
"lastHeartbeat" : ISODate("2021-10-20T06:37:04.284Z"),
"lastHeartbeatRecv" : ISODate("2021-10-20T06:37:03.289Z"),
"pingMs" : NumberLong(0),
"lastHeartbeatMessage" : "",
"syncSourceHost" : "192.168.11.45:27018",
"syncSourceId" : 0,
"infoMessage" : "",
"configVersion" : 1,
"configTerm" : 1
},
{
"_id" : 2,
"name" : "192.168.11.45:27020",
"health" : 1,
"state" : 2,
"stateStr" : "SECONDARY", # 节点类型
"uptime" : 61,
"optime" : {
"ts" : Timestamp(1634711814, 1),
"t" : NumberLong(1)
},
"optimeDurable" : {
"ts" : Timestamp(1634711814, 1),
"t" : NumberLong(1)
},
"optimeDate" : ISODate("2021-10-20T06:36:54Z"),
"optimeDurableDate" : ISODate("2021-10-20T06:36:54Z"),
"lastHeartbeat" : ISODate("2021-10-20T06:37:04.284Z"),
"lastHeartbeatRecv" : ISODate("2021-10-20T06:37:03.289Z"),
"pingMs" : NumberLong(0),
"lastHeartbeatMessage" : "",
"syncSourceHost" : "192.168.11.45:27018",
"syncSourceId" : 0,
"infoMessage" : "",
"configVersion" : 1,
"configTerm" : 1
}
],
"ok" : 1,
"$clusterTime" : {
"clusterTime" : Timestamp(1634711824, 1),
"signature" : {
"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
"keyId" : NumberLong(0)
}
},
"operationTime" : Timestamp(1634711824, 1)
}
4.4、同步测试
在主节点mongo1-27018上新增一条记录,可在mongo2-27019、mongo3-27020上看到

五、节点类型
Mongodb的节点类型:主节点(Master或者Primary)、副本节点(Slave或者Secondary)、仲裁节点、Secondary-Only节点、Hidden节点、Delayed节点、Non-Voting节点
-
仲裁节点:不存储数据,只负责故障转移的群体投票,这样减少了数据复制的压力;
-
Secondary-Only:不能成为primary节点,只能作为secondary副本节点,防止部分性能不高的节点成为主节点;
-
Hidden:不能被客户端指定Ip引用,也不能设置为主节点,但是可以投票,一般用于备份数据;
-
Delayed:可以指定一个时间延迟从primary节点同步数据,主要用于备份数据。如果实时同步,误删除数据马上同步到从节点。所以延迟复制主要用于避免用户错误。
-
Non-Voting:没有选举权的secondary节点,纯粹的备份数据节点。
六、更改节点优先级
修改节点的优先级可以触发从新选举,这样可以人工指定主节点。
使用如下命令,在主节点mongo1 192.168.11.45:27018操作,将mongo2 192.168.11.45:27019 提升为Master:
cfg = rs.conf();
cfg.members[0].priority = 1
cfg.members[1].priority = 10
cfg.members[2].priority = 1
rs.reconfig(cfg);
然后查看集群状态:rs.status()
七、设置隐藏节点
隐藏节点可以在选举中投票,但是不能被客户端引用,也不能成为主节点。也就是说这个节点不能用于读写分离的场景。
使用如下命令,在主节点mongo1 192.168.11.45:27018操作,将mongo2 192.168.11.45:27019 设置为隐藏节点:
注意,只有优先级为0的成员才能设置为隐藏节点。如果设置优先级不为0的节点为隐藏节点,则报错。
cfg = rs.conf();
cfg.members[0].priority = 1
cfg.members[1].priority = 0 # 先设置优先级为0
cfg.members[2].priority = 1
cfg.members[1].hidden = 1
rs.reconfig(cfg);
然后查看集群状态:rs.status(),该节点还是SECONDARY状态,但是通过rs.isMaster()和rs.conf()可以看到这个节点的变化:
-
rs.isMaster()的hosts中192.168.11.45:27019节点已经不可见;
-
rs.conf()显示该节点状态为hidden
八、设置仲裁节点
仲裁节点不存储数据,只是用于投票。所以仲裁节点对于服务器负载很低。
节点一旦以仲裁者的身份加入集群,他就只能是仲裁者,无法将仲裁者配置为非仲裁者,反之也是一样。
另外一个集群最多只能使用一个仲裁者,额外的仲裁者拖累选举新Master节点的速度,同时也不能提供更好的数据安全性。
初始化集群时,设置仲裁者的配置如下:
> var config={
_id:"rs0",
members:[
{_id:0,host:"192.168.11.45:27018"},
{_id:1,host:"192.168.11.45:27019",arbiterOnly:true},
{_id:2,host:"192.168.11.45:27020"}
]};
使用仲裁者主要是因为MongoDB副本集需要奇数成员,而又没有足够服务器的情况。在服务器充足的情况下,不应该使用仲裁者节点。
九、设置延迟复制节点
延迟节点的优先级必须为0.这个和hidden节点是一样的。。
cfg = rs.conf();
cfg.members[1].priority = 0 # 先设置优先级为0
cfg.members[1].slaveDelay = 3600 # 单位秒
rs.reconfig(cfg);
在192.168.11.45:27018节点删除一个集合所有数据,模拟人为失误 db.users.remove({})
在192.168.11.45:27020节点查看,发现数据已经完全丢失;
而在192.168.11.45:27019延迟节点,可以看到因为延迟复制的缘故,数据还在。
这个时候千万不要提升延迟节点的优先级。因为这样他会立即应用原主节点的所有操作,并成为新的主节点。这样误操作就同步到了延迟节点。
应该进行如下操作:
-
关闭副本集中其他的成员,除了延迟节点。
-
删除其他成员数据目录中的所有数据。确保每个其他成员的数据目录都是空的(除了延迟节点)
-
重启其他成员,他们会自动从延迟节点中恢复数据。
十、设置Secondary-Only节点
Priority为0的节点永远不能成为主节点,所以设置Secondary-only节点只需要将其priority设置为0.
十一、设置Non-Voting节点
cfg
= rs
.conf
();
cfg
.members
[
1
].
votes
=
0
# 设置不能投票
rs
.reconfig
(cfg
);
十二、副本集成员状态
副本集成员状态指的是rs.status()的stateStr字段.
-
STARTUP:刚加入到复制集中,配置还未加载
-
STARTUP2:配置已加载完,初始化状态
-
RECOVERING:正在恢复,不适用读
-
ARBITER: 仲裁者
-
DOWN:节点不可到达
-
UNKNOWN:未获取其他节点状态而不知是什么状态,一般发生在只有两个成员的架构,脑裂
-
REMOVED:移除复制集
-
ROLLBACK:数据回滚,在回滚结束时,转移到RECOVERING或SECONDARY状态
-
FATAL:出错。查看日志grep “replSet FATAL”找出错原因,重新做同步
-
PRIMARY:主节点
-
SECONDARY:备份节点