前言
这几年一直在it行业里摸爬滚打,一路走来,不少总结了一些python行业里的高频面试,看到大部分初入行的新鲜血液,还在为各样的面试题答案或收录有各种困难问题
于是乎,我自己开发了一款面试宝典,希望能帮到大家,也希望有更多的Python新人真正加入从事到这个行业里,让python火不只是停留在广告上。
微信小程序搜索:Python面试宝典
或可关注原创个人博客:https://lienze.tech
也可关注微信公众号,不定时发送各类有趣猎奇的技术文章:Python编程学习
副本集
此集群拥有一个主节点和多个从节点,这一点与主从复制模式类似,且主从节点所负责的工作也类似
但是副本集与主从复制的区别在于:当集群中主节点发生故障时,副本集可以自动投票,选举出新的主节点,并引导其余的从节点连接新的主节点
MongoDB 的副本集是自带故障转移功能的主从复制
一个副本集最多可以有50 个成员,但只有7 个投票成员。如果副本集已经有 7 个投票成员,则额外的成员必须是非投票成员
基本概念
- Oplog
Oplog 是用于存储 MongoDB 数据库所有数据的操作记录的(实际只记录增删改和一些系统命令操作,查是不会记录的),有点类似于 mysql 的 binlog 日志
Oplog 的存在极大地方便了 MongoDB 副本集的各节点的数据同步,MongoDB 的主节点接收请求操作,然后在 Oplog 中记录操作,次节点异步地复制并应用这些操作
- Oplog 的默认储存大小
Storage Engine | Default Oplog Size | Lower Bound | Upper Bound |
---|---|---|---|
In-Memory Storage Engine | 5% of physical memory | 50 MB | 50 GB |
WiredTiger Storage Engine | 5% of free disk space | 990 MB | 50 GB |
MMAPv1 Storage Engine | 5% of free disk space | 990 MB | 50 GB |
从MongoDB 4.0开始, Oplog 可以超过其配置的大小限制,以避免删除
一旦mongod第一次创建了 Oplog ,更改--oplogSize
选项将不会影响 Oplog 的大小
replSetResizeOplog使您可以动态调整 Oplog 的大小,而无需重新启动该mongod
过程
oplog 中每个操作都是幂等性的,也就是说,无论是对目标数据库应用一次还是多次,oplog操作都会产生相同的结果,这样就保证了数据的一致性
- 副本集同步原理
每个oplog都有时间戳,所有从节点都使用这个时间戳来追踪它们最后执行写操作的记录
当某个从节点准备更新自己时,会做三件事
首先,查看自己oplog里的最后一条时间戳
其次,查询主节点oplog里所有大于此时间戳的文档
最后,把那些文档应用到自己库里,并添加写操作文档到自己的oplog里
- 故障转移
当主节点超过配置的electionTimeoutMillis
时间(默认为 10 秒)未与集合的其他成员通信时
符合条件的辅助节点将要求进行选举以将自己提名为新的主节点。集群尝试完成新主节点的选举并恢复正常操作
在选举成功完成之前,副本集无法处理写操作
如果此类查询配置为在主服务器脱机时在辅助服务器上运行,则副本集可以继续为读取查询提供服务
选举算法
Bully算法是一种协调者(主节点)竞选算法,主要思想是集群的每个成员都可以声明它是主节点并通知其他节点
别的节点可以选择接受他的自荐或者拒绝,并将自己自荐,如果一个节点被拒绝升级称为主节点,那么他将退出竞选,只有全票通过,你才是主
节点按照一定属性来判断谁应该胜出,这个属性可以是一个静态ID,比如初始化过程给定的,也可以是更新的度量像最近一次事务ID,也可以是一个时间戳子
选举过程
- 得到每个服务器节点的最后操作时间戳,每个mongodb都有oplog机制会记录本机的操作,方便和主服务器进行对比数据是否同步还可以用于错误恢复
- 如果集群中大部分服务器down机了,保留活着的节点都为secondary状态并停止,不选举了
- 如果集群中选举出来的主节点或者所有从节点最后一次同步时间看起来很旧了,停止选举等待人来操作
- 如果上面都没有问题就选择最后操作时间戳最新(保证数据是最新的)的服务器节点作为主节点
参与选举的节点数量必须大于副本集总节点数量的一半,如果已经小于一半了所有节点保持只读状态
副本成员
成员 | 说明 |
---|---|
Secondary | 正常情况下,复制集的Seconary会参与Primary选举(自身也可能会被选为Primary),并从Primary同步最新写入的数据,以保证与Primary存储相同的数据。Secondary可以提供读服务,增加Secondary节点可以提供复制集的读服务能力,同时提升复制集的可用性。另外,Mongodb支持对复制集的Secondary节点进行灵活的配置,以适应多种场景的需求。 |
Arbiter | Arbiter节点只参与投票,不能被选为Primary,并且不从Primary同步数据。比如你部署了一个2个节点的复制集,1个Primary,1个Secondary,任意节点宕机,复制集将不能提供服务了(无法选出Primary),这时可以给复制集添加一个Arbiter节点,即使有节点宕机,仍能选出Primary。Arbiter本身不存储数据,是非常轻量级的服务,当复制集成员为偶数时,最好加入一个Arbiter节点,以提升复制集可用性。 |
Priority0 | Priority0节点的选举优先级为0,不会被选举为Primary比如你跨机房A、B部署了一个复制集,并且想指定Primary必须在A机房,这时可以将B机房的复制集成员Priority设置为0,这样Primary就一定会是A机房的成员。(注意:如果这样部署,最好将『大多数』节点部署在A机房,否则网络分区时可能无法选出Primary) |
Vote0 | Mongodb 3.0里,复制集成员最多50个,参与Primary选举投票的成员最多7个,其他成员(Vote0)的vote属性必须设置为0,即不参与投票。 |
Hidden | Hidden节点不能被选为主(Priority为0),并且对Driver不可见。因Hidden节点不会接受Driver的请求,可使用Hidden节点做一些数据备份、离线计算的任务,不会影响复制集的服务。 |
Delayed | Delayed节点必须是Hidden节点,并且其数据落后与Primary一段时间(可配置,比如1个小时)。因Delayed节点的数据比Primary落后一段时间,当错误或者无效的数据写入Primary时,可通过Delayed节点的数据来恢复到之前的时间点。 |
配置流程
- 启动主节点
mongod --dbpath ./mongo1/db --port=27018 --replSet=test
--replSet
: 指明副本集名字,一个副本集一个单独名字
- 启动从节点
mongod --dbpath ./mongo2/db --port=27019 --replSet=test
- 主节点初始化
mongo --port=8080
rs.initiate()
- 查看当前副本集的配置信息
rs.conf()
{
"_id" : "test",
"version" : 1,
"term" : 1,
"members" : [
{
"_id" : 0,
"host" : "localhost:27018",
"arbiterOnly" : false,
"buildIndexes" : true,
"hidden" : false,
"priority" : 1,
"tags" : {
},
"secondaryDelaySecs" : NumberLong(0),
"votes" : 1
}
],
"protocolVersion" : NumberLong(1),
"writeConcernMajorityJournalDefault" : true,
"settings" : {
"chainingAllowed" : true,
"heartbeatIntervalMillis" : 2000,
"heartbeatTimeoutSecs" : 10,
"electionTimeoutMillis" : 10000,
"catchUpTimeoutMillis" : -1,
"catchUpTakeoverDelayMillis" : 30000,
"getLastErrorModes" : {
},
"getLastErrorDefaults" : {
"w" : 1,
"wtimeout" : 0
},
"replicaSetId" : ObjectId("6135b1dc0e49be700167cf63")
}
}
chainingAllowed
: 是否允许级联复制
heartbeatIntervalMillis
: 心跳检测时间,默认是2s
heartbeatTimeoutSecs
: 心跳检测失效时间,默认为10s,如果在10s内没有收到节点的心跳信息,则判断节点不可达(HostUnreachable),对primary和Secondary均适用
- 添加从节点,在主节点控制台进行
rs.add("localhost:27019")
- 查看副本集主从状态
rs.status()
{
...
"members" : [
{
"_id" : 0,
"name" : "localhost:27018",
"health" : 1,
"state" : 1,
"stateStr" : "PRIMARY",
"uptime" : 1064,
"optime" : {
"ts" : Timestamp(1630909904, 1),
"t" : NumberLong(1)
},
"optimeDate" : ISODate("2021-09-06T06:31:44Z"),
"syncSourceHost" : "",
"syncSourceId" : -1,
"infoMessage" : "",
"electionTime" : Timestamp(1630908893, 2),
"electionDate" : ISODate("2021-09-06T06:14:53Z"),
"configVersion" : 3,
"configTerm" : 1,
"self" : true,
"lastHeartbeatMessage" : ""
},
{
"_id" : 1,
"name" : "localhost:27019",
"health" : 1,
"state" : 2,
"stateStr" : "SECONDARY",
"uptime" : 609,
"optime" : {
"ts" : Timestamp(1630909904, 1),
"t" : NumberLong(1)
},
"optimeDurable" : {
"ts" : Timestamp(1630909904, 1),
"t" : NumberLong(1)
},
"optimeDate" : ISODate("2021-09-06T06:31:44Z"),
"optimeDurableDate" : ISODate("2021-09-06T06:31:44Z"),
"lastHeartbeat" : ISODate("2021-09-06T06:31:48.808Z"),
"lastHeartbeatRecv" : ISODate("2021-09-06T06:31:47.746Z"),
"pingMs" : NumberLong(0),
"lastHeartbeatMessage" : "",
"syncSourceHost" : "localhost:27018",
"syncSourceId" : 0,
"infoMessage" : "",
"configVersion" : 3,
"configTerm" : 1
}
],
"ok" : 1,
"$clusterTime" : {
"clusterTime" : Timestamp(1630909904, 1),
"signature" : {
"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
"keyId" : NumberLong(0)
}
},
"operationTime" : Timestamp(1630909904, 1)
}
通过rs.conf
看到的字段会比较多,这里只截取了部分关键的内容,通过查看members
可以看到当前集群内部的节点信息
其中localhost:27019
为丛,localhost:27018
为主
连接测试
测试sdk
客户端: pymongo
,通过uri
字符串连接进行集群的连接
连接字符串URI格式: https://docs.mongodb.com/manual/reference/connection-string/
# coding:utf-8
import pymongo
client = pymongo.MongoClient(
"mongodb://localhost:27018,localhost:27019/?replicaSet=test"
)
collection = client["test"]["test"]
collection.insert({"1": 1})
集群连接,不需要考虑主从主机的先后顺序
宕机测试
宕机到最后一台机器时,最后一台机器的状态是Secondary
意味着如果只剩一台机器时,只能进行读取操作,而不能再进行写入
当拉起一台机器后这最后一台机器就变成了主节点
一个集群的生成,至少具备两个节点
读写分离
mongodb默认是从主节点读写数据的,副本节点上不允许读
设置读写分离需要先在副本节点 SECONDARY 设置 secondaryOk
rs.secondaryOk()
关闭从节点读
rs.secondaryOk(false)
使用pymongo客户端进行读写分离的设置
client = pymongo.MongoClient(
"mongodb://localhost:27019,localhost:27018/?replicaSet=test"
)
db = client.get_database(
"test", read_preference=ReadPreference.SECONDARY)
collection = db["test"]
默认情况下,应用程序将其读取操作定向到副本集中的 主要成员(即读取首选项模式“主要”)
但是,客户端可以指定读取首选项以将读取操作发送到辅助节点
- PRIMARY:默认选项,从primary节点读取数据
- PRIMARY_PREFERRED:优先从primary节点读取,如果没有primary节点,则从集群中可用的secondary节点读取
- SECONDARY:从secondary节点读取数据,如果从节点不可用,抛出异常
- SECONDARY_PREFERRED:优先从secondary节点读取,如果没有可用的secondary节点,则从primary节点读取
- NEAREST:从网络延迟最低的节点上读取数据