期内容:MongoDB 复制集的介绍及搭建
让客户满意是我们工作的目标,不断超越客户的期望值来自于我们对这个行业的热爱。我们立志把好的技术通过有效、简单的方式提供给客户,将通过不懈努力成为客户在信息化领域值得信任、有价值的长期合作伙伴,公司提供的服务项目有:域名申请、网页空间、营销软件、网站建设、和布克赛尔蒙古网站维护、网站推广。
复制集是一组 mongod 进程一起维护相同的数据集。复制集提供了冗余及高可用。下面的几幅图是 MongoDB 复制集的工作原理:
Diagram of default routing of reads and writes to the primary.
Diagram of a 3 member replica set that consists of a primary and two secondaries.
Diagram of a replica set that consists of a primary, a secondary, and an arbiter.
主服务器是唯一可以随时写入的节点。从节点处于热待机状态,如果主节点发生故障,从节点可以接管主节点。一旦主节点失败,从节点将会竞选主节点角色。
我们也可以有仲裁节点。仲裁员节点不保存任何数据,其唯一目的是参与选举过程。
我们的复制集集群必须始终有奇数节点(包括仲裁节点)。三、五和七个节点是推荐的数量,所以如果初选(或更多服务器)失败,我们在选举过程中拥有多数选票。
当复制集的其他节点在超过 10 秒(可配置)内没有收到主节点的消息时,符合条件的从节点将开始进行投票选举。第一个选举并赢得多票数的节点将成为新的主节点。所有剩余的服务器现在都将从新的主节点复制,保持其作为从节点的角色,继续从新的主节点同步。
从 MongoDB 3.6 开始,如果客户端驱动程序检测到主操作已关闭,则可以一次重试写操作。复制集最多可以有 50 名节点,但其中最多只能有 7 名节点可以在选举过程中投票。
复制集中的所有服务器都通过心跳与所有其他节点保持定期通信。心跳是一个小包,会定期进行发送,以验证所有节点是否正常运行。
从节点与主节点通信,以从 oplog 获取最新的变更(增、删、改),并将其应用于自己的数据库中。
当主节点不可用时,所有从节点都会丢失心跳。其余节点将等到设置 electionTimeoutMillis 时间(默认时间为 10 秒)后,然后从节点将开始一轮或多轮选举,以选举产生新的主节点。
要从从节点中选择为主节点,它必须具有如下两个属性:
在三个服务器各一票的简单例子中,一旦主节点不可用,其他两台服务器将各有一票(总共三分之二),那么谁拥有最新 oplog 的服务器将被选为主节点。
通过选举完成故障恢复:
MongoDB 复制集的大部分优势,其中一些列出如下:
介绍了复制集的优势,那么有没其他缺憾呢?答案是肯定有的,因为完美是不存在的。
在上述优势的列表中缺少的最值得注意的是写扩展。这是因为,在 MongoDB 中,我们只能有一个主节点,只有这个主节点负责数据的写入。
当我们想扩展写入性能时,我们通常会设计和使用分片集群,这是下一章要分享的话题,敬请期待。
演示环境:
主机名 |
IP |
角色 |
mongo01.tyun.cn |
10.20.20.19 |
Primary |
mongo02.tyun.cn |
10.20.20.11 |
slave1 |
mongo03.tyun.cn |
10.20.20.41 |
slave2 |
由于我们启用了认证,所以需要为每个节点使用 Keyfile(当然还有其他方式):
[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
把 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
接下来配置 Primary、slave 及 arbiter 节点。客户端连接到 Primary:
[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 官方驱动程序支持五个级别的数据读选择项:
读选择项的模式 |
描述 |
primary |
这是默认的模式,从主节点读 |
primaryPreferred |
在这种模式下,应用也是从主节点读数据,但是当主节点不可用时,会从从节点读取数据 |
secondary |
完全从从节点读取数据 |
secondaryPreferred |
优先从从节点读取数据。当从节点不可用时,从主节点读取数据 |
nearest |
应用程序将从节点中就近读取数据,而不考虑节点的类型 |
通过上述提供的选项,可以很方便地配置读写分离、就近读取等策略,细分控制读取策略。上述的这些选项该怎么用呢?
复制集搭建完成后,该怎么连接使用呢?
连接复制集与连接单个节点是没有什么区别的。
配置完成就可以做数据同步的测试了,在 Primary 上创建数据,然后查看数据,然后登录其他从节点查看数据。操作如下:
[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
}
复制集的管理可能比单服务器部署所需的要复杂得多。在本节中,我们将重点关注复制集中一些最常见的管理及维护性的操作。
MongoDB 允许我们为每个节点设置不同的优先级,设置如下:
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);
当要对复制集中的节点进行维护时,优先级的设置就派上用场了。
要在设置集群后更改优先级,我们必须使用 mongo shell 连接到主服务器并获取配置对象:
> 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 秒的停机时间。尝试确定最低流量时间窗口,以运行重新配置等维护操作,并
新闻名称:自建MongoDB实践:MongoDB复制集
分享路径:http://www.mswzjz.cn/qtweb/news35/523035.html攀枝花网站建设、攀枝花网站运维推广公司-贝锐智能,是专注品牌与效果的网络营销公司;服务项目有等
声明:本网站发布的内容(图片、视频和文字)以用户投稿、用户转载内容为主,如果涉及侵权请尽快告知,我们将会在第一时间删除。文章观点不代表本网站立场,如需处理请联系客服。电话:028-86922220;邮箱:631063699@qq.com。内容未经允许不得转载,或转载时需注明来源: 贝锐智能