目录
一、什么是MongoDB的副本集
简介
(1)冗余和数据可用性
(2)MongoDB中的复制
(3)主从复制和副本集区别
二、副本集的架构
三、副本集的成员
四、部署副本集
1、节点划分
2、安装MongoDB
2.1、下载解压安装包
3、创建主节点
3.1、创建存储数据和日志的目录
3.2、新建配置文件
3.3、启动节点服务
4、创建副本节点
4.1、创建存储数据和日志的目录
4.2、新建配置文件
4.3、启动节点服务
5、创建仲裁节点
5.1、创建存储数据和日志的目录
5.3、启动节点服务
配置system启动服务
6、添加环境变量
7、初始化副本集
7.1、客户端连接主节点
7.2、初始化副本集
8、查看副本集信息
8.1、查看副本集的配置内容
8.2、查看集群状态
9、添加副本节点和仲裁节点
10、再次查看副本集配置信息
11、设置SECONDARY副本节点可读
五、测试副本集的数据读写操作
1、主节点测试
2、副本节点测试
2.1、设置副本节点只能作为备份不能读取
3、仲裁节点不存放任何数据
六、主节点的选举原则
1、主节点选举触发条件
2、选举规则
七、集群故障分析
1、主节点故障
2、副本节点故障
3、仲裁节点故障
4、主节点和仲裁节点故障
5、从节点和仲裁节点故障
6、主节点和从节点故障
7、所有节点故障
一、什么是MongoDB的副本集
简介
MongoDB 中的副本集(Replica Sets)是一组维护相同数据集的MongoDB服务。副本集可提供冗余和高可用性,是所有生产部署的基础。
也可以说,副本集类似于有自动故障恢复功能的主从集群 。通俗的讲就是用 多台机器进行同一数据的异步同步,从而使多台及其拥有同一数据的多个副本,并且当主库宕掉时在不需要用户干预的情况下自动切换其他服务器做主库。而且还可以利用副本服务器做只读服务器,实现读写分离,提高负载。
(1)冗余和数据可用性
复制提供冗余并提高数据可用性。通过在不同数据库服务器上提供多个数据副本,复制可提供一定级别的容错功能,以防止丢失单个数据库服务器。
(2)MongoDB中的复制
副本集是一组维护相同数据集的MongoDB实例。副本集包含多个数据承载节点和可选的一个仲裁节点。在承载数据的节点中,一个且仅一个成员被视为主节点,而且他节点被视为次要(从)节点。
主节点接受所有读写操作。副本集只能由一个主要能够具有{w: “most”}写入关注的写入;虽然在某些 情况下,另一个MongoDB实例可能暂时认为自己也是主要的。主要记录其操作日志中的数据集的所有更改,即oplog。
(3)主从复制和副本集区别
主从集群和副本集最大的区别就是副本集没有固定的“主节点”;整个集群会选出一个“主节点”,当其宕掉后,又在剩下的从节点中选中其他节点为“主节点”,副本集总有一个活跃点(主、primary)和一个或多个备份节点(从、secondary)。
二、副本集的架构
一个副本集最多有50个节点。一个副本集最多有7个投票节点,其余节点必须是没有投票权的节点。
副本集的最小推荐配置是三个节点:
- 一个主节点和两个从节点。
- 一个主节点、一个从节点和仲裁节点。
三、副本集的成员
副本集有两种类型三种角色
-
两种类型
-
主节点(Primary)类型:数据操作的主要连接点,可读写
-
次要(辅助、从)节点(Secondaries)类型:数据冗余备份节点,可以读或选举
-
-
三种角色
-
主要成员(Primary):主要接收所有写操作。就是主节点。
-
副本成员(Replicate):从主节点通过复制操作以维护相同的数据集,即备份数据,不可写操作,但可以读操作(但需要配置)。是默认的一种从节点类型
-
仲裁者(Arbiter):不保留任何数据的副本,只具有投票选举作用,当然也可以将仲裁服务器维护为副本集的一部分,即副本成员同时也可以是仲裁者。也是一种从节点类型。
-
-
建议
-
如果你的副本+主节点的个数是偶数,建议加一个仲裁者,形成奇数,容易满足大多数的投票。
-
如果你的副本+主节点的个数是奇数,可以不加仲裁者。
-
四、部署副本集
1、节点划分
由于测试使用一台机器上部署三个mongodb节点,生产环境需要两到三台机器
端口 | 角色 |
---|---|
27017 | 主节点 |
27018 | 副本节点 |
27019 | 仲裁节点 |
2、安装MongoDB
2.1、下载解压安装包
官方安装包获取地址:Download MongoDB Community Server | MongoDB
wget https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-rhel70-4.4.29.tgz
tar xzvf mongodb-linux-x86_64-rhel70-4.4.29.tgz -C /usr/local
cd /usr/local/
ln -s /usr/local/mongodb-linux-x86_64-rhel70-4.4.29 /usr/local/mongodb
3、创建主节点
3.1、创建存储数据和日志的目录
mkdir -p /usr/local/mongodb/replica_sets/mongors_27017/log
mkdir -p /usr/local/mongodb/replica_sets/mongors_27017/data/db
3.2、新建配置文件
vim /usr/local/mongodb/replica_sets/mongors_27017/mongod.conf
systemLog:
destination: file
path: "/usr/local/mongodb/replica_sets/mongors_27017/log/mongod.log"
logAppend: true
storage:
dbPath: "/usr/local/mongodb/replica_sets/mongors_27017/data/db"
journal:
# 启用持久性日志
enabled: true
processManagement:
fork: true
pidFilePath: "/usr/local/mongodb/replica_sets/mongors_27017/log/mongod.pid"
net:
bindIp: localhost,192.168.95.195
port: 27017
replication:
replSetName: mongors
3.3、启动节点服务
[root@localhost local]# /usr/local/mongodb/bin/mongod -f /usr/local/mongodb/replica_sets/mongors_27017/mongod.conf
about to fork child process, waiting until server is ready for connections.
forked process: 37112
child process started successfully, parent exiting
4、创建副本节点
4.1、创建存储数据和日志的目录
mkdir -p /usr/local/mongodb/replica_sets/mongors_27018/log
mkdir -p /usr/local/mongodb/replica_sets/mongors_27018/data/db
4.2、新建配置文件
vim /usr/local/mongodb/replica_sets/mongors_27018/mongod.conf
systemLog:
destination: file
path: "/usr/local/mongodb/replica_sets/mongors_27018/log/mongod.log"
logAppend: true
storage:
dbPath: "/usr/local/mongodb/replica_sets/mongors_27018/data/db"
journal:
enabled: true
processManagement:
fork: true
pidFilePath: "/usr/local/mongodb/replica_sets/mongors_27018/log/mongod.pid"
net:
bindIp: localhost,192.168.95.195
port: 27018
replication:
replSetName: mongors
4.3、启动节点服务
[root@localhost ~]# /usr/local/mongodb/bin/mongod -f /usr/local/mongodb/replica_sets/mongors_27018/mongod.conf
about to fork child process, waiting until server is ready for connections.
forked process: 38263
child process started successfully, parent exiting
5、创建仲裁节点
5.1、创建存储数据和日志的目录
mkdir -p /usr/local/mongodb/replica_sets/mongors_27019/log
mkdir -p /usr/local/mongodb/replica_sets/mongors_27019/data/db
5.2、创建配置文件
vim /usr/local/mongodb/replica_sets/mongors_27019/mongod.conf
systemLog:
destination: file
path: "/usr/local/mongodb/replica_sets/mongors_27019/log/mongod.log"
logAppend: true
storage:
dbPath: "/usr/local/mongodb/replica_sets/mongors_27019/data/db"
journal:
# 启用持久性日志
enabled: true
processManagement:
fork: true
pidFilePath: "/usr/local/mongodb/replica_sets/mongors_27019/log/mongod.pid"
net:
bindIp: localhost,192.168.95.195
port: 27019
replication:
replSetName: myrs
5.3、启动节点服务
[root@localhost ~]# /usr/local/mongodb/bin/mongod -f /usr/local/mongodb/replica_sets/mongors_27019/mongod.conf
about to fork child process, waiting until server is ready for connections.
forked process: 38314
child process started successfully, parent exiting
配置mongodb system启动服务
以上节点启动可制作systemctl start mongodb.service可以参考以下文章链接
mongodb设置system开机自启_mongodb开机自启动-CSDN博客
6、添加环境变量
vim /etc/profile
export PATH=/usr/local/mongodb/bin/:$PATH
source /etc/profile
[root@localhost ~]# which mongo
/usr/local/mongodb/bin/mongo
7、初始化副本集
7.1、客户端连接主节点
mongo --host 192.168.95.195 --port=27017
7.2、初始化副本集
当前并未选举主节点(primary),并且操作没有设置为允许从辅助节点(secondary)上读取。
所以很多命令无法使用,必须初始化副本集
> show dbs
uncaught exception: Error: listDatabases failed:{
"topologyVersion" : {
"processId" : ObjectId("692f9e56d74e94faba5875bc"),
"counter" : NumberLong(0)
},
"ok" : 0,
"errmsg" : "not master and slaveOk=false",
"code" : 13435,
"codeName" : "NotPrimaryNoSecondaryOk"
} :
_getErrorWithCode@src/mongo/shell/utils.js:25:13
Mongo.prototype.getDBs/<@src/mongo/shell/mongo.js:147:19
Mongo.prototype.getDBs@src/mongo/shell/mongo.js:99:12
shellHelper.show@src/mongo/shell/utils.js:937:13
shellHelper@src/mongo/shell/utils.js:819:15
@(shellhelp2):1:1
初始化副本集
双击回车,成为主节点(一次回车→SECONDARY,两次回车→PRIMARY)
执行结果:
> rs.initiate()
{
"info2" : "no configuration specified. Using a default configuration for the set",
"me" : "192.168.95.195:27017",
"ok" : 1
}
mongors:SECONDARY>
mongors:PRIMARY>
mongors:PRIMARY>
mongors:PRIMARY>
"ok"的值为1,说明创建成功。
命令行提示符发生变化,变成了一个从节点角色,此时默认不能读写。
稍等片刻,发现副本集只有自己一个,变成主节点。
8、查看副本集信息
8.1、查看副本集的配置内容
mongors:PRIMARY> rs.config()
{
"_id" : "mongors",
"version" : 1,
"term" : 1,
"protocolVersion" : NumberLong(1),
"writeConcernMajorityJournalDefault" : true,
"members" : [
{
"_id" : 0,
"host" : "192.168.95.195: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("662fa68cd74e84faba9867e0")
}
}
提示:
rs.config()是该方法的别名。configuration:可选,如果没有配置,则使用默认主节点配置。
说明
"_id" : "mongors" :副本集的配置数据存储的主键值,默认就是副本集的名字
"members" :副本集成员数组,此时只有一个:"host" : "192.168.112.40:27017"
该成员不是仲裁节点: "arbiterOnly" : false
优先级(权重值): "priority" : 1
"settings" :副本集的参数配置。
8.2、查看集群状态
rs.status()
9、添加副本节点和仲裁节点
添加副本节点
mongors:PRIMARY> rs.add("192.168.95.195:27018")
{
"ok" : 1,
"$clusterTime" : {
"clusterTime" : Timestamp(1714399646, 1),
"signature" : {
"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
"keyId" : NumberLong(0)
}
},
"operationTime" : Timestamp(1714399646, 1)
}
添加仲裁节点
mongors:PRIMARY> rs.addArb("192.168.95.195:27019")
{
"ok" : 1,
"$clusterTime" : {
"clusterTime" : Timestamp(1714399738, 1),
"signature" : {
"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
"keyId" : NumberLong(0)
}
},
"operationTime" : Timestamp(1714399738, 1)
}
10、再次查看副本集配置信息
mongors:PRIMARY> rs.conf()
{
"_id" : "mongors",
"version" : 3,
"term" : 1,
"protocolVersion" : NumberLong(1),
"writeConcernMajorityJournalDefault" : true,
"members" : [
{
"_id" : 0,
"host" : "192.168.112.40:27017",
"arbiterOnly" : false,
"buildIndexes" : true,
"hidden" : false,
"priority" : 1,
"tags" : {
},
"slaveDelay" : NumberLong(0),
"votes" : 1
},
{
"_id" : 1,
"host" : "192.168.112.40:27018",
"arbiterOnly" : false,
"buildIndexes" : true,
"hidden" : false,
"priority" : 1,
"tags" : {
},
"slaveDelay" : NumberLong(0),
"votes" : 1
},
{
"_id" : 2,
"host" : "192.168.112.40: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("662fa69cd74e84faba8865e0")
}
}
- 成员信息:
- 成员1(_id: 0)和成员2(_id: 1)都是数据承载节点,它们位于同一台机器的不同端口(27017 和 27018)。两者都有相同的优先级("priority" : 1),意味着它们都可以成为主节点。它们都没有延迟复制("slaveDelay" : NumberLong(0)),并且都参与选举投票("votes" : 1)。
- 成员3(_id: 2)是一个仲裁者(arbiterOnly: true),位于端口27019。仲裁者的角色是在选举新主节点时起到决定性的一票作用,但它不存储实际数据,也没有优先级,不参与数据复制。
11、设置SECONDARY副本节点可读
rs.slaveOk()
五、测试副本集的数据读写操作
1、主节点测试
[root@localhost ~]# mongo --port 27017
mongors:PRIMARY> show dbs
admin 0.000GB
config 0.000GB
local 0.000GB
mongors:PRIMARY> use test1
switched to db test1
mongors:PRIMARY> db.q1.insert({"id":"1000","content":"学习部署副本集","userid":"001","name":"xjx","createdatatime":new Date(),"state":null})
WriteResult({ "nInserted" : 1 })
mongors:PRIMARY> db.q1.find().pretty()
{
"_id" : ObjectId("662fb0d727a6cea3c847b26e"),
"id" : "1000",
"content" : "学习部署副本集",
"userid" : "001",
"name" : "xjx",
"createdatatime" : ISODate("2024-08-15T14:38:15.243Z"),
"state" : null
}
2、副本节点测试
[root@localhost ~]# mongo --port 27018
mongors:SECONDARY> show dbs
admin 0.000GB
config 0.000GB
local 0.000GB
test1 0.000GB
myrs:SECONDARY> use test1
switched to db test1
myrs:SECONDARY> show tables;
q1
mongors:SECONDARY> db.q1.find().pretty()
{
"_id" : ObjectId("662fb0d727a6cea3c847b26e"),
"id" : "1000",
"content" : "学习部署副本集",
"userid" : "001",
"name" : "xjx",
"createdatatime" : ISODate("2024-08-15T14:38:15.243Z"),
"state" : null
}
mongors:SECONDARY> db.q1.insert({"id":"1001","content":"学习部署副本集","userid":"002","name":"lisi","createdatatime":new Date(),"state":null})
WriteCommandError({
"topologyVersion" : {
"processId" : ObjectId("662fa148dfb1efa1f75795c3"),
"counter" : NumberLong(4)
},
"operationTime" : Timestamp(1714402027, 1),
"ok" : 0,
"errmsg" : "not master",
"code" : 10107,
"codeName" : "NotWritablePrimary",
"$clusterTime" : {
"clusterTime" : Timestamp(1714402027, 1),
"signature" : {
"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
"keyId" : NumberLong(0)
}
}
})
无法写入数据
"errmsg" : "not master"
"codeName" : "NotWritablePrimary"
2.1、设置副本节点只能作为备份不能读取
#设置从节点有读取权限
rs.slaveOk()
# 取消从节点的数据读取权限
rs.slaveOk(false)
3、仲裁节点不存放任何数据
[root@localhost ~]# mongo --port 27019
mongors:ARBITER> show dbs
uncaught exception: Error: listDatabases failed:{
"topologyVersion" : {
"processId" : ObjectId("662fa25360629ad0a0f05cc1"),
"counter" : NumberLong(1)
},
"ok" : 0,
"errmsg" : "not master and slaveOk=false",
"code" : 13435,
"codeName" : "NotPrimaryNoSecondaryOk"
} :
_getErrorWithCode@src/mongo/shell/utils.js:25:13
Mongo.prototype.getDBs/<@src/mongo/shell/mongo.js:147:19
Mongo.prototype.getDBs@src/mongo/shell/mongo.js:99:12
shellHelper.show@src/mongo/shell/utils.js:937:13
shellHelper@src/mongo/shell/utils.js:819:15
@(shellhelp2):1:1
mongors:ARBITER> rs.slaveOk()
WARNING: slaveOk() is deprecated and may be removed in the next major release. Please use secondaryOk() instead.
mongors:ARBITER> show dbs
uncaught exception: Error: listDatabases failed:{
"topologyVersion" : {
"processId" : ObjectId("662fa25360629ad0a0f05cc1"),
"counter" : NumberLong(1)
},
"ok" : 0,
"errmsg" : "node is not in primary or recovering state",
"code" : 13436,
"codeName" : "NotPrimaryOrSecondary"
} :
_getErrorWithCode@src/mongo/shell/utils.js:25:13
Mongo.prototype.getDBs/<@src/mongo/shell/mongo.js:147:19
Mongo.prototype.getDBs@src/mongo/shell/mongo.js:99:12
shellHelper.show@src/mongo/shell/utils.js:937:13
shellHelper@src/mongo/shell/utils.js:819:15
@(shellhelp2):1:1
"errmsg" : "node is not in primary or recovering state"
仲裁节点(Arbiter)在MongoDB副本集中仅用于选举过程中的投票,并不存储数据
六、主节点的选举原则
1、主节点选举触发条件
-
MongoDB在副本集中,会自动进行主节点的选举,主节点选举的触发条件
-
主节点故障
-
主节点网络不可达(默认心跳信息为10秒)
-
人工干预(
rs.stepDown(600)
)primary直接降级在600s内不会把自己选为primary
-
-
一旦触发选举,就要根据一定规则来选择主节点。
2、选举规则
-
选举规则是根据票数来决定谁获胜
-
票数最高,且获得了“大多数”成员的投票支持的节点获胜。
-
"大多数"的定义为:假设复制集内投票成员时N,则大多数为N/2+1。例如:3个投票成员,则大多数的值是2。当复制集内存活成员数量不足大多数时,整个复制集将无法选举primary,复制集将无法提供写服务,处于只读状态。
-
若票数相同,且都获得了“大多数”成员的投票支持的,数据新的节点获胜。
-
数据的新旧是通过操作日志oplog来对比的。
-
-
在获得票数的时候,优先级(priority)参数影响重大。
-
可以通过设置优先级(priority)来设置额外票数。优先级即权重,取值为0-1000,相当于增加0-1000的票数,优先级的值越大,就越可能获得多数成员的投票(votes)数。指定较高的值可使成员更有资格成员主要成员,更低的值可使成员更不符合条件。
-
默认情况下,优先级的值是1
七、集群故障分析
1、主节点故障
- 从节点和仲裁节点对主节点的心跳失败,当失败超过10秒,此时因为没有主节点了,会自动发起投票。
- 而副本节点只有一台,因此,候选人只有一个就是副本节点,开始投票。
- 仲裁节点向副本节点投了一票,副本节点本身自带一票,因此共两票,超过了"大多数"。
- 27019是仲裁节点,没有选举权,27018不向其投票,其票数是0。
- 最终结果,27018成为主节点。具备读写功能。
- 再启动 27017主节点,发现27017变成了从节点,27018仍保持主节点。
- 登录27017节点,发现是从节点了,数据自动从27018同步。
- 此时:不影响正常使用
2、副本节点故障
- 主节点和仲裁节点对副本节点的心跳失败。因为主节点还在,因此,没有触发投票选举。
如果此时,在主节点写入数据。再启动从节点,会发现,主节点写入的数据,会自动同步给从节点。 - 此时:不影响正常使用
3、仲裁节点故障
- 主节点和副本节点对仲裁节点的心跳失败。因为主节点还在,因此,没有触发投票选举。
- 此时:不影响正常使用
4、主节点和仲裁节点故障
副本集中没有主节点了,导致此时,副本集是只读状态,无法写入。
因为27017的票数,没有获得大多数,即没有大于等于2,它只有默认的一票(优先级是1)
如果要触发选举,随便加入一个成员即可。
- 如果只加入 27019仲裁节点成员,则主节点一定是27017,因为没得选了,仲裁节点不参与选举,但参与投票。
- 如果只加入 27018节点,会发起选举。因为27017和27018都是两票,则按照谁数据新,谁当主节点。
此时:影响正常使用,需要处理
5、从节点和仲裁节点故障
10秒后,27017主节点自动降级为副本节点。(服务降级)
副本集不可写数据了,已经故障了。
此时:影响正常使用,需要处理
6、主节点和从节点故障
集群将处于不完全状态,无法执行写操作,因为剩余的副本节点不足以立即选出新的主节点(假设只剩一个副本节点和仲裁节点)。直到至少有一个额外的副本节点在线并同步,以便选举出新的主节点
此时:影响正常使用,需要处理
7、所有节点故障
-
整个集群不可用,既不能执行读也不能执行写操作。
-
这种情况需要手动干预,逐一排查并恢复各个节点,确保至少一个主节点和多数节点(包括仲裁节点)在线,以恢复集群服务。
-
此时:影响正常使用,需要处理