自建 MongoDB 实践:MongoDB 复制集
新钛云服已累计为您分享721篇技术干货
· MongoDB 的安装及基本使用 (点击进入)
· MongoDB 文档查询 (点击进入)
· MongoDB 复制集的介绍及搭建 (本期内容)
· MongoDB 分片集群的介绍及搭建 (后续更新)
· MongoDB 的备份及恢复 (后续更新)
· MongoDB 安全加密 (后续更新)
· MongoDB Change Stream 功能介绍及代码演示 (后续更新)
· MongoDB 其他(后续更新)
复制集架构概览
mongod
进程一起维护相同的数据集。复制集提供了冗余及高可用。下面的几幅图是 MongoDB 复制集的工作原理:从 MongoDB 3.6 开始,如果客户端驱动程序检测到主操作已关闭,则可以一次重试写操作。复制集最多可以有 50 名节点,但其中最多只能有 7 名节点可以在选举过程中投票。
复制集是如何选举的
electionTimeoutMillis
时间(默认时间为 10 秒)后,然后从节点将开始一轮或多轮选举,以选举产生新的主节点。属于拥有 50%+1 选票 成为这个组中最新的从节点
数据是如何复制的
当一个修改操作(增、删、改)到达主节点时,主节点对数据的操作将会记录下来,这些操作记录称之为
oplog
从节点通过在主节点上打开一个
tailable
游标不断获取新进入主节点的oplog
,并在自己的数据上回放,以此保持跟主节点的数据一致
具有投票的节点之间的两两互相发送心跳
当 5 次心跳未收到时,判断为节点失联
如果失联的主节点,从节点会发起选举,选出新的节点
如果失联的从节点,则不会产生新的选举
选举基于 RAFT一致性算法实现,选举成功的必要条件是大多数投票节点存活
复制集中最多可以有 50 个节点,但具有投票权的节点最多 7 个
复制集的优势
提供数据冗余,供恢复时使用 读扩展,客户端可以从多个从节点进行数据的读取 灾难恢复 避免停机维护
配置复制集
主机名 | IP | |
准备工作
[root@mongo01 ~]# openssl rand -base64 756 > /etc/mongod.keyfile
[root@mongo01 ~]# cat /etc/mongod.keyfile
k+iOUxLSaYYGlclb379Ehb/AvsIGt8GaALW48IQ6QZFzT4DaFVn/T3tuiOXREgza
3EyjxqW31ReXcPhFeePRsjADw00DrEqD3iO+pHW6ZVMMQHcrIPdm4wkUKkWY7jkh
611NtKHfZKHY0QXfsrCKAn5uQXyswg6gmmssXzfsz2TEBr1R5z5n0GFz7GogOr9y
j1vYRE5NaikVLQxMSrJa2yJVGK28y/s2FJ0FB5R/z9OEeKG7LET/hqgRidztiLaD
ZEEaQU+xUPpsoUW3qF7oNlkYH4u9YBSRq8xu37GdfIgZjyQXol0TckXfFBNkn/F0
zyXRIiwtMk2ehc2kQV/n9xXMkd3vemLMTZN+aSoEPxvE4jojU5BHMS0c1Hjnpc68
D+A3NpqvXlC6u9D5XWoBJgdcVsEHgwTpOwCsk621hMDNs2/vMDnEDhjI4fat/F5x
G2ECUmL1yNQ4ls5O3BtSJwbuxqU7uXfC1wVWwAcBaeKbGOQQOvCxvx2jNIGk0zLH
XdoaXbiiGJyaIbEqstsZyYjX9rI7Br4K1KuLbKoxLhl5xJRDNFe0LAHGwXHf6YkR
9QTZwpLrbl6HbTq0MkiGsc643BfGgHgbm28ECwKe1HGABnmnrqbmo7QzFvxwHI8E
wWud2WbC/GKexSLLM50HSAU7FTbjiYjS2afIbBxVV1raKoDpbnPkRgbpXBORIP+b
1f38CAI17vGbLllaoPrB2p6bR/jR+GZhCTK4zjq1g5ssxRCbYnuH7M6x/owTIp4s
zpli6rhnpcWJGwvmpMUyeQBrrIevRZjjwSNaGACdPg52xqapx9ZMyKip0ALmOEHl
g4h4cDyiiPepbh8dz2hj/3/9RtdhyfFrMebBWImxZehVghtQR9Tp2znTBtn73TQa
ElcYK49kMn3lrMw2uqCGXBWKGQvStPR9YnRwHaQ40FjVx6YR1kv3T6kzwlKdlNZA
iMyi5u9TBCLbO+ziUKQiNX+ErJS0Qujtw0G1REBOVcqP9Wl0
密钥的长度必须在 6 到 1024 个字符之间,并且只能包含 base64 集中的字符。
[root@mongo01 ~]# chmod 400 /etc/mongod.keyfile
[root@mongo01 ~]# chown mongod. /etc/mongod.keyfile
[root@mongo01 ~]# scp /etc/mongod.keyfile mongo02.tyun.cn:/etc/
[root@mongo01 ~]# scp /etc/mongod.keyfile mongo03.tyun.cn:/etc/
[root@mongo01 ~]# ssh mongo02.tyun.cn -- chmod 400 /etc/mongod.keyfile
[root@mongo01 ~]# ssh mongo03.tyun.cn -- chmod 400 /etc/mongod.keyfile
[root@mongo01 ~]# ssh mongo02.tyun.cn -- chown mongod. /etc/mongod.keyfile
[root@mongo01 ~]# ssh mongo03.tyun.cn -- chown mongod. /etc/mongod.keyfile
[root@mongo01 ~]# ssh mongo02.tyun.cn -- ls -l /etc/mongod.keyfile
-r-------- 1 mongod mongod 1024 Aug 2 07:51 /etc/mongod.keyfile
[root@mongo01 ~]# ssh mongo03.tyun.cn -- ls -l /etc/mongod.keyfile
-r-------- 1 mongod mongod 1024 Aug 2 07:50 /etc/mongod.keyfile
/var/lib/mongo
。确保三台机器的配置一样,只有 bind_ip
不一样。配置分别如下(仅以第一台配置为例,其他节点的配置需要按需修改 bindIp
配置参数):[root@mongo01 ~]# egrep -v "(^#|^$)" /etc/mongod.conf
systemLog:
destination: file
logAppend: true
path: /var/log/mongodb/mongod.log
storage:
dbPath: /var/lib/mongo
journal:
enabled: true
processManagement:
fork: true # fork and run in background
pidFilePath: /var/run/mongodb/mongod.pid # location of pidfile
timeZoneInfo: /usr/share/zoneinfo
net:
port: 27017
bindIp: 127.0.0.1,10.20.20.19
security:
authorization: enabled
keyFile: /etc/mongod.keyfile
replication:
replSetName: rs0
[root@mongo01 ~]# systemctl start mongod
[root@mongo02 ~]# systemctl start mongod
[root@mongo03 ~]# systemctl start mongod
[root@mongo01 ~]# mongo --authenticationDatabase "admin" -u "root" -p
MongoDB shell version v4.4.15
Enter password: xxxxxxxx # 在此输入密码
> rs.initiate({
_id: "rs0",
members: [{
_id: 0,
host: "mongo01.tyun.cn:27017"
},{
_id: 1,
host: "mongo02.tyun.cn:27017"
},{
_id: 2,
host: "mongo03.tyun.cn:27017"
}]
})
{ "ok" : 1 }
# 注意观察提示符已经发生了变化
rs0:SECONDARY>
rs0:SECONDARY>
......
rs0:SECONDARY>
rs0:SECONDARY>
rs0:PRIMARY>
>
",进行了复制集的初始化后,提示变成了 rs0:SECONDARY
,这个时候所有的节点都是从节点角色,因为要进行一轮或多轮的投票选举。过了一段时间,有一个节点(mongo01)经过投票选举被选为了主节点,所以它的提示符变成了 rs0:PRIMARY
。rs0:PRIMARY> rs.status()
{
"set" : "rs0",
"date" : ISODate("2022-08-02T08:04:34.878Z"),
"myState" : 1,
"term" : NumberLong(1),
"syncSourceHost" : "",
"syncSourceId" : -1,
"heartbeatIntervalMillis" : NumberLong(2000),
"majorityVoteCount" : 2,
"writeMajorityCount" : 2,
"votingMembersCount" : 3,
"writableVotingMembersCount" : 3,
"optimes" : {
"lastCommittedOpTime" : {
"ts" : Timestamp(1659427469, 1),
"t" : NumberLong(1)
},
"lastCommittedWallTime" : ISODate("2022-08-02T08:04:29.498Z"),
"readConcernMajorityOpTime" : {
"ts" : Timestamp(1659427469, 1),
"t" : NumberLong(1)
},
"readConcernMajorityWallTime" : ISODate("2022-08-02T08:04:29.498Z"),
"appliedOpTime" : {
"ts" : Timestamp(1659427469, 1),
"t" : NumberLong(1)
},
"durableOpTime" : {
"ts" : Timestamp(1659427469, 1),
"t" : NumberLong(1)
},
"lastAppliedWallTime" : ISODate("2022-08-02T08:04:29.498Z"),
"lastDurableWallTime" : ISODate("2022-08-02T08:04:29.498Z")
},
"lastStableRecoveryTimestamp" : Timestamp(1659427439, 1),
"electionCandidateMetrics" : {
"lastElectionReason" : "electionTimeout",
"lastElectionDate" : ISODate("2022-08-02T07:59:59.469Z"),
"electionTerm" : NumberLong(1),
"lastCommittedOpTimeAtElection" : {
"ts" : Timestamp(0, 0),
"t" : NumberLong(-1)
},
"lastSeenOpTimeAtElection" : {
"ts" : Timestamp(1659427188, 1),
"t" : NumberLong(-1)
},
"numVotesNeeded" : 2,
"priorityAtElection" : 1,
"electionTimeoutMillis" : NumberLong(10000),
"numCatchUpOps" : NumberLong(0),
"newTermStartDate" : ISODate("2022-08-02T07:59:59.486Z"),
"wMajorityWriteAvailabilityDate" : ISODate("2022-08-02T08:00:00.622Z")
},
"members" : [
{
"_id" : 0,
"name" : "mongo01.tyun.cn:27017",
"health" : 1,
"state" : 1,
"stateStr" : "PRIMARY",
"uptime" : 1701,
"optime" : {
"ts" : Timestamp(1659427469, 1),
"t" : NumberLong(1)
},
"optimeDate" : ISODate("2022-08-02T08:04:29Z"),
"lastAppliedWallTime" : ISODate("2022-08-02T08:04:29.498Z"),
"lastDurableWallTime" : ISODate("2022-08-02T08:04:29.498Z"),
"syncSourceHost" : "",
"syncSourceId" : -1,
"infoMessage" : "",
"electionTime" : Timestamp(1659427199, 1),
"electionDate" : ISODate("2022-08-02T07:59:59Z"),
"configVersion" : 1,
"configTerm" : 1,
"self" : true,
"lastHeartbeatMessage" : ""
},
{
"_id" : 1,
"name" : "mongo02.tyun.cn:27017",
"health" : 1,
"state" : 2,
"stateStr" : "SECONDARY",
"uptime" : 286,
"optime" : {
"ts" : Timestamp(1659427469, 1),
"t" : NumberLong(1)
},
"optimeDurable" : {
"ts" : Timestamp(1659427469, 1),
"t" : NumberLong(1)
},
"optimeDate" : ISODate("2022-08-02T08:04:29Z"),
"optimeDurableDate" : ISODate("2022-08-02T08:04:29Z"),
"lastAppliedWallTime" : ISODate("2022-08-02T08:04:29.498Z"),
"lastDurableWallTime" : ISODate("2022-08-02T08:04:29.498Z"),
"lastHeartbeat" : ISODate("2022-08-02T08:04:33.506Z"),
"lastHeartbeatRecv" : ISODate("2022-08-02T08:04:33.006Z"),
"pingMs" : NumberLong(0),
"lastHeartbeatMessage" : "",
"syncSourceHost" : "mongo01.tyun.cn:27017",
"syncSourceId" : 0,
"infoMessage" : "",
"configVersion" : 1,
"configTerm" : 1
},
{
"_id" : 2,
"name" : "mongo03.tyun.cn:27017",
"health" : 1,
"state" : 2,
"stateStr" : "SECONDARY",
"uptime" : 286,
"optime" : {
"ts" : Timestamp(1659427469, 1),
"t" : NumberLong(1)
},
"optimeDurable" : {
"ts" : Timestamp(1659427469, 1),
"t" : NumberLong(1)
},
"optimeDate" : ISODate("2022-08-02T08:04:29Z"),
"optimeDurableDate" : ISODate("2022-08-02T08:04:29Z"),
"lastAppliedWallTime" : ISODate("2022-08-02T08:04:29.498Z"),
"lastDurableWallTime" : ISODate("2022-08-02T08:04:29.498Z"),
"lastHeartbeat" : ISODate("2022-08-02T08:04:33.507Z"),
"lastHeartbeatRecv" : ISODate("2022-08-02T08:04:33.006Z"),
"pingMs" : NumberLong(0),
"lastHeartbeatMessage" : "",
"syncSourceHost" : "mongo01.tyun.cn:27017",
"syncSourceId" : 0,
"infoMessage" : "",
"configVersion" : 1,
"configTerm" : 1
}
],
"ok" : 1,
"$clusterTime" : {
"clusterTime" : Timestamp(1659427469, 1),
"signature" : {
"hash" : BinData(0,"MUcQYY28dUYGQB+XmqfVO7kUMVU="),
"keyId" : NumberLong("7127185549797883908")
}
},
"operationTime" : Timestamp(1659427469, 1)
}
members
信息:rs0:PRIMARY> var members = rs.status().members
rs0:PRIMARY> for (i = 0; i < members.length; i++) {
if (members[i].stateStr === "PRIMARY") { continue }
print("Secondary:", members[i].name, "SyncSource:", members[i].syncSourceHost)
}
Secondary: mongo02.tyun.cn:27017 SyncSource: mongo01.tyun.cn:27017
Secondary: mongo03.tyun.cn:27017 SyncSource: mongo01.tyun.cn:27017
mongo02.tyun.cn:27017
及 mongo03.tyun.cn:27017
分别从主节点 mongo01.tyun.cn:27017
上进行数据的同步。读首选项
读首选项的模式 | 描述 |
链接字符串的形式: mongodb://db0.example.com,db1.example.com,db2.example.com/?replicaSet=myRepl&readPreference=secondary
Mongo shell 的形式: db.collection.find().readPref("secondary")
连接复制集
连接复制集与连接单个节点是没有什么区别的。
[root@mongo01 ~]# mongo --authenticationDatabase "admin" -u "tyun" -p
MongoDB shell version v4.4.15
Enter password: xxxxxx # 在此输入密码
connecting to: mongodb://127.0.0.1:27017/?authSource=admin&compressors=disabled&gssapiServiceName=mongodb
Implicit session: session { "id" : UUID("d325d424-b594-4e5d-903f-407e185ad3f6") }
MongoDB server version: 4.4.15
rs0:PRIMARY> show dbs
admin 0.000GB
config 0.000GB
local 0.000GB
order 0.000GB
test 0.000GB
rs0:PRIMARY> use test
switched to db test
rs0:PRIMARY> show tables
customer
inventory
oscars
rs0:PRIMARY> db.books.insert({name: "云迁移专家", price: 100})
WriteResult({ "nInserted" : 1 })
[root@mongo01 ~]# mongo --host 10.20.20.11:27017 --authenticationDatabase "admin" -u "tyun" -p
MongoDB shell version v4.4.15
Enter password: xxxxx # 在此输入密码
connecting to: mongodb://10.20.20.11:27017/?authSource=admin&compressors=disabled&gssapiServiceName=mongodb
Implicit session: session { "id" : UUID("f958dbce-8de4-41b7-a037-22565ff0f914") }
MongoDB server version: 4.4.15
rs0:SECONDARY>
rs0:SECONDARY> show dbs
uncaught exception: Error: listDatabases failed:{
"topologyVersion" : {
"processId" : ObjectId("62e8d7f431935e1af2ac1825"),
"counter" : NumberLong(4)
},
"operationTime" : Timestamp(1659428959, 1),
"ok" : 0,
"errmsg" : "not master and slaveOk=false",
"code" : 13435,
"codeName" : "NotPrimaryNoSecondaryOk",
"$clusterTime" : {
"clusterTime" : Timestamp(1659428959, 1),
"signature" : {
"hash" : BinData(0,"9x5C9QDI8H8S8dvEekvyCn7DS10="),
"keyId" : NumberLong("7127185549797883908")
}
}
} :
_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
rs0:SECONDARY> rs.secondaryOk() # 这是设置命令,之前的命令是 rs.slaveOk()
rs0:SECONDARY> show dbs
admin 0.000GB
config 0.000GB
local 0.000GB
order 0.000GB
test 0.000GB
# 查看 books 表中的数据
rs0:SECONDARY> db.books.find()
{ "_id" : ObjectId("62e8e024229630850e8feb36"), "name" : "云迁移专家", "price" : 100 }
rs0:SECONDARY> db.books.find().pretty()
{
"_id" : ObjectId("62e8e024229630850e8feb36"),
"name" : "云迁移专家",
"price" : 100
}
rs0:PRIMARY> db.books.insert({name: "云迁移最佳实践", price: 999})
WriteResult({ "nInserted" : 1 })
# 从节点进行查看
rs0:SECONDARY> db.books.find().pretty()
{
"_id" : ObjectId("62e8e024229630850e8feb36"),
"name" : "云迁移专家",
"price" : 100
}
{
"_id" : ObjectId("62e8e1aa229630850e8feb37"),
"name" : "云迁移最佳实践",
"price" : 999
}
复制集的日常操作
节点优先级设置
var conf = rs.conf()
// 将 0 号节点的优先级调整为 10
conf.members[0].priority = 10;
// 将 1 号节点调整为 hidden 节点
conf.members[1].hidden = true;
// hidden 节点必须配置{priority: 0}
conf.members[1].priority = 0;
// 应用以上调整
rs.reconfig(conf);
当要对复制集中的节点进行维护时,优先级的设置就派上用场了。
> cfg = rs.conf()
> cfg.members[0].priority = 0.778
> cfg.members[1].priority = 999.9999
每个节点的默认优先级为1。数值是浮点精度,优先级可以从 0(永远不会成为主)到 1000。
设置 0 优先级的复制集节点
> var conf = rs.conf()
// 将 0 号节点的优先级调整为 10
> conf.members[0].priority = 10;
// 应用以上调整
> rs.reconfig(conf);
确保每个节点都运行相同版本的 MongoDB,否则可能会出现意外行为。避免在业务高峰期重新配置复制集集群。重新配置复制集可能会迫使选举新的主节点,这将关闭所有活跃的连接,并可能导致 10-30 秒的停机时间。尝试确定最低流量时间窗口,以运行重新配置等维护操作,并始终有一个恢复计划,以防出现问题。
隐藏复制集节点
db.isMaster()
mongo shell 命令和类似的管理命令中,并且出于所有目的,客户端不会考虑(即读取首选项)。> cfg = rs.conf()
> cfg.members[0].priority = 0
> cfg.members[0].hidden = true
> rs.reconfig(cfg)
延迟复制集节点
priority = 0
及 hidden = true
。延迟复制节点可以参加选举,但是对客户端不可见并且不会被选举为主节点。设置如下:> cfg = rs.conf()
> cfg.members[0].priority = 0
> cfg.members[0].hidden = true
> cfg.members[0].slaveDelay = 7200
> rs.reconfig(cfg)
改变 oplog 大小
// 查看 oplog 大小
rs0:PRIMARY> rs.printReplicationInfo()
configured oplog size: 2482.624755859375MB
log length start to end: 7486261secs (2079.52hrs)
oplog first event time: Sat Aug 27 2022 14:38:52 GMT+0000 (UTC)
oplog last event time: Tue Nov 22 2022 06:09:53 GMT+0000 (UTC)
now: Tue Nov 22 2022 06:09:59 GMT+0000 (UTC)
rs0:PRIMARY> use local
rs0:PRIMARY> db.oplog.rs.stats(1024*1024).maxSize
2482
// 改变大小为 16G
rs0:PRIMARY> db.adminCommand({replSetResizeOplog: 1, size: 16000}
链式复制
> cfg.settings.chainingAllowed = true
重新配置复制集
shard0:PRIMARY> cfg = rs.conf()
shard0:PRIMARY> cfg.members
[
{
"_id" : 0,
"host" : "mongo01.tyun.cn:27010",
"arbiterOnly" : false,
"buildIndexes" : true,
"hidden" : false,
"priority" : 1,
"tags" : {
},
"slaveDelay" : NumberLong(0),
"votes" : 1
},
{
"_id" : 1,
"host" : "mongo02.tyun.cn:27010",
"arbiterOnly" : false,
"buildIndexes" : true,
"hidden" : false,
"priority" : 1,
"tags" : {
},
"slaveDelay" : NumberLong(0),
"votes" : 1
},
{
"_id" : 2,
"host" : "mongo03.tyun.cn:27010",
"arbiterOnly" : false,
"buildIndexes" : true,
"hidden" : false,
"priority" : 1,
"tags" : {
},
"slaveDelay" : NumberLong(0),
"votes" : 1
}
]
printjson(cfg)
打印上面的配置信息。假设现存的集群中还剩余节点 1、2、3 的话:shard0:PRIMARY> cfg.members = [cfg.members[1] , cfg.members[2] , cfg.members[3]]
shard0:PRIMARY> rs.reconfig(cfg, {force : true})
force: true
选项,我们使其重新配置。当然,我们的复制集中至少需要三名幸存的节点才能发挥作用。重要的是尽快删除故障服务器,通过 kill
进程或将其从网络中剔除,以避免意外结果;这些服务器可能认为它们仍然是集群的一部分,但集群却不再承认它们。
总结
需要在单独的物理主机上部署每个 mongod 实例。如果我们使用的是虚拟机,请确保它们被分配到不同的物理主机上。使用
bind_ip
选项来确保服务器映射到特定的网络接口和端口地址。使用防火墙阻止对任何其他端口的访问和/或仅允许在应用程序服务器和 MongoDB 服务器之间访问。
启用认证机制
推荐阅读
推荐视频
微信扫码关注该文公众号作者
戳这里提交新闻线索和高质量文章给我们。
来源: qq
点击查看作者最近其他文章