一、副本集 Replica Sets
1.1 简介
MongoDB 中的副本集(Replica Set)是一组维护相同数据集的 mongod 服务。 副本集可提供冗余和高可用性,是所有生产部署的基础。
也可以说,副本集类似于有自动故障恢复功能的主从集群。通俗的讲就是用多台机器进行同一数据的异步同步,从而使多台机器拥有同一数据的多个副本,并且当主库 down 掉时在不需要用户干预的情况下自动切换其他备份服务器做主库。而且还可以利用副本服务器做只读服务器,实现读写分离,提高负载。
1):冗余和数据可用性
复制提供冗余并提高数据可用性。 通过在不同数据库服务器上提供多个数据副本,复制可提供一定级别的容错功能,以防止丢失单个数据库服务器。
在某些情况下,复制可以提供增加的读取性能,因为客户端可以将读取操作发送到不同的服务上, 在不同数据中心维护数据副本可以增加分布式应用程序的数据位置和可用性。 您还可以为专用目的维护其他副本,例如灾难恢复,报告或备份。
2):MongoDB 中的复制
副本集是一组维护相同数据集的 mongod 实例。 副本集包含多个数据承载节点和可选的一个仲裁节点。在承载数据的节点中,一个且仅一个成员被视为主节点,而其他节点被视为次要(从)节点。
主节点接收所有写操作。 副本集只能有一个主要的写入; 虽然在某些情况下,另一个 mongod 实例可能暂时认为自己也是主要的。主要记录其操作日志中的数据集的所有更改,即 oplog。
辅助(副本)节点复制主节点的 oplog 并将操作应用于其数据集,以使辅助节点的数据集反映主节点的数据集。 如果主要人员不在,则符合条件的中学将举行选举以选出新的主要人员。
3):主从复制和副本集区别
主从集群和副本集最大的区别就是副本集没有固定的 “主节点”;整个集群会选出一个“主节点”,当其挂掉后,又在剩下的从节点中选中其他节点为 “主节点”,副本集总有一个活跃点(主、primary) 和一个或多个备份节点 (从、secondary)。
1.2 副本集三个角色
副本集有两种类型三种角色。
两种类型:
主节点(Primary)类型:数据操作的主要连接点,可读写。
次要(辅助、从)节点(Secondaries)类型:数据冗余备份节点,可以读或选举。
三种角色:
主要成员(Primary):主要接收所有写操作。就是主节点。
副本成员(Replicate):从主节点通过复制操作以维护相同的数据集,即备份数据,不可写操作,但可以读操作(但需要配置)。是默认的一种从节点类型。
仲裁者(Arbiter):不保留任何数据的副本,只具有投票选举作用。当然也可以将仲裁服务器维护为副本集的一部分,即副本成员同时也可以是仲裁者。也是一种从节点类型。
关于仲裁者的额外说明:
您可以将额外的 mongod 实例添加到副本集作为仲裁者。 仲裁者不维护数据集。 仲裁者的目的是通过响应其他副本集成员的心跳和选举请求来维护副本集中的仲裁。 因为它们不存储数据集,所以仲裁器可以是提供副本集仲裁功能的好方法,其资源成本比具有数据集的全功能副本集成员更便宜。
如果您的副本集具有偶数个成员,请添加仲裁者以获得主要选举中的“大多数”投票。 仲裁者不需要专用硬件。
仲裁者将永远是仲裁者,而主要人员可能会退出并成为次要人员,而次要人员可能成为选举期间的主要人员。
如果你的副本+主节点的个数是偶数,建议加一个仲裁者,形成奇数,容易满足大多数的投票。
如果你的副本+主节点的个数是奇数,可以不加仲裁者。
1.3 副本集架构目标
一主一副本一仲裁,架构图如下所示:
1.4 副本集的创建
1.4.1 创建主节点
建立存放数据和日志的目录
# 存放日志信息
mkdir -p /usr/local/mongodb/replica_sets/myrs_27017/log
# 存放数据信息
mkdir -p /usr/local/mongodb/replica_sets/myrs_27017/data/db
新建配置信息
vim /usr/local/mongodb/replica_sets/myrs_27017/mongod.conf
内容如下:
systemLog:
# MongoDB发送所有日志输出的目标指定为文件
destination: file
# mongod或mongos应向其发送所有诊断日志记录信息的日志文件的路径
path: "/usr/local/mongodb/replica_sets/myrs_27017/log/mongod.log"
# 当mongos或mongod实例重新启动时,mongos或mongod会将新条目附加到现有日志文件的末尾。
logAppend: true
storage:
# mongod实例存储其数据的目录。storage.dbPath设置仅适用于mongod。
dbPath: "/usr/local/mongodb/replica_sets/myrs_27017/data/db"
journal:
# 启用或禁用持久性日志以确保数据文件保持有效和可恢复。
enabled: true
processManagement:
# 启用在后台运行mongos或mongod进程的守护进程模式。
fork: true
# 指定用于保存mongos或mongod进程的进程ID的文件位置,其中mongos或mongod将写入其PID
pidFilePath: "/usr/local/mongodb/replica_sets/myrs_27017/log/mongod.pid"
net:
# 服务实例绑定所有IP,有副作用,副本集初始化的时候,节点名字会自动设置为本地域名,而不是ip
#bindIpAll: true
# 服务实例绑定的IP
bindIp: localhost,192.168.229.154
#bindIp
# 绑定的端口
port: 27017
replication:
# 副本集的名称
replSetName: myrs
启动节点服务:
[root@node1 mongodb]# /usr/local/mongodb/bin/mongod -f /usr/local/mongodb/replica_sets/myrs_27017/mongod.conf
about to fork child process, waiting until server is ready for connections.
forked process: 42243
child process started successfully, parent exiting
1.4.2 创建副本节点
建立存放数据和日志的目录
# 存放日志信息
mkdir -p /usr/local/mongodb/replica_sets/myrs_27018/log
# 存放数据信息
mkdir -p /usr/local/mongodb/replica_sets/myrs_27018/data/db
新建配置信息
vim /usr/local/mongodb/replica_sets/myrs_27018/mongod.conf
内容如下:
systemLog:
# MongoDB发送所有日志输出的目标指定为文件
destination: file
# mongod或mongos应向其发送所有诊断日志记录信息的日志文件的路径
path: "/usr/local/mongodb/replica_sets/myrs_27018/log/mongod.log"
# 当mongos或mongod实例重新启动时,mongos或mongod会将新条目附加到现有日志文件的末尾。
logAppend: true
storage:
# mongod实例存储其数据的目录。storage.dbPath设置仅适用于mongod。
dbPath: "/usr/local/mongodb/replica_sets/myrs_27018/data/db"
journal:
# 启用或禁用持久性日志以确保数据文件保持有效和可恢复。
enabled: true
processManagement:
# 启用在后台运行mongos或mongod进程的守护进程模式。
fork: true
# 指定用于保存mongos或mongod进程的进程ID的文件位置,其中mongos或mongod将写入其PID
pidFilePath: "/usr/local/mongodb/replica_sets/myrs_27018/log/mongod.pid"
net:
# 服务实例绑定所有IP,有副作用,副本集初始化的时候,节点名字会自动设置为本地域名,而不是ip
#bindIpAll: true
# 服务实例绑定的IP
bindIp: localhost,192.168.229.154
#bindIp
# 绑定的端口
port: 27018
replication:
# 副本集的名称
replSetName: myrs
启动节点服务:
[root@node1 mongodb]# /usr/local/mongodb/bin/mongod -f /usr/local/mongodb/replica_sets/myrs_27018/mongod.conf
about to fork child process, waiting until server is ready for connections.
forked process: 45029
child process started successfully, parent exiting
1.4.3 创建仲裁节点
建立存放数据和日志的目录
# 存放日志信息
mkdir -p /usr/local/mongodb/replica_sets/myrs_27019/log
# 存放数据信息
mkdir -p /usr/local/mongodb/replica_sets/myrs_27019/data/db
新建配置信息
vim /usr/local/mongodb/replica_sets/myrs_27019/mongod.conf
内容如下:
systemLog:
# MongoDB发送所有日志输出的目标指定为文件
destination: file
# mongod或mongos应向其发送所有诊断日志记录信息的日志文件的路径
path: "/usr/local/mongodb/replica_sets/myrs_27019/log/mongod.log"
# 当mongos或mongod实例重新启动时,mongos或mongod会将新条目附加到现有日志文件的末尾。
logAppend: true
storage:
# mongod实例存储其数据的目录。storage.dbPath设置仅适用于mongod。
dbPath: "/usr/local/mongodb/replica_sets/myrs_27019/data/db"
journal:
# 启用或禁用持久性日志以确保数据文件保持有效和可恢复。
enabled: true
processManagement:
# 启用在后台运行mongos或mongod进程的守护进程模式。
fork: true
# 指定用于保存mongos或mongod进程的进程ID的文件位置,其中mongos或mongod将写入其PID
pidFilePath: "/usr/local/mongodb/replica_sets/myrs_27019/log/mongod.pid"
net:
# 服务实例绑定所有IP,有副作用,副本集初始化的时候,节点名字会自动设置为本地域名,而不是ip
#bindIpAll: true
# 服务实例绑定的IP
bindIp: localhost,192.168.229.154
#bindIp
# 绑定的端口
port: 27019
replication:
# 副本集的名称
replSetName: myrs
启动节点服务:
[root@node1 mongodb]# /usr/local/mongodb/bin/mongod -f /usr/local/mongodb/replica_sets/myrs_27019/mongod.conf
about to fork child process, waiting until server is ready for connections.
forked process: 46093
child process started successfully, parent exiting
1.4.4 初始化配置副本集和主节点
使用客户端命令连接任意一个节点,但这里尽量要连接主节点 (27017节点):
/usr/local/mongodb/bin/mongo --host=192.168.229.154 --port=27017
结果,连接上之后,很多命令无法使用,比如 show dbs 等,如下所示,必须初始化副本集才可以。
# 使用默认的配置来初始化副本集
rs.initiate()
# "ok":1,说明创建成功。
# 命令行提示符发生变化,变成了一个从节点角色,此时默认不能读写。稍等片刻,回车,变成主节点。
> rs.initiate()
{
"info2" : "no configuration specified. Using a default configuration for the set",
"me" : "192.168.229.154:27017",
"ok" : 1
}
myrs:SECONDARY>
myrs:PRIMARY>
1.4.5 查看副本集的配置内容
查看当前副本集配置的文档,语法如下:
# rs.config() 是该方法的别名。
# configuration:可选,如果没有配置,则使用默认主节点配置
rs.conf(configuration)
在 27017 上执行副本集中当前节点的默认节点配置,如下:
myrs:PRIMARY> rs.conf()
{
# 副本集的配置数据存储的主键值,默认就是副本集的名字
"_id" : "myrs",
"version" : 1,
"protocolVersion" : NumberLong(1),
"writeConcernMajorityJournalDefault" : true,
# 副本集成员数组,此时只有一个: "host" : "192.168.229.154:27017"
"members" : [
{
"_id" : 0,
"host" : "192.168.229.154:27017",
# 该成员不是仲裁节点
"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("6528b761af0089c47768dd07")
}
}
提示:
副本集配置的查看命令,本质是查询的是 system.replset 的表中的数据
myrs:PRIMARY> use local
switched to db local
myrs:PRIMARY> show collections
oplog.rs
replset.election
replset.minvalid
replset.oplogTruncateAfterPoint
startup_log
system.replset
system.rollback.id
myrs:PRIMARY> db.system.replset.find()
{ "_id" : "myrs", "version" : 1, "protocolVersion" : NumberLong(1), "writeConcernMajorityJournalDefault" : true, "members" : [ { "_id" : 0, "host" : "192.168.229.154:27017", "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("6528b761af0089c47768dd07") } }
myrs:PRIMARY>
1.4.6 查看副本集状态
返回包含状态信息的文档。此输出使用从副本集的其他成员发送的心跳包中获得的数据反映副本集的当前状态。语法如下:
rs.status()
# 在 27017 上查看副本集状态
myrs:PRIMARY> rs.status()
{
# 副本集的名字
"set" : "myrs",
"date" : ISODate("2023-10-13T06:33:05.838Z"),
# 说明状态正常
"myState" : 1,
"term" : NumberLong(1),
"syncingTo" : "",
"syncSourceHost" : "",
"syncSourceId" : -1,
"heartbeatIntervalMillis" : NumberLong(2000),
"optimes" : {
"lastCommittedOpTime" : {
"ts" : Timestamp(1697178781, 1),
"t" : NumberLong(1)
},
"readConcernMajorityOpTime" : {
"ts" : Timestamp(1697178781, 1),
"t" : NumberLong(1)
},
"appliedOpTime" : {
"ts" : Timestamp(1697178781, 1),
"t" : NumberLong(1)
},
"durableOpTime" : {
"ts" : Timestamp(1697178781, 1),
"t" : NumberLong(1)
}
},
"lastStableCheckpointTimestamp" : Timestamp(1697178721, 1),
"electionCandidateMetrics" : {
"lastElectionReason" : "electionTimeout",
"lastElectionDate" : ISODate("2023-10-13T03:20:02.154Z"),
"electionTerm" : NumberLong(1),
"lastCommittedOpTimeAtElection" : {
"ts" : Timestamp(0, 0),
"t" : NumberLong(-1)
},
"lastSeenOpTimeAtElection" : {
"ts" : Timestamp(1697167201, 1),
"t" : NumberLong(-1)
},
"numVotesNeeded" : 1,
"priorityAtElection" : 1,
"electionTimeoutMillis" : NumberLong(10000),
"newTermStartDate" : ISODate("2023-10-13T03:20:02.231Z"),
"wMajorityWriteAvailabilityDate" : ISODate("2023-10-13T03:20:02.308Z")
},
# 副本集成员数组,此时只有一个
"members" : [
{
"_id" : 0,
"name" : "192.168.229.154:27017",
# 该节点是健康的
"health" : 1,
"state" : 1,
"stateStr" : "PRIMARY",
"uptime" : 12350,
"optime" : {
"ts" : Timestamp(1697178781, 1),
"t" : NumberLong(1)
},
"optimeDate" : ISODate("2023-10-13T06:33:01Z"),
"syncingTo" : "",
"syncSourceHost" : "",
"syncSourceId" : -1,
"infoMessage" : "",
"electionTime" : Timestamp(1697167202, 1),
"electionDate" : ISODate("2023-10-13T03:20:02Z"),
"configVersion" : 1,
"self" : true,
"lastHeartbeatMessage" : ""
}
],
"ok" : 1,
"operationTime" : Timestamp(1697178781, 1),
"$clusterTime" : {
"clusterTime" : Timestamp(1697178781, 1),
"signature" : {
"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
"keyId" : NumberLong(0)
}
}
}
1.4.7 添加副本从节点
在主节点添加从节点,将其他成员加入到副本集,语法如下:
# host格式为:ip地址:端口号
rs.add(host)
将 27018 的副本节点添加到副本集中,命令如下:
myrs:PRIMARY> rs.add("192.168.229.154:27018")
{
# 说明添加成功
"ok" : 1,
"operationTime" : Timestamp(1697179246, 1),
"$clusterTime" : {
"clusterTime" : Timestamp(1697179246, 1),
"signature" : {
"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
"keyId" : NumberLong(0)
}
}
}
此时,再次查看副本集状态
myrs:PRIMARY> rs.status()
{
"set" : "myrs",
"date" : ISODate("2023-10-13T06:41:58.219Z"),
"myState" : 1,
"term" : NumberLong(1),
"syncingTo" : "",
"syncSourceHost" : "",
"syncSourceId" : -1,
"heartbeatIntervalMillis" : NumberLong(2000),
"optimes" : {
"lastCommittedOpTime" : {
"ts" : Timestamp(1697179312, 1),
"t" : NumberLong(1)
},
"readConcernMajorityOpTime" : {
"ts" : Timestamp(1697179312, 1),
"t" : NumberLong(1)
},
"appliedOpTime" : {
"ts" : Timestamp(1697179312, 1),
"t" : NumberLong(1)
},
"durableOpTime" : {
"ts" : Timestamp(1697179312, 1),
"t" : NumberLong(1)
}
},
"lastStableCheckpointTimestamp" : Timestamp(1697179262, 1),
"electionCandidateMetrics" : {
"lastElectionReason" : "electionTimeout",
"lastElectionDate" : ISODate("2023-10-13T03:20:02.154Z"),
"electionTerm" : NumberLong(1),
"lastCommittedOpTimeAtElection" : {
"ts" : Timestamp(0, 0),
"t" : NumberLong(-1)
},
"lastSeenOpTimeAtElection" : {
"ts" : Timestamp(1697167201, 1),
"t" : NumberLong(-1)
},
"numVotesNeeded" : 1,
"priorityAtElection" : 1,
"electionTimeoutMillis" : NumberLong(10000),
"newTermStartDate" : ISODate("2023-10-13T03:20:02.231Z"),
"wMajorityWriteAvailabilityDate" : ISODate("2023-10-13T03:20:02.308Z")
},
"members" : [
{
"_id" : 0,
"name" : "192.168.229.154:27017",
"health" : 1,
"state" : 1,
"stateStr" : "PRIMARY",
"uptime" : 12883,
"optime" : {
"ts" : Timestamp(1697179312, 1),
"t" : NumberLong(1)
},
"optimeDate" : ISODate("2023-10-13T06:41:52Z"),
"syncingTo" : "",
"syncSourceHost" : "",
"syncSourceId" : -1,
"infoMessage" : "",
"electionTime" : Timestamp(1697167202, 1),
"electionDate" : ISODate("2023-10-13T03:20:02Z"),
"configVersion" : 2,
"self" : true,
"lastHeartbeatMessage" : ""
},
# 这个新创建的第二个角色
{
"_id" : 1,
"name" : "192.168.229.154:27018",
"health" : 1,
"state" : 2,
"stateStr" : "SECONDARY",
"uptime" : 71,
"optime" : {
"ts" : Timestamp(1697179312, 1),
"t" : NumberLong(1)
},
"optimeDurable" : {
"ts" : Timestamp(1697179312, 1),
"t" : NumberLong(1)
},
"optimeDate" : ISODate("2023-10-13T06:41:52Z"),
"optimeDurableDate" : ISODate("2023-10-13T06:41:52Z"),
"lastHeartbeat" : ISODate("2023-10-13T06:41:56.894Z"),
"lastHeartbeatRecv" : ISODate("2023-10-13T06:41:57.236Z"),
"pingMs" : NumberLong(0),
"lastHeartbeatMessage" : "",
"syncingTo" : "192.168.229.154:27017",
"syncSourceHost" : "192.168.229.154:27017",
"syncSourceId" : 0,
"infoMessage" : "",
"configVersion" : 2
}
],
"ok" : 1,
"operationTime" : Timestamp(1697179312, 1),
"$clusterTime" : {
"clusterTime" : Timestamp(1697179312, 1),
"signature" : {
"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
"keyId" : NumberLong(0)
}
}
}
1.4.8 添加仲裁从节点
添加一个仲裁节点到副本集,语法如下:
rs.addArb(host)
将 27019 的仲裁节点添加到副本集中
myrs:PRIMARY> rs.addArb("192.168.229.154:27019")
{
# 说明添加成功
"ok" : 1,
"operationTime" : Timestamp(1697179436, 1),
"$clusterTime" : {
"clusterTime" : Timestamp(1697179436, 1),
"signature" : {
"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
"keyId" : NumberLong(0)
}
}
}
此时,再次查看副本集状态
myrs:PRIMARY> rs.status()
{
"set" : "myrs",
"date" : ISODate("2023-10-13T06:45:08.595Z"),
"myState" : 1,
"term" : NumberLong(1),
"syncingTo" : "",
"syncSourceHost" : "",
"syncSourceId" : -1,
"heartbeatIntervalMillis" : NumberLong(2000),
"optimes" : {
"lastCommittedOpTime" : {
"ts" : Timestamp(1697179502, 1),
"t" : NumberLong(1)
},
"readConcernMajorityOpTime" : {
"ts" : Timestamp(1697179502, 1),
"t" : NumberLong(1)
},
"appliedOpTime" : {
"ts" : Timestamp(1697179502, 1),
"t" : NumberLong(1)
},
"durableOpTime" : {
"ts" : Timestamp(1697179502, 1),
"t" : NumberLong(1)
}
},
"lastStableCheckpointTimestamp" : Timestamp(1697179436, 1),
"electionCandidateMetrics" : {
"lastElectionReason" : "electionTimeout",
"lastElectionDate" : ISODate("2023-10-13T03:20:02.154Z"),
"electionTerm" : NumberLong(1),
"lastCommittedOpTimeAtElection" : {
"ts" : Timestamp(0, 0),
"t" : NumberLong(-1)
},
"lastSeenOpTimeAtElection" : {
"ts" : Timestamp(1697167201, 1),
"t" : NumberLong(-1)
},
"numVotesNeeded" : 1,
"priorityAtElection" : 1,
"electionTimeoutMillis" : NumberLong(10000),
"newTermStartDate" : ISODate("2023-10-13T03:20:02.231Z"),
"wMajorityWriteAvailabilityDate" : ISODate("2023-10-13T03:20:02.308Z")
},
"members" : [
{
"_id" : 0,
"name" : "192.168.229.154:27017",
"health" : 1,
"state" : 1,
"stateStr" : "PRIMARY",
"uptime" : 13073,
"optime" : {
"ts" : Timestamp(1697179502, 1),
"t" : NumberLong(1)
},
"optimeDate" : ISODate("2023-10-13T06:45:02Z"),
"syncingTo" : "",
"syncSourceHost" : "",
"syncSourceId" : -1,
"infoMessage" : "",
"electionTime" : Timestamp(1697167202, 1),
"electionDate" : ISODate("2023-10-13T03:20:02Z"),
"configVersion" : 3,
"self" : true,
"lastHeartbeatMessage" : ""
},
{
"_id" : 1,
"name" : "192.168.229.154:27018",
"health" : 1,
"state" : 2,
"stateStr" : "SECONDARY",
"uptime" : 262,
"optime" : {
"ts" : Timestamp(1697179502, 1),
"t" : NumberLong(1)
},
"optimeDurable" : {
"ts" : Timestamp(1697179502, 1),
"t" : NumberLong(1)
},
"optimeDate" : ISODate("2023-10-13T06:45:02Z"),
"optimeDurableDate" : ISODate("2023-10-13T06:45:02Z"),
"lastHeartbeat" : ISODate("2023-10-13T06:45:06.956Z"),
"lastHeartbeatRecv" : ISODate("2023-10-13T06:45:06.973Z"),
"pingMs" : NumberLong(0),
"lastHeartbeatMessage" : "",
"syncingTo" : "192.168.229.154:27017",
"syncSourceHost" : "192.168.229.154:27017",
"syncSourceId" : 0,
"infoMessage" : "",
"configVersion" : 3
},
{
"_id" : 2,
"name" : "192.168.229.154:27019",
"health" : 1,
"state" : 7,
"stateStr" : "ARBITER",
"uptime" : 71,
"lastHeartbeat" : ISODate("2023-10-13T06:45:06.955Z"),
"lastHeartbeatRecv" : ISODate("2023-10-13T06:45:06.956Z"),
"pingMs" : NumberLong(0),
"lastHeartbeatMessage" : "",
"syncingTo" : "",
"syncSourceHost" : "",
"syncSourceId" : -1,
"infoMessage" : "",
"configVersion" : 3
}
],
"ok" : 1,
"operationTime" : Timestamp(1697179502, 1),
"$clusterTime" : {
"clusterTime" : Timestamp(1697179502, 1),
"signature" : {
"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
"keyId" : NumberLong(0)
}
}
}
1.5 副本集读写操作
接下来我们测试三个不同角色的节点的数据读写情况。登录主节点 27017,写入和读取数据
myrs:PRIMARY> use articledb
switched to db articledb
myrs:PRIMARY> db
articledb
myrs:PRIMARY> db.comment.insert({"articleid":"100000","content":"今天天气真好,阳光明媚","userid":"1001","nickname":"Rose","createdatetime":new Date()})
WriteResult({ "nInserted" : 1 })
myrs:PRIMARY> db.comment.find()
{ "_id" : ObjectId("6528e8155dabb447bc9211d2"), "articleid" : "100000", "content" : "今天天气真好,阳光明媚", "userid" : "1001", "nickname" : "Rose", "createdatetime" : ISODate("2023-10-13T06:47:49.542Z") }
登录从节点 27018
myrs:SECONDARY> show dbs
2023-10-13T00:06:41.119-0700 E QUERY [js] Error: listDatabases failed:{
"operationTime" : Timestamp(1697180793, 1),
"ok" : 0,
"errmsg" : "not master and slaveOk=false",
"code" : 13435,
"codeName" : "NotMasterNoSlaveOk",
"$clusterTime" : {
"clusterTime" : Timestamp(1697180793, 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:151:1
shellHelper.show@src/mongo/shell/utils.js:882:13
shellHelper@src/mongo/shell/utils.js:766:15
@(shellhelp2):1:1
发现,不能读取集合的数据。当前从节点只是一个备份,不是奴隶节点,无法读取数据,写当然更不行。
因为默认情况下,从节点是没有读写权限的,可以增加读的权限,但需要进行设置。
# 设置读操作权限
# 该命令是 db.getMongo().setSlaveOk() 的简化命令。
rs.slaveOk() 或 rs.slaveOk(true)
# 在 27018 上设置作为奴隶节点权限,具备读权限
myrs:SECONDARY> rs.slaveOk()
WARNING: slaveOk() is deprecated and may be removed in the next major release. Please use secondaryOk() instead.
# 执行查询命令,运行成功!
myrs:SECONDARY> show dbs
admin 0.000GB
articledb 0.000GB
config 0.000GB
local 0.000GB
myrs:SECONDARY> db.comment.find()
myrs:SECONDARY> use articledb
switched to db articledb
myrs:SECONDARY> db.comment.find()
{ "_id" : ObjectId("6528e8155dabb447bc9211d2"), "articleid" : "100000", "content" : "今天天气真好,阳光明媚", "userid" : "1001", "nickname" : "Rose", "createdatetime" : ISODate("2023-10-13T06:47:49.542Z") }
# 但仍然不允许插入
myrs:SECONDARY> db.comment.insert({"_id":"1","articleid":"100001","content":"我们不应该把清晨浪费在手机上,健康很重要,k一杯温水幸福你我他。","userid":"1002","nickname":"相忘于江湖","createdatetime":new Date("2019-08-05T22:08:15.522Z"),"likenum":NumberInt(1000),"state":"1"})
WriteCommandError({
"operationTime" : Timestamp(1697181093, 1),
"ok" : 0,
"errmsg" : "not master",
"code" : 10107,
"codeName" : "NotMaster",
"$clusterTime" : {
"clusterTime" : Timestamp(1697181093, 1),
"signature" : {
"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
"keyId" : NumberLong(0)
}
}
})
现在实现了读写分离,让主插入数据,让从来读取数据。
如果要取消作为奴隶节点的读权限:
rs.slaveOk(false)
# 在 27018 取消设置奴隶节点权限
myrs:SECONDARY> rs.slaveOk(false)
WARNING: slaveOk() is deprecated and may be removed in the next major release. Please use secondaryOk() instead.
# 无法再查看数据
myrs:SECONDARY> db.comment.find()
Error: error: {
"operationTime" : Timestamp(1697181203, 1),
"ok" : 0,
"errmsg" : "not master and slaveOk=false",
"code" : 13435,
"codeName" : "NotMasterNoSlaveOk",
"$clusterTime" : {
"clusterTime" : Timestamp(1697181203, 1),
"signature" : {
"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
"keyId" : NumberLong(0)
}
}
}
仲裁者节点,不存放任何业务数据的,可以登录查看
# 登录 27019 节点
/usr/local/mongodb/bin/mongo --host 192.168.229.154 --port 27019
# 设置为奴隶节点
myrs:ARBITER> rs.slaveOk()
WARNING: slaveOk() is deprecated and may be removed in the next major release. Please use secondaryOk() instead.
# 无法查看相关信息
myrs:ARBITER> show dbs
2023-10-13T00:15:19.072-0700 E QUERY [js] Error: listDatabases failed:{
"ok" : 0,
"errmsg" : "node is not in primary or recovering state",
"code" : 13436,
"codeName" : "NotMasterOrSecondary"
} :
_getErrorWithCode@src/mongo/shell/utils.js:25:13
Mongo.prototype.getDBs@src/mongo/shell/mongo.js:151:1
shellHelper.show@src/mongo/shell/utils.js:882:13
shellHelper@src/mongo/shell/utils.js:766:15
@(shellhelp2):1:1
1.6 主节点的选举原则
MongoDB 在副本集中,会自动进行主节点的选举,主节点选举的触发条件:
a、主节点故障
b、主节点网络不可达(默认心跳信息为10秒)
c、人工干预(rs.stepDown(600))
一旦触发选举,就要根据一定规则来选主节点。选举规则是根据票数来决定谁获胜:
票数最高,并且获得了 “大多数” 成员的投票支持的节点获胜。
“大多数” 的定义为:假设复制集内投票成员数量为 N,则大多数为 N/2 + 1。例如:3 个投票成员,则大多数的值是 2。当复制集内存活成员数量不足大多数时,整个复制集将无法选举出 Primary,复制集将无法提供写服务,处于只读状态。
若票数相同,且都获得了“大多数”成员的投票支持的,数据新的节点获胜。
数据的新旧是通过操作日志 oplog 来对比的。
在获得票数的时候,优先级(priority)参数影响重大。
可以通过设置优先级(priority)来设置额外票数。优先级即权重,取值为 0-1000,相当于可额外增加 0-1000 的票数,优先级的值越大,就越可能获得多数成员的投票(votes)数。指定较高的值可使成员更有资格成为主要成员,更低的值可使成员更不符合条件。
默认情况下,优先级的值是 1
myrs:PRIMARY> rs.conf()
{
"_id" : "myrs",
"version" : 3,
"protocolVersion" : NumberLong(1),
"writeConcernMajorityJournalDefault" : true,
"members" : [
{
"_id" : 0,
"host" : "192.168.229.154:27017",
"arbiterOnly" : false,
"buildIndexes" : true,
"hidden" : false,
"priority" : 1,
"tags" : {
},
"slaveDelay" : NumberLong(0),
"votes" : 1
},
{
"_id" : 1,
"host" : "192.168.229.154:27018",
"arbiterOnly" : false,
"buildIndexes" : true,
"hidden" : false,
"priority" : 1,
"tags" : {
},
"slaveDelay" : NumberLong(0),
"votes" : 1
},
{
"_id" : 2,
"host" : "192.168.229.154:27019",
"arbiterOnly" : true,
"buildIndexes" : true,
"hidden" : false,
"priority" : 0,
"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("6528b761af0089c47768dd07")
}
}
可以看出,主节点和副本节点的优先级各为 1,即,默认可以认为都已经有了一票。但选举节点,优先级是 0,(要注意是,官方说了,选举节点的优先级必须是 0,不能是别的值。即不具备选举权,但具有投票权)。
1.7 故障测试
1.7.1 副本节点故障测试
首先关闭 27018 副本节点。主节点和仲裁节点对 27018 的心跳失败。但是因为主节点还在,因此,没有触发投票选举。如果此时,在主节点写入数据。
db.comment.insert({"_id":"1","articleid":"100001","content":"我们不应该把清晨浪费在手机上,健康很重要,一杯温水幸福你我他。","userid":"1002","nickname":"相忘于江湖","createdatetime":new Date("2019-08-05T22:08:15.522Z"),"likenum":NumberInt(1000),"state":"1"})
再次启动 27018 从节点,会发现,主节点写入的数据,会自动同步给从节点。
1.7.2 主节点故障测试
关闭 27017 节点,从节点和仲裁节点对 27017 的心跳失败,当失败超过 10 秒,此时因为没有主节点了,会自动发起投票。
而副本节点只有 27018,因此,候选人只有一个就是 27018,开始投票。
27019 向 27018 投了一票,27018 本身自带一票,因此共两票,超过了 “大多数”
27019 是仲裁节点,没有选举权,27018 不向其投票,其票数是 0。
最终结果,27018 成为主节点。具备读写功能。在 27018 写入数据查看。
db.comment.insert({"_id":"1","articleid":"100001","content":"我们不应该把清晨浪费在手机上,健康很重要,一杯温水幸福你我他。","userid":"1002","nickname":"相忘于江湖","createdatetime":new Date("2019-08-05T22:08:15.522Z"),"likenum":NumberInt(1000),"state":"1"})
再启动 27017 节点,发现 27017 变成了从节点,27018 仍保持主节点。
登录 27017 节点,发现是从节点了,数据自动从 27018 同步。从而实现了高可用。
1.7.3 仲裁节点和主节点故障
先关掉仲裁节点 27019,关掉现在的主节点 27018。
登录 27017 后,发现,27017 仍然是从节点,副本集中没有主节点了,导致此时,副本集是只读状态,无法写入。
为啥不选举了?因为 27017 的票数,没有获得大多数,即没有大于等于 2,它只有默认的一票(优先级是1)如果要触发选举,随便加入一个成员即可。
如果只加入 27019 仲裁节点成员,则主节点一定是 27017,因为没得选了,仲裁节点不参与选举,但参与投票。
如果只加入 27018 节点,会发起选举。因为 27017 和 27018 都是两票,则按照谁数据新,谁当主节点。
1.7.4 仲裁节点和从节点故障
先关掉仲裁节点 27019,关掉现在的副本节点 27018,
10 秒后,27017 主节点自动降级为副本节点。(服务降级)副本集不可写数据了,已经故障了。
1.8 Compass 连接副本集
1.9 SpringDataMongoDB 连接副本集
副本集语法如下:
# slaveOk=true :开启副本节点读的功能,可实现读写分离
# connect=replicaSet:自动到副本集中选择读写的主机,如果 slaveOk 是打开的,则实现了读写分离
mongodb://host1,host2,host3/数据库名称?connect=replicaSet&laveOk=true$replicaSet=副本集名字
修改我们自己的 application.yml 文件,如下所示:
spring:
# 数据源配置
data:
mongodb:
# 主机地址
# host: 192.168.229.154
# 数据库
# database: articledb
# 默认端口是27017
# port: 27017
# 也可以使用uri连接
uri: mongodb://192.168.229.154:27017,192.168.229.154:27018,192.168.229.154:27019/articledb?connect=replicaSet&slaveOk=true&replicaSet=myrs
注意:
SpringDataMongoDB 自动实现了读写分离。
写操作时,只打开主节点连接;
读操作时,同时打开主节点和从节点连接,但使用从节点获取数据。