本文主要介绍了Linux环境下mongoDB副本集模式的搭建(一主一从一仲裁),具体包括了搭建步骤、主从切换、高可用测试等。
架构图
环境
- Ubuntu 5.4.0-6ubuntu1~16.04.12
- mongodb-linux-x86_64-ubuntu1604-4.2.8.tgz
步骤
创建主节点
- 建立存放数据和日志的目录
# 主节点
mkdir -p /mongodb/replica_sets/rs_27017/log
mkdir -p /mongodb/replica_sets/rs_27017/data/db
- 或修改配置文件
vim /mongodb/replica_sets/rs_27017/mongod.conf
systemLog:
#MongoDB发送所有日志输出的目标指定为文件
destination: file
#mongod或mongos应向其发送所有诊断日志记录信息的日志文件的路径
path: "/mongodb/replica_sets/rs_27017/log/mongod.log"
#当mongos或mongod实例重新启动时,mongos或mongod会将新条目附加到现有日志文件的末尾
logAppend: true
storage:
#mongod实例存储其数据的目录。storage.dbPath设置仅适用于mongod
dbPath: "/mongodb/replica_sets/rs_27017/data/db"
journal:
#启用或禁用持久性日志以确保数据文件保持有效和可恢复。
enabled: true
processManagement:
#启用在后台运行mongos或mongod进程的守护进程模式。
fork: true
#指定用于保存mongos或mongod进程的进程ID的文件位置,其中mongos或mongod将写入其PID
pidFilePath: "/mongodb/replica_sets/rs_27017/log/mongod.pid"
net:
#服务实例绑定所有IP,有副作用,副本集初始化的时候,节点名字会自动设置为本地域名,而不是ip
#bindIpAll: true
#服务实例绑定的IP
bindIp: localhost,192.168.30.129
#bindIp
#绑定的端口
port: 27017
replication:
#副本集的名称
replSetName: kwz_rs
- 启动节点服务:
/usr/local/mongodb/bin/mongod -f /mongodb/replica_sets/rs_27017/mongod.conf
[图片上传失败...(image-b61550-1601215146003)]
此处有一个错误:Error parsing YAML config file: yaml-cpp: error at line 2, column 13: illegal map value,涉及到yml格式空格问题,参考这篇文章解决
创建副本级节点
- 建立存放数据和日志的目录
# 主节点
mkdir -p /mongodb/replica_sets/rs_27018/log
mkdir -p /mongodb/replica_sets/rs_27018/data/db
- 或修改配置文件
vim /mongodb/replica_sets/rs_27018/mongod.conf
systemLog:
#MongoDB发送所有日志输出的目标指定为文件
destination: file
#mongod或mongos应向其发送所有诊断日志记录信息的日志文件的路径
path: "/mongodb/replica_sets/rs_27018/log/mongod.log"
#当mongos或mongod实例重新启动时,mongos或mongod会将新条目附加到现有日志文件的末尾
logAppend: true
storage:
#mongod实例存储其数据的目录。storage.dbPath设置仅适用于mongod
dbPath: "/mongodb/replica_sets/rs_27018/data/db"
journal:
#启用或禁用持久性日志以确保数据文件保持有效和可恢复。
enabled: true
processManagement:
#启用在后台运行mongos或mongod进程的守护进程模式。
fork: true
#指定用于保存mongos或mongod进程的进程ID的文件位置,其中mongos或mongod将写入其PID
pidFilePath: "/mongodb/replica_sets/rs_27018/log/mongod.pid"
net:
#服务实例绑定所有IP,有副作用,副本集初始化的时候,节点名字会自动设置为本地域名,而不是ip
#bindIpAll: true
#服务实例绑定的IP
bindIp: localhost,192.168.30.129
#bindIp
#绑定的端口
port: 27018
replication:
#副本集的名称
replSetName: kwz_rs
- 启动节点服务:
/usr/local/mongodb/bin/mongod -f /mongodb/replica_sets/rs_27018/mongod.conf
[图片上传失败...(image-ea8248-1601215146004)]
创建仲裁节点
- 建立存放数据和日志的目录
# 主节点
mkdir -p /mongodb/replica_sets/rs_27019/log
mkdir -p /mongodb/replica_sets/rs_27019/data/db
- 或修改配置文件
vim /mongodb/replica_sets/rs_27019/mongod.conf
systemLog:
#MongoDB发送所有日志输出的目标指定为文件
destination: file
#mongod或mongos应向其发送所有诊断日志记录信息的日志文件的路径
path: "/mongodb/replica_sets/rs_27018/log/mongod.log"
#当mongos或mongod实例重新启动时,mongos或mongod会将新条目附加到现有日志文件的末尾
logAppend: true
storage:
#mongod实例存储其数据的目录。storage.dbPath设置仅适用于mongod
dbPath: "/mongodb/replica_sets/rs_27018/data/db"
journal:
#启用或禁用持久性日志以确保数据文件保持有效和可恢复。
enabled: true
processManagement:
#启用在后台运行mongos或mongod进程的守护进程模式。
fork: true
#指定用于保存mongos或mongod进程的进程ID的文件位置,其中mongos或mongod将写入其PID
pidFilePath: "/mongodb/replica_sets/rs_27018/log/mongod.pid"
net:
#服务实例绑定所有IP,有副作用,副本集初始化的时候,节点名字会自动设置为本地域名,而不是ip
#bindIpAll: true
#服务实例绑定的IP
bindIp: localhost,192.168.30.129
#bindIp
#绑定的端口
port: 27018
replication:
#副本集的名称
replSetName: kwz_rs
- 启动节点服务:
/usr/local/mongodb/bin/mongod -f /mongodb/replica_sets/rs_27018/mongod.conf
[图片上传失败...(image-1c606c-1601215146004)]
初始化配置副本集和主节点
用客户端连接主节点(27017)
/usr/local/mongodb/bin/mongo --host=localhost --port=27017
连接成功之后,许多命令不能用,需要初始化副本集才行,使用默认的配置来初始化副本集:
rs.initiate()
{
"info2" : "no configuration specified. Using a default configuration for the set",
"me" : "localhost:27017",
"ok" : 1,
"$clusterTime" : {
"clusterTime" : Timestamp(1601049270, 1),
"signature" : {
"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
"keyId" : NumberLong(0)
}
},
"operationTime" : Timestamp(1601049270, 1)
}
kwz_rs:SECONDARY>
kwz_rs:SECONDARY>
kwz_rs:PRIMARY>
- “ok”的值为1则说明创建成功
- 命令行提示符发生变化,变成了一个从节点角色,此时默认不能读写。稍等片刻,回车,变成主节 点,如上图显示的
这时候执行show dbs就有数据了:
kwz_rs:PRIMARY> show dbs;
admin 0.000GB
config 0.000GB
local 0.000GB
查看副本集的配置
在27017上执行副本集中当前节点的默认节点配置
kwz_rs:PRIMARY> rs.config()
{
# 副本集配置数据存储的主键值,默认就是副本集的名字
"_id" : "kwz_rs",
"version" : 1,
"protocolVersion" : NumberLong(1),
"writeConcernMajorityJournalDefault" : true,
# 副本集成员数组,此时只有27017一个
"members" : [
{
"_id" : 0,
"host" : "localhost: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("5f6e12b681a01e73db9b210a")
}
}
值得注意的是 副本集rs.conf()的查看命令,本质查询的是 system.replset 的表中的数据,它是在local库下面
查看副本集状态
检查副本集状态,从其他成员发送的心跳包中获得的数据反映副本集的当前状态,通过rs.status()命令
在27017上查看副本集状态:
kwz_rs:PRIMARY> rs.status()
{
# 副本集的名字
"set" : "kwz_rs",
"date" : ISODate("2020-09-25T16:38:25.293Z"),
# 状态正常
"myState" : 1,
"term" : NumberLong(1),
"syncingTo" : "",
"syncSourceHost" : "",
"syncSourceId" : -1,
"heartbeatIntervalMillis" : NumberLong(2000),
"majorityVoteCount" : 1,
"writeMajorityCount" : 1,
"optimes" : {
"lastCommittedOpTime" : {
"ts" : Timestamp(1601051900, 1),
"t" : NumberLong(1)
},
"lastCommittedWallTime" : ISODate("2020-09-25T16:38:20.800Z"),
"readConcernMajorityOpTime" : {
"ts" : Timestamp(1601051900, 1),
"t" : NumberLong(1)
},
"readConcernMajorityWallTime" : ISODate("2020-09-25T16:38:20.800Z"),
"appliedOpTime" : {
"ts" : Timestamp(1601051900, 1),
"t" : NumberLong(1)
},
"durableOpTime" : {
"ts" : Timestamp(1601051900, 1),
"t" : NumberLong(1)
},
"lastAppliedWallTime" : ISODate("2020-09-25T16:38:20.800Z"),
"lastDurableWallTime" : ISODate("2020-09-25T16:38:20.800Z")
},
"lastStableRecoveryTimestamp" : Timestamp(1601051850, 1),
"lastStableCheckpointTimestamp" : Timestamp(1601051850, 1),
"electionCandidateMetrics" : {
"lastElectionReason" : "electionTimeout",
"lastElectionDate" : ISODate("2020-09-25T15:54:30.537Z"),
"electionTerm" : NumberLong(1),
"lastCommittedOpTimeAtElection" : {
"ts" : Timestamp(0, 0),
"t" : NumberLong(-1)
},
"lastSeenOpTimeAtElection" : {
"ts" : Timestamp(1601049270, 1),
"t" : NumberLong(-1)
},
"numVotesNeeded" : 1,
"priorityAtElection" : 1,
"electionTimeoutMillis" : NumberLong(10000),
"newTermStartDate" : ISODate("2020-09-25T15:54:30.567Z"),
"wMajorityWriteAvailabilityDate" : ISODate("2020-09-25T15:54:30.580Z")
},
# 副本集成员数组
"members" : [
{
"_id" : 0,
"name" : "localhost:27017",
# 健康
"health" : 1,
"state" : 1,
# 主节点
"stateStr" : "PRIMARY",
"uptime" : 89235,
"optime" : {
"ts" : Timestamp(1601051900, 1),
"t" : NumberLong(1)
},
"optimeDate" : ISODate("2020-09-25T16:38:20Z"),
"syncingTo" : "",
"syncSourceHost" : "",
"syncSourceId" : -1,
"infoMessage" : "",
"electionTime" : Timestamp(1601049270, 2),
"electionDate" : ISODate("2020-09-25T15:54:30Z"),
"configVersion" : 1,
"self" : true,
"lastHeartbeatMessage" : ""
}
],
"ok" : 1,
"$clusterTime" : {
"clusterTime" : Timestamp(1601051900, 1),
"signature" : {
"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
"keyId" : NumberLong(0)
}
},
"operationTime" : Timestamp(1601051900, 1)
}
添加副本从节点
在主节点添加从节点,将其他成员加入到副本集中
下面将27018的副本节点添加到副本集中:
kwz_rs:PRIMARY> rs.add("localhost:27018")
{
# 说明添加成功
"ok" : 1,
"$clusterTime" : {
"clusterTime" : Timestamp(1601053665, 1),
"signature" : {
"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
"keyId" : NumberLong(0)
}
},
"operationTime" : Timestamp(1601053665, 1)
}
此时再次查看副本集状态:
kwz_rs:PRIMARY> rs.status()
{
"set" : "kwz_rs",
"date" : ISODate("2020-09-25T17:09:34.680Z"),
"myState" : 1,
"term" : NumberLong(1),
"syncingTo" : "",
"syncSourceHost" : "",
"syncSourceId" : -1,
"heartbeatIntervalMillis" : NumberLong(2000),
"majorityVoteCount" : 2,
"writeMajorityCount" : 2,
"optimes" : {
"lastCommittedOpTime" : {
"ts" : Timestamp(1601053770, 1),
"t" : NumberLong(1)
},
"lastCommittedWallTime" : ISODate("2020-09-25T17:09:30.959Z"),
"readConcernMajorityOpTime" : {
"ts" : Timestamp(1601053770, 1),
"t" : NumberLong(1)
},
"readConcernMajorityWallTime" : ISODate("2020-09-25T17:09:30.959Z"),
"appliedOpTime" : {
"ts" : Timestamp(1601053770, 1),
"t" : NumberLong(1)
},
"durableOpTime" : {
"ts" : Timestamp(1601053770, 1),
"t" : NumberLong(1)
},
"lastAppliedWallTime" : ISODate("2020-09-25T17:09:30.959Z"),
"lastDurableWallTime" : ISODate("2020-09-25T17:09:30.959Z")
},
"lastStableRecoveryTimestamp" : Timestamp(1601053770, 1),
"lastStableCheckpointTimestamp" : Timestamp(1601053770, 1),
"electionCandidateMetrics" : {
"lastElectionReason" : "electionTimeout",
"lastElectionDate" : ISODate("2020-09-25T15:54:30.537Z"),
"electionTerm" : NumberLong(1),
"lastCommittedOpTimeAtElection" : {
"ts" : Timestamp(0, 0),
"t" : NumberLong(-1)
},
"lastSeenOpTimeAtElection" : {
"ts" : Timestamp(1601049270, 1),
"t" : NumberLong(-1)
},
"numVotesNeeded" : 1,
"priorityAtElection" : 1,
"electionTimeoutMillis" : NumberLong(10000),
"newTermStartDate" : ISODate("2020-09-25T15:54:30.567Z"),
"wMajorityWriteAvailabilityDate" : ISODate("2020-09-25T15:54:30.580Z")
},
# 副本集成员数组中现在有两个了
"members" : [
{
"_id" : 0,
"name" : "localhost:27017",
"health" : 1,
"state" : 1,
"stateStr" : "PRIMARY",
"uptime" : 91104,
"optime" : {
"ts" : Timestamp(1601053770, 1),
"t" : NumberLong(1)
},
"optimeDate" : ISODate("2020-09-25T17:09:30Z"),
"syncingTo" : "",
"syncSourceHost" : "",
"syncSourceId" : -1,
"infoMessage" : "",
"electionTime" : Timestamp(1601049270, 2),
"electionDate" : ISODate("2020-09-25T15:54:30Z"),
"configVersion" : 2,
"self" : true,
"lastHeartbeatMessage" : ""
},
{
"_id" : 1,
# 第二个节点的名字
"name" : "localhost:27018",
"health" : 1,
"state" : 2,
# 它的角色是SECONDARY
"stateStr" : "SECONDARY",
"uptime" : 109,
"optime" : {
"ts" : Timestamp(1601053770, 1),
"t" : NumberLong(1)
},
"optimeDurable" : {
"ts" : Timestamp(1601053770, 1),
"t" : NumberLong(1)
},
"optimeDate" : ISODate("2020-09-25T17:09:30Z"),
"optimeDurableDate" : ISODate("2020-09-25T17:09:30Z"),
"lastHeartbeat" : ISODate("2020-09-25T17:09:33.236Z"),
"lastHeartbeatRecv" : ISODate("2020-09-25T17:09:34.403Z"),
"pingMs" : NumberLong(0),
"lastHeartbeatMessage" : "",
"syncingTo" : "localhost:27017",
"syncSourceHost" : "localhost:27017",
"syncSourceId" : 0,
"infoMessage" : "",
"configVersion" : 2
}
],
"ok" : 1,
"$clusterTime" : {
"clusterTime" : Timestamp(1601053770, 1),
"signature" : {
"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
"keyId" : NumberLong(0)
}
},
"operationTime" : Timestamp(1601053770, 1)
}
添加仲裁从节点
将27019作为仲裁节点,添加到副本集中:
kwz_rs:PRIMARY> rs.addArb("localhost:27019")
{
"ok" : 1,
"$clusterTime" : {
"clusterTime" : Timestamp(1601054101, 1),
"signature" : {
"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
"keyId" : NumberLong(0)
}
},
"operationTime" : Timestamp(1601054101, 1)
}
查看副本集状态:
kwz_rs:PRIMARY> rs.status()
{
"set" : "kwz_rs",
"date" : ISODate("2020-09-25T17:15:46.933Z"),
"myState" : 1,
"term" : NumberLong(1),
"syncingTo" : "",
"syncSourceHost" : "",
"syncSourceId" : -1,
"heartbeatIntervalMillis" : NumberLong(2000),
"majorityVoteCount" : 2,
"writeMajorityCount" : 2,
"optimes" : {
"lastCommittedOpTime" : {
"ts" : Timestamp(1601054140, 1),
"t" : NumberLong(1)
},
"lastCommittedWallTime" : ISODate("2020-09-25T17:15:40.991Z"),
"readConcernMajorityOpTime" : {
"ts" : Timestamp(1601054140, 1),
"t" : NumberLong(1)
},
"readConcernMajorityWallTime" : ISODate("2020-09-25T17:15:40.991Z"),
"appliedOpTime" : {
"ts" : Timestamp(1601054140, 1),
"t" : NumberLong(1)
},
"durableOpTime" : {
"ts" : Timestamp(1601054140, 1),
"t" : NumberLong(1)
},
"lastAppliedWallTime" : ISODate("2020-09-25T17:15:40.991Z"),
"lastDurableWallTime" : ISODate("2020-09-25T17:15:40.991Z")
},
"lastStableRecoveryTimestamp" : Timestamp(1601054130, 1),
"lastStableCheckpointTimestamp" : Timestamp(1601054130, 1),
"electionCandidateMetrics" : {
"lastElectionReason" : "electionTimeout",
"lastElectionDate" : ISODate("2020-09-25T15:54:30.537Z"),
"electionTerm" : NumberLong(1),
"lastCommittedOpTimeAtElection" : {
"ts" : Timestamp(0, 0),
"t" : NumberLong(-1)
},
"lastSeenOpTimeAtElection" : {
"ts" : Timestamp(1601049270, 1),
"t" : NumberLong(-1)
},
"numVotesNeeded" : 1,
"priorityAtElection" : 1,
"electionTimeoutMillis" : NumberLong(10000),
"newTermStartDate" : ISODate("2020-09-25T15:54:30.567Z"),
"wMajorityWriteAvailabilityDate" : ISODate("2020-09-25T15:54:30.580Z")
},
# 副本集成员数组中现在有三个了
"members" : [
{
"_id" : 0,
"name" : "localhost:27017",
"health" : 1,
"state" : 1,
"stateStr" : "PRIMARY",
"uptime" : 91476,
"optime" : {
"ts" : Timestamp(1601054140, 1),
"t" : NumberLong(1)
},
"optimeDate" : ISODate("2020-09-25T17:15:40Z"),
"syncingTo" : "",
"syncSourceHost" : "",
"syncSourceId" : -1,
"infoMessage" : "",
"electionTime" : Timestamp(1601049270, 2),
"electionDate" : ISODate("2020-09-25T15:54:30Z"),
"configVersion" : 3,
"self" : true,
"lastHeartbeatMessage" : ""
},
{
"_id" : 1,
"name" : "localhost:27018",
"health" : 1,
"state" : 2,
"stateStr" : "SECONDARY",
"uptime" : 481,
"optime" : {
"ts" : Timestamp(1601054140, 1),
"t" : NumberLong(1)
},
"optimeDurable" : {
"ts" : Timestamp(1601054140, 1),
"t" : NumberLong(1)
},
"optimeDate" : ISODate("2020-09-25T17:15:40Z"),
"optimeDurableDate" : ISODate("2020-09-25T17:15:40Z"),
"lastHeartbeat" : ISODate("2020-09-25T17:15:45.481Z"),
"lastHeartbeatRecv" : ISODate("2020-09-25T17:15:45.505Z"),
"pingMs" : NumberLong(0),
"lastHeartbeatMessage" : "",
"syncingTo" : "localhost:27017",
"syncSourceHost" : "localhost:27017",
"syncSourceId" : 0,
"infoMessage" : "",
"configVersion" : 3
},
{
"_id" : 2,
"name" : "localhost:27019",
"health" : 1,
"state" : 7,
"stateStr" : "ARBITER",
"uptime" : 45,
"lastHeartbeat" : ISODate("2020-09-25T17:15:45.492Z"),
"lastHeartbeatRecv" : ISODate("2020-09-25T17:15:45.510Z"),
"pingMs" : NumberLong(0),
"lastHeartbeatMessage" : "",
"syncingTo" : "",
"syncSourceHost" : "",
"syncSourceId" : -1,
"infoMessage" : "",
"configVersion" : 3
}
],
"ok" : 1,
"$clusterTime" : {
"clusterTime" : Timestamp(1601054140, 1),
"signature" : {
"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
"keyId" : NumberLong(0)
}
},
"operationTime" : Timestamp(1601054140, 1)
}
副本集数据读写测试
主节点写入和读取数据
登陆27017主节点,进行写入和读取数据操作:
/usr/local/mongodb/bin/mongo --host localhost --port 27017
kwz_rs:PRIMARY> use article
switched to db article
kwz_rs:PRIMARY> db
article
kwz_rs:PRIMARY> db.comment.insert({"articleid":"001","content":"本篇论文纯属虚构","userId":"1001","nickName":"hanhan","createDatetime":new Date()})
WriteResult({ "nInserted" : 1 })
kwz_rs:PRIMARY> db.comment.find()
{ "_id" : ObjectId("5f6f07a88c6d2fbab4118f14"), "articleid" : "001", "content" : "本篇论文纯属虚构", "userId" : "1001", "nickName" : "hanhan", "createDatetime" : ISODate("2020-09-26T09:19:36.729Z") }
如上所示,我们在27017主节点进行插入和读取操作,均正常
从节点写入和读取数据
登陆27018从节点,进行写入和读取数据操作
/usr/local/mongodb/bin/mongo --host localhost --port 27018
kwz_rs:SECONDARY> show dbs
2020-09-26T17:27:32.202+0800 E QUERY [js] uncaught exception: Error: listDatabases failed:{
"operationTime" : Timestamp(1601112444, 1),
"ok" : 0,
"errmsg" : "not master and slaveOk=false",
"code" : 13435,
"codeName" : "NotMasterNoSlaveOk",
"$clusterTime" : {
"clusterTime" : Timestamp(1601112444, 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
可以发现不能读取集合的数据,有个"errmsg" : "not master and slaveOk=false",可见当前节点只是数据的一个备份,不是slave节点,无法读取数据,写入当然更不行。默认情况下,从节点是没有读写权限的,可以增加读的权限,但需要进行设置。
设置读操作权限,允许从成员上进行读的操作:
rs.slaveOk()或者rs.slaveOk(true)
同理,如果想取消从节点的读取权限,设置为:
rs.slaveOk(false)
这时候再看一下,就有了:
kwz_rs:SECONDARY> show dbs
admin 0.000GB
article 0.000GB
config 0.000GB
local 0.000GB
kwz_rs:SECONDARY> use article
switched to db article
kwz_rs:SECONDARY> show collections
comment
kwz_rs:SECONDARY> db.comment.find()
{ "_id" : ObjectId("5f6f07a88c6d2fbab4118f14"), "articleid" : "001", "content" : "本篇论文纯属虚构", "userId" : "1001", "nickName" : "hanhan", "createDatetime" : ISODate("2020-09-26T09:19:36.729Z") }
kwz_rs:SECONDARY> db.comment.insert({"articleid":"002","content":"本篇论文纯属虚构","userId":"1001","nickName":"hanhan","createDatetime":new Date()})
WriteCommandError({
"operationTime" : Timestamp(1601112924, 1),
"ok" : 0,
"errmsg" : "not master",
"code" : 10107,
"codeName" : "NotMaster",
"$clusterTime" : {
"clusterTime" : Timestamp(1601112924, 1),
"signature" : {
"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
"keyId" : NumberLong(0)
}
}
})
仲裁节点查看
登陆27019仲裁节点
kwz_rs:ARBITER> rs.slaveOk()
kwz_rs:ARBITER> show dbs
local 0.000GB
kwz_rs:ARBITER> use local
switched to db local
kwz_rs:ARBITER> show collections
replset.election
replset.minvalid
replset.oplogTruncateAfterPoint
startup_log
system.replset
system.rollback.id
kwz_rs:ARBITER>
可见仲裁节点不存放任何业务数据,只存放了副本集配置等数据
主节点的选举原则
MongoDB在副本集中,会自动进行主节点的选举,触发主节点选举的条件主要有三个,分别为:
- 主节点故障
- 主节点网络不可达(默认心跳时间为10s,超过10s即认为网络不可达)
- 人工干预
一旦触发选举 ,会根据一定规则进行主节点的选举,选举规则是根据票数的多少来决定的
- 票数最高,则获胜
- 若票数相同,且都获得了“大多数”成员的投票支持的,此时,数据新的节点会获胜,数据的新旧是通过操作日志oplog进行对比的
副本集配置中的priority(优先级)对投票选举影响极大,一般情况下默认为1,它的可选范围是0-1000,相当于额外增加了0-1000票数,指定较高的值,使成员更有资格成为主要成员,更低的值可使成员更不符合条件, 通过rs.conf()可以看到集群优先级的配置。一般主节点和副本节点的优先级各为1,默认都有一票,而仲裁节点的优先级必须是0,不能为别的值。它不具备选举权,但是具备投票权(这也很好理解,仲裁节点你都没有数据,怎么可能给你选举权呢)。
优先级的设置
- 查看副本级配置
kwz_rs:PRIMARY> rs.conf()
{
"_id" : "kwz_rs",
"version" : 3,
"protocolVersion" : NumberLong(1),
"writeConcernMajorityJournalDefault" : true,
"members" : [
{
"_id" : 0,
"host" : "localhost:27017",
"arbiterOnly" : false,
"buildIndexes" : true,
"hidden" : false,
"priority" : 1,
"tags" : {
},
"slaveDelay" : NumberLong(0),
"votes" : 1
},
{
"_id" : 1,
"host" : "localhost:27018",
"arbiterOnly" : false,
"buildIndexes" : true,
"hidden" : false,
"priority" : 1,
"tags" : {
},
"slaveDelay" : NumberLong(0),
"votes" : 1
},
{
"_id" : 2,
"host" : "localhost: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("5f6e12b681a01e73db9b210a")
}
}
可以看到27018副本级节点原来的优先级1,现在我们将它改为2,让它默认有2票
- 将配置导入conf_temp
conf_temp=rs.config()
- 将27018副本节点优先级设为2(这里数组的index默认是从0开始的)
conf_temp.members[1].priority=2
- 重新加载conf_temp配置
kwz_rs:PRIMARY> rs.reconfig(conf_temp)
{
"ok" : 1,
"$clusterTime" : {
"clusterTime" : Timestamp(1601130737, 1),
"signature" : {
"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
"keyId" : NumberLong(0)
}
},
"operationTime" : Timestamp(1601130737, 1)
}
- 再次查看副本集配置信息
kwz_rs:PRIMARY> rs.conf()
{
"_id" : "kwz_rs",
"version" : 4,
"protocolVersion" : NumberLong(1),
"writeConcernMajorityJournalDefault" : true,
"members" : [
{
"_id" : 0,
"host" : "localhost:27017",
"arbiterOnly" : false,
"buildIndexes" : true,
"hidden" : false,
"priority" : 1,
"tags" : {
},
"slaveDelay" : NumberLong(0),
"votes" : 1
},
{
"_id" : 1,
"host" : "localhost:27018",
"arbiterOnly" : false,
"buildIndexes" : true,
"hidden" : false,
"priority" : 2,
"tags" : {
},
"slaveDelay" : NumberLong(0),
"votes" : 1
},
{
"_id" : 2,
"host" : "localhost: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("5f6e12b681a01e73db9b210a")
}
}
可以看到27018节点的优先级已经变为2了
高可用测试
副本节点选举为主节点测试
如上所示,当副本节点的priority大于主节点的priority,此时,27017的主节点自动蜕变为副本节点,而27018的副本节点被选举为主节点,完成切换
副本节点故障测试
关闭27018副本节点、此时主节点还在,没有触发选举操作。此时,往主节点里面插入数据:
db.comment.insert({"_id":"4","articleid":"100001","content":"秋天里的第一杯奶茶","userId":"1002","nickName":"去苏州一起喝","createDatetime":new Date("2019-0805T22:08:15.522Z"),"likeNum":NumberInt(1000)})
WriteResult({ "nInserted" : 1 })
再次启动27018副本节点:
/usr/local/mongodb/bin/mongod -f /mongodb/replica_sets/rs_27018/mongod.conf
此时,之前27017主节点同步的数据同步过来了
kwz_rs:SECONDARY> db.comment.find()
{ "_id" : "4", "articleid" : "100001", "content" : "秋天里的第一杯奶茶", "userId" : "1002", "nickName" : "去苏州一起喝", "createDatetime" : ISODate("1970-01-01T00:00:00Z"), "likeNum" : 1000 }
{ "_id" : ObjectId("5f6f5dc39ef50a5bad627ea5") }
{ "_id" : ObjectId("5f6f5db29ef50a5bad627ea4") }
主节点故障测试
此时关闭27017主节点,从节点和仲裁节点对27017的心跳失败,当失败超过10秒,此时因为没有主节点了,会自动发起投票进行主节点的选取,副本节点只有27018,27019仲裁节点只有投票权没有选举权,因此27019仲裁节点投一票给27018,27018自带一票,共两票。被选举为主节点,此时具备了读写功能,如下:
kwz_rs:PRIMARY> db.comment.insert({"_id":"5","articleid":"100001","content":"秋天里的第一杯奶茶","userId":"1002","nickName":"去苏州一起喝","createDatetime":new Date("2019-0805T22:08:15.522Z"),"likeNum":NumberInt(1000)})
WriteResult({ "nInserted" : 1 })
仲裁节点和主节点故障测试
演示仲裁节点和主节点故障,步骤如下:
- 先恢复一主一从一仲裁
- 关掉仲裁节点27019
- 关掉主节点27018
此时登陆27017节点,发现27017还是从节点,副本集现在没有主节点了,现在副本集是只读状态,无法进行写入操作了
此时如果想触发主节点的选举,再加入一个成员即可
- 如果只加入27019仲裁节点成员,则主节点一定是27017,因为没得选了,仲裁节点不参与选举, 但参与投票
- 如果只加入27018节点,会发起选举。因为27017和27018都是两票,则按照谁数据新,谁当主节点
仲裁节点和从节点故障测试
- 先恢复一主一从一仲裁
- 关掉仲裁节点27019
- 关掉现在的副本节点27018
等待10s后,27017主节点自动降级为副本节点了。
小结
本篇文章主要介绍了Linux环境下mongoDB 4副本集模式搭建步骤以及它的选举规则,在搭建完之后又分别进行了高可用测试。
参考:
- https://www.bilibili.com/video/BV14Z4y1p7Xu?p=29
- https://github.com/nuptkwz/notes/tree/master/technology/mongo