MongoDB 主从复制是将数据同步到多个 MongoDB 服务器的过程。复制提供了数据的冗余备份,并在多个服务器上存储数据副本,提高了数据的可用性, 并可以保证数据的安全性。复制还允许您从硬件故障和服务中断中恢复数据。
主从复制是 MongoDB 最常用的复制方式,也是一个简单的数据库同步备份的集群技术。这种方式很灵活,可用于备份、故障恢复、读扩展等。
MongoDB 的主从复制至少需要两个节点。其中一个是主节点(Primary),负责处理客户端请求,其余的都是从节点(Secondary),负责复制主节点上的数据。
MongoDB 节点常见的搭配方式为:
一主一从
一主多从
主节点记录在其上的所有操作日志,从节点定期轮询主节点获取这些操作。然后对自己的数据副本执行这些操作,从而保证从节点的数据与主节点一致。MongoDB 主从复制结构图如下所示:

以上结构图中,客户端从主节点(Primary)读取数据,在客户端写入数据到主节点(Primary)时, 主节点与从节点进行数据交互保障数据的一致性。
下面将介绍一个实例来了解 MongoDB 的主从复制。步骤如下:
在开始之前,我们准备了 3 个 CentOS6.4 系统(均为VMWare虚拟机)。服务器IP地址和主机名分别如下:
主节点(S1):192.168.238.201
从节点(S2):192.168.238.202
从节点(S3):192.168.238.203
在开始启动 MongoDB 时,需要先安装 MongoDB,采用 mongodb-org-unstable-server-4.3.3-1.el6.x86_64.rpm 包进行安装。然后分别去配置每个 MongoDB 服务。
a、配置和启动主节点S1
主节点的配置信息如下:
systemLog: destination: file logAppend: true path: /var/log/mongodb/mongod.log storage: dbPath: /var/lib/mongo journal: enabled: true processManagement: fork: true pidFilePath: /var/run/mongodb/mongod.pid timeZoneInfo: /usr/share/zoneinfo net: port: 27017 bindIp: localhost,192.168.238.201 # 配置复制集 replication: replSetName: rs0
启动主节点,如下:
[root@S1 lib]# service mongod start Starting mongod: [ OK ]
b、配置和启动从节点S2
主节点的配置信息如下:
systemLog: destination: file logAppend: true path: /var/log/mongodb/mongod.log storage: dbPath: /var/lib/mongo journal: enabled: true processManagement: fork: true pidFilePath: /var/run/mongodb/mongod.pid timeZoneInfo: /usr/share/zoneinfo net: port: 27017 bindIp: localhost,192.168.238.202 # 配置复制集 replication: replSetName: rs0
启动主节点,如下:
[root@S2 lib]# service mongod start Starting mongod: [ OK ]
c、配置和启动从节点S3
主节点的配置信息如下:
systemLog: destination: file logAppend: true path: /var/log/mongodb/mongod.log storage: dbPath: /var/lib/mongo journal: enabled: true processManagement: fork: true pidFilePath: /var/run/mongodb/mongod.pid timeZoneInfo: /usr/share/zoneinfo net: port: 27017 bindIp: localhost,192.168.238.203 # 配置复制集 replication: replSetName: rs0
启动主节点,如下:
[root@S3 lib]# service mongod start Starting mongod: [ OK ]
(3)初始化MongoDB复制集合
使用 mongo 工具连接到主节点,如下:
mongo --host 192.168.238.201 --port 27017
在 Mongo 客户端使用命令 rs.initiate() 来启动一个新的副本集。如下:
> rs.initiate()
{
"info2" : "no configuration specified. Using a default configuration for the set",
"me" : "192.168.238.201:27017",
"ok" : 1,
"$clusterTime" : {
"clusterTime" : Timestamp(1530620665, 1),
"signature" : {
"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
"keyId" : NumberLong(0)
}
},
"operationTime" : Timestamp(1530620665, 1)
}
rs0:SECONDARY>
rs0:PRIMARY>上面,ok 为 1,表示启动成功。启动成功后命令提示符为“rs0:SECONDARY>”按一下回车键将看见“rs0:PRIMARY>”。
此时,我们就可以使用 rs.add() 指令为 rs0 复制集添加成员。如下:
rs0:PRIMARY> rs.add("192.168.238.202:27017")
{
"ok" : 1,
"$clusterTime" : {
"clusterTime" : Timestamp(1530620749, 1),
"signature" : {
"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
"keyId" : NumberLong(0)
}
},
"operationTime" : Timestamp(1530620749, 1)
}
rs0:PRIMARY> rs.add("192.168.238.203:27017")
{
"ok" : 1,
"$clusterTime" : {
"clusterTime" : Timestamp(1530620753, 1),
"signature" : {
"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
"keyId" : NumberLong(0)
}
},
"operationTime" : Timestamp(1530620753, 1)
}上面向 rs0 复制集中添加了两个成员,分别为 192.168.238.202 和 192.168.238.203。
你可以使用 rs.status() 指令查看 mongodb 状态。如下:
rs0:PRIMARY> rs.status()
{
"set" : "rs0",
"date" : ISODate("2018-07-03T12:26:04.252Z"),
"myState" : 1,
"term" : NumberLong(1),
"syncSourceHost" : "",
"syncSourceId" : -1,
"heartbeatIntervalMillis" : NumberLong(2000),
"majorityVoteCount" : 2,
"writeMajorityCount" : 2,
"optimes" : {
"lastCommittedOpTime" : {
"ts" : Timestamp(1530620753, 1),
"t" : NumberLong(1)
},
"lastCommittedWallTime" : ISODate("2018-07-03T12:25:53.165Z"),
"readConcernMajorityOpTime" : {
"ts" : Timestamp(1530620753, 1),
"t" : NumberLong(1)
},
"readConcernMajorityWallTime" : ISODate("2018-07-03T12:25:53.165Z"),
"appliedOpTime" : {
"ts" : Timestamp(1530620753, 1),
"t" : NumberLong(1)
},
"durableOpTime" : {
"ts" : Timestamp(1530620753, 1),
"t" : NumberLong(1)
},
"lastAppliedWallTime" : ISODate("2018-07-03T12:25:53.165Z"),
"lastDurableWallTime" : ISODate("2018-07-03T12:25:53.165Z")
},
"lastStableRecoveryTimestamp" : Timestamp(1530620716, 1),
"electionCandidateMetrics" : {
"lastElectionReason" : "electionTimeout",
"lastElectionDate" : ISODate("2018-07-03T12:24:25.867Z"),
"electionTerm" : NumberLong(1),
"lastCommittedOpTimeAtElection" : {
"ts" : Timestamp(0, 0),
"t" : NumberLong(-1)
},
"lastSeenOpTimeAtElection" : {
"ts" : Timestamp(1530620665, 1),
"t" : NumberLong(-1)
},
"numVotesNeeded" : 1,
"priorityAtElection" : 1,
"electionTimeoutMillis" : NumberLong(10000),
"newTermStartDate" : ISODate("2018-07-03T12:24:26.881Z"),
"wMajorityWriteAvailabilityDate" : ISODate("2018-07-03T12:24:26.904Z")
},
"members" : [
{
"_id" : 0,
"name" : "192.168.238.201:27017",
"health" : 1,
"state" : 1,
"stateStr" : "PRIMARY",
"uptime" : 159,
"optime" : {
"ts" : Timestamp(1530620753, 1),
"t" : NumberLong(1)
},
"optimeDate" : ISODate("2018-07-03T12:25:53Z"),
"syncSourceHost" : "",
"syncSourceId" : -1,
"infoMessage" : "could not find member to sync from",
"electionTime" : Timestamp(1530620665, 2),
"electionDate" : ISODate("2018-07-03T12:24:25Z"),
"configVersion" : 3,
"self" : true,
"lastHeartbeatMessage" : ""
},
{
"_id" : 1,
"name" : "192.168.238.202:27017",
"health" : 1,
"state" : 2,
"stateStr" : "SECONDARY",
"uptime" : 14,
"optime" : {
"ts" : Timestamp(1530620753, 1),
"t" : NumberLong(1)
},
"optimeDurable" : {
"ts" : Timestamp(1530620753, 1),
"t" : NumberLong(1)
},
"optimeDate" : ISODate("2018-07-03T12:25:53Z"),
"optimeDurableDate" : ISODate("2018-07-03T12:25:53Z"),
"lastHeartbeat" : ISODate("2018-07-03T12:26:03.170Z"),
"lastHeartbeatRecv" : ISODate("2018-07-03T12:26:03.693Z"),
"pingMs" : NumberLong(0),
"lastHeartbeatMessage" : "",
"syncSourceHost" : "192.168.238.201:27017",
"syncSourceId" : 0,
"infoMessage" : "",
"configVersion" : 3
},
{
"_id" : 2,
"name" : "192.168.238.203:27017",
"health" : 1,
"state" : 2,
"stateStr" : "SECONDARY",
"uptime" : 11,
"optime" : {
"ts" : Timestamp(1530620753, 1),
"t" : NumberLong(1)
},
"optimeDurable" : {
"ts" : Timestamp(1530620753, 1),
"t" : NumberLong(1)
},
"optimeDate" : ISODate("2018-07-03T12:25:53Z"),
"optimeDurableDate" : ISODate("2018-07-03T12:25:53Z"),
"lastHeartbeat" : ISODate("2018-07-03T12:26:03.215Z"),
"lastHeartbeatRecv" : ISODate("2018-07-03T12:26:03.760Z"),
"pingMs" : NumberLong(6),
"lastHeartbeatMessage" : "",
"syncSourceHost" : "192.168.238.201:27017",
"syncSourceId" : 0,
"infoMessage" : "",
"configVersion" : 3
}
],
"ok" : 1,
"$clusterTime" : {
"clusterTime" : Timestamp(1530620753, 1),
"signature" : {
"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
"keyId" : NumberLong(0)
}
},
"operationTime" : Timestamp(1530620753, 1)
}上面文档中的 members 字段是一个数组,保存了所有复制集中的节点信息。
使用 mongo 客户端连接到主节点,插入数据。如下:
# 连接到主节点
mongo --host 192.168.238.201 --port 27017
# 向 test 数据库中的 test 集合插入一个文档
rs0:PRIMARY> db.test.insert({name:"test", version:"1.1.1"})
WriteResult({ "nInserted" : 1 })再次,使用 mongo 客户端连接到从节点 192.168.238.202。如下:
# 连接到从节点
mongo --host 192.168.238.202 --port 27017
# 执行后允许进行读取操作
rs0:SECONDARY> rs.slaveOk()
# 显示所有的数据库
rs0:SECONDARY> show dbs
admin 0.000GB
config 0.000GB
local 0.000GB
test 0.000GB
# 切换到 test 数据库
rs0:SECONDARY> use test
switched to db test
# 查询 test 集合中的文档
rs0:SECONDARY> db.test.find()
{ "_id" : ObjectId("5e5b884159de83945fb5d3c6"), "name" : "test", "version" : "1.1.1" }
rs0:SECONDARY>连接到从节点 192.168.238.203,如下:
# 连接到从节点
mongo --host 192.168.238.203 --port 27017
# 执行后允许进行读取操作
rs0:SECONDARY> rs.slaveOk()
# 显示所有的数据库
rs0:SECONDARY> show dbs
admin 0.000GB
config 0.000GB
local 0.000GB
test 0.000GB
# 切换到 test 数据库
rs0:SECONDARY> use test
switched to db test
# 显示 test 数据库中的所有集合
rs0:SECONDARY> show collections
test
# 查询 test 集合中的文档
rs0:SECONDARY> db.test.find()
{ "_id" : ObjectId("5e5b884159de83945fb5d3c6"), "name" : "test", "version" : "1.1.1" }如果你在执行其他操作之前没有执行 rs.slaveOk() 命令,则会抛出如下错误:
rs0:SECONDARY> show dbs
2020-03-01T18:27:49.612+0800 E QUERY [js] uncaught exception: Error: listDatabases failed:{
"topologyVersion" : {
"processId" : ObjectId("5b381d08b1311857257fef9f"),
"counter" : NumberLong(3)
},
"operationTime" : Timestamp(1530647569, 1),
"ok" : 0,
"errmsg" : "not master and slaveOk=false",
"code" : 13435,
"codeName" : "NotMasterNoSlaveOk",
"$clusterTime" : {
"clusterTime" : Timestamp(1530647569, 1),
"signature" : {
"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
"keyId" : NumberLong(0)
}
}
} :
_getErrorWithCode@src/mongo/shell/utils.js:25:13
Mongo.prototype.getDBs/<@src/mongo/shell/mongo.js:135:19
Mongo.prototype.getDBs@src/mongo/shell/mongo.js:87:12
shellHelper.show@src/mongo/shell/utils.js:906:13
shellHelper@src/mongo/shell/utils.js:790:15
@(shellhelp2):1:1如果你在从节点中执行insert命令,则抛出如下错误:
rs0:SECONDARY> db.test.insert({name:"123"})
WriteCommandError({
"topologyVersion" : {
"processId" : ObjectId("5b381d08b1311857257fef9f"),
"counter" : NumberLong(3)
},
"operationTime" : Timestamp(1530647929, 1),
"ok" : 0,
"errmsg" : "not master",
"code" : 10107,
"codeName" : "NotMaster",
"$clusterTime" : {
"clusterTime" : Timestamp(1530647929, 1),
"signature" : {
"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
"keyId" : NumberLong(0)
}
}
})注意:主节点才能插入和读取数据,从节点只能读取数据。