MongoDB7.0--复制集
摘要
-
本文介绍Linux下MongoDB7.0复制集的安装和使用
-
MongoDB官方文档
-
本文基于
CentOS8(x86_64)
-
Mongodb分片集群
搭建参看MongoDB 分片集群搭建,虽然是基于4.4
版本的,但是安装方式差别不大,只是配置文件中个别的属性名称发生了变化。
复制集节点类型
-
一个复制集最多支持50个节点,且做多只能有7个节点参与投票,节点类型分为
Primary
、Secondary
与Arbiter
。 -
Primary
:主节点,其接收所有的写请求,然后把修改同步到所有Secondary
节点。一个复制集只能有一个主节点,当主节点挂掉后,其他节点会重新选举出来一个主节点。 -
Secondary
:备节点,与主节点保持同样的数据集。当主节点挂掉时,参与竞选主节点。分为以下三个不同类型:- Hidden = false:正常的只读节点,是否可选为主,是否可投票,取决于
Priority
,Vote
的值; - Hidden = true:隐藏节点,对客户端不可见, 可以参与选举,但是
Priority
必须为 0,即不能被提升为主。 由于隐藏节点不会接受业务访问,因此可通过隐藏节点做一些数据备份、离线计算的任务,这并不会影响整个复制集。 - Delayed :延迟节点,必须同时具备
隐藏节点
和Priority=0
的特性,会延迟一定的时间(secondaryDelaySecs 配置决定)从上游复制增量,常用于快速回滚场景。
- Hidden = false:正常的只读节点,是否可选为主,是否可投票,取决于
-
Arbiter
:仲裁节点,只用于参与选举投票,本身不承载任何数据,只作为投票角色。比如你部署了2个节点的复制集,1个 Primary,1个Secondary,任意节点宕机,复制集将不能提供服务了(无法选出Primary),这时可以给复制集添加⼀个 Arbiter节点,即使有节点宕机,仍能选出Primary。 Arbiter本身不存储数据,是非常轻量级的服务,当复制集成员为偶数时,最好加入⼀个Arbiter节点,以提升复制集可用性。
搭建基于3台机器的复制集
1 | 10.1.2.26 |
-
mongod.conf配置文件,需要创建好相关目录
1 | systemLog: |
-
创建key文件
1 | openssl rand -base64 756 > /mongodb/mongo.key |
-
分别启动3个节点
1 | mongod -f /mongodb/mongo.conf |
初始化
-
登录任意节点
1 | mongosh 10.1.2.26:27017 |
-
初始化复制集
1 | # 必须切换到admin数据库 |
-
查看谁是主节点
1 | # 查看谁是主节点,可以看到此时主节点是 10.1.2.26 |
-
查看集群状态
1 | > rs.status() |
state的值及对应的含义
0: Startup - 成员正在启动。
1: Primary - 成员是主副本集节点。可以接收写入操作。每个副本集有且仅有一个主节点。
2: Secondary - 成员是从副本集节点。复制主节点的数据变更。
3: Recovering - 成员已经接收到新的数据,但还无法提供读或投票服务。此状态通常是短暂的。
4: Fatal - 成员发生了不可恢复的错误,已停止接收和复制数据。人工干预需要重启成员或者副本集。
5: Startup2 - 成员正在初始化副本集的内部数据结构。
6: Unknown - 因为与此成员的同步被打断,导致此成员状态未知。
7: Arbiter - 成员是仲裁者。
8: Down - 这个成员目前不能到达。
9: Rollback - 这个成员正在滚动回数据以达到一致的状态。
10: Removed - 这个成员已经从副本集中删除。
授权
-
创建用户
授权后需要重新登录或进行认证后才能继续操作
1 | # 登录主节点 |
登录
-
方法1,连接的同时认证
1 | # 默认登录test数据库 |
-
方法2,先连接,再认证
1 | mongosh 10.1.2.26:27017 |
查看用户信息
1 | # 先要切换到 admin 数据库 |
开启从库读取数据的权限
-
使用当前最新版
mongosh-2.2.2
连接从库时,默认就支持读取数据,其默认的readPreference
为primaryPreferred
。 -
如果使用之前的版本连接从库时,默认是不能读取数据的,需要手工开启
1 | # 提示非主节点,不能查询数据, |
-
全局设置
readPreference
,对本次链接都有效
1 | # 开启从库读取数据权限 |
-
也可以在链接参数中指定
readPreference
1 | # 单节点连接 |
-
可以在查询时指定
readPreference
,只针对当前查询,对后续查询无效
1 | rs0 [direct: secondary] admin> db.system.users.find().readPref("secondary") |
readPreference
readPreference决定使用哪一类节点(主或从节点)来满足正在发起的读请求。可选值包括:
-
primary
:默认值,只读主节点的数据。 -
primaryPreferred
:优先读主节点的数据,如果主节点不可用,则读从节点的数据。 -
secondary
:只读从节点的数据。 -
secondaryPreferred
:优先读从节点的数据,如果从节点不可用,则读主节点的数据。 -
nearest
:优先读距离最近的节点的数据。
指定
readPreference
时应注意高可用问题。例如将readPreference
指定primary
,则发生故障转移不存在primary
期间将没有节点可读。如果业务允许,则应选择primaryPreferred
;
Tag
-
集群中节点的标签,用于选择节点
-
readPreference
只能控制使用一类(主或从节点)节点。Tag
则可以将节点选择控制到一个或几个节点。 -
为节点添加
Tag
1 | # 必须在主节点下才能修改配置 |
-
全局指定
Tag
1 | rs0 [direct: primary] admin> db.getMongo().setReadPref("primaryPreferred", [ {purpose: "online"} ]) |
-
在链接参数中指定
Tag
1 | mongosh "mongodb://user:password@10.1.2.26:27017,10.1.2.142:27017,10.1.2.41:27017/test?authSource=admin&replicaSet=rs0&readPreference=primaryPreferred&readPreferenceTags=purpose:online" |
-
在查询时指定
Tag
1 | rs0 [direct: secondary] admin> db.system.users.find().readPref("secondary", [ {purpose: "analyse"} ]) |
-
使用 Tag 时应注意高可用问题,如果只有一个节点拥有一个特定 Tag,则在这个节点失效时将无节点可读。这在有时候是期望的结果,有时候不是。例如:
- 如果报表使用的节点失效,即使不生成报表,通常也不希望将报表负载转移到其他节点上,此时只有一个节点有报表 Tag 是合理的选择;
- 如果线上节点失效,通常希望有替代节点,所以应该保持多个节点有同样的 Tag;
-
Tag 有时需要与优先级、选举权综合考虑。例如做报表的节点通常不会希望它成为主节点,则优先级应为 0。
-
通过
rs.conf()
可以查看复制集中各个节点的Tag信息
优先级(priority) 与 选举权(vote)
-
当
Priority
等于 0 时,它不可以被复制集选举为主,Priority
的值越高,则被选举为主的概率更大。 -
当
Vote
等于 0 时,它不可以参与选举投票,此时该节点的Priority
也必须为 0,即它也不能被选举为主。 -
由于一个复制集中最多只有7个投票成员,因此多出来的成员则必须将其vote属性值设置为0,即这些成员将无法参与投票。
-
通过
rs.conf()
可以查看复制集中各个节点的相关配置
1 | > rs.conf() |
-
修改节点优先级
1 | # 必须在主节点执行 |
-
设置隐藏节点
1 | # 必须在主节点执行 |
-
配置延时节点
1 | cfg = rs.conf() |
读关注(readConcern)
-
在
readPreference
选择了指定的节点后,readConcern
决定这个节点上的数据哪些是可读的 -
类似于关系数据库的隔离级别,可选值包括:
1 | - available:读取所有可用的数据; |
-
主节点读取数据时默认
readConcern
是local
,从节点读取数据时默认readConcern
是available
分片集群中 local 和 available 的区别
- 如果一个 chunk x 正在从 shard1 向 shard2 迁移;
- 整个迁移过程中 chunk x 中的部分数据会在 shard1 和 shard2 中同时存在,但源分片 shard1仍然是chunk x 的负责方:
- 所有对 chunk x 的读写操作仍然进入 shard1;
- config 中记录的信息 chunk x 仍然属于 shard1;
- 此时如果读 shard2,则会体现出 local 和 available 的区别:
- local:只取应该由 shard2 负责的数据(不包括 x);
- available:shard2 上有什么就读什么(包括 x);
-
示例
1 | db.user.find().readConcern('local') |
写关注(writeConcern)
-
writeConcern
决定一个写操作落到多少个节点上才算成功。 -
MongoDB支持客户端灵活配置写入策略(writeConcern),以满足不同场景的需求。
1 | { w: <value>, j: <boolean>, wtimeout: <number> } |
-
通常重要数据应用
{w: "majority"}
,普通数据可以应用{w: 1}
以确保最佳性能。 -
示例
1 | # 等等大多数节点写入成功 |
优雅的重启复制集
-
逐个重启复制集里所有的Secondary节点
1 | # 关闭服务 |
-
对Primary发送rs.stepDown()命令,等待primary降级为Secondary
1 | rs0 [primary] local> rs.stepDown() |
-
重启降级后的Primary
oplog
-
在复制集架构中,主节点与备节点之间是通过oplog来同步数据的,这里的oplog是一个特殊的固定集合,当主节点上的一个写操作完成后,会向oplog集合写入一条对应的日志,而备节点则通过这个oplog不断拉取到新的日志,在本地进行回放以达到数据同步的目的。
-
查询oplog
1 | use local |
1 | { |
-
oplog集合的大小默认为
min(磁盘可用空间*5%,50GB)
,可以通过如下命令修改
1 | # 将复制集成员的oplog大小修改为1G,这里单位是M |
-
这里要注意每个节点的oplog大小都需要单独的配置,并且必须一致,否则有可能会出现同步失败的情况。
-
查看当前节点的oplog的状态
1 | rs0 [primary] test> rs.printReplicationInfo() |
-
以从节点视角查看oplog复制状态
1 | rs0 [primary] local> rs.printSecondaryReplicationInfo() |
既然oplog的大小有限制,新的数据会覆盖旧的数据,那么新加入的节点如何全量同步数据呢?
-
当MongoDB的从库节点需要进行数据同步时,会执行以下步骤:
- 初始同步: 当一个新节点加入到副本集中时,它会执行初始同步以获取集合中的所有数据。初始同步会复制所有数据库的所有数据,包括系统数据库。之后,新节点会开始从oplog里获取更改历史,应用这些更改历史到数据,以保证与其他从库节点一致。注意,初始同步中的数据包括在新加入节点之前的数据以及新节点加入之后其他节点产生的新数据。
- oplog落后: 如果从库在初始同步执行期间不能及时获取oplog(例如,oplog的数据超出了大小限制并被覆盖,或者初次同步时间过长),新节点需要重新进行初始同步操作。
-
这些步骤确保了MongoDB副本集的数据在所有节点间的一致性,尽管oplog有大小限制并且旧的内容会被新的操作覆盖。
复制集相关操作
命令 | 描述 |
---|---|
rs.add(“hostname:port”) | 为复制集新增节点 |
rs.addArb(“hostname:port”) | 为复制集新增一个 arbiters |
rs.conf() | 返回复制集配置信息 |
rs.config() | 同 rs.conf() |
rs.freeze(3600) | 防止当前节点在一段时间内选举成为主节点,如这里是3600秒,则当前节点在3600秒内不会选举成为主节点 |
rs.help() | 返回 replica set 的命令帮助 |
rs.initiate() | 初始化一个新的复制集 |
rs.printReplicationInfo() | 以当前节点的视角返回复制的状态报告 |
rs.printSecondaryReplicationInfo() | 以从节点的视角返回复制状态报告 |
rs.reconfig() | 通过重新应用复制集配置来为复制集更新配置 |
rs.remove(“hostname:port”) | 从复制集中移除一个节点 |
rs.secondaryOk() | 为当前的连接设置从节点可读,该方法已经过时了,使用 db.getMongo().setReadPref() 代替 |
rs.status() | 返回复制集状态信息 |
rs.stepDown() | 让当前的 primary 变为从节点并触发新一轮的主节点选举 |
rs.syncFrom(“hostname:port”) | 设置复制集节点从哪个节点处同步数据,将会覆盖默认选取逻辑 |
rs.reconfigForPSASet() | 在主-从-仲裁器(PSA)副本集或正在更改为PSA架构的副本集上安全地执行一些重新配置更改。 |
移除节点
1 | rs0 [primary] local> rs.remove("10.1.2.41:27017") |
添加仲裁节点
当集群中的节点数>=3
时,无论是添加仲裁节点还是从节点都没有问题。
-
上面删除了集群中的一个节点,此时集群中的节点说是2(一主一从),此时如果主发生故障集群就没办法工作了,此时我们可以为其添加一个仲裁节点(只投票不存储数据)。
1 | # 添加仲裁节点,但是此时添加仲裁节点会报错 |
-
此时关闭主节点,就会看到另一个从节点成为主节点了
添加新的节点
当集群中的节点数>=3
时,无论是添加仲裁节点还是从节点都没有问题。
-
如果当前集群中有两个节点(一主一从),我们也可以不添加仲裁节点,而是直接添加一个新的从节点
1 | rs0 [primary] test> rs.add("10.1.2.41:27017") |
-
当集群中只有一个主节点和一个仲裁节点时,此时若直接添加新的节点会报错
1 | rs0 [primary] local> rs.add("10.1.2.142:27017") |
-
根据给出的网址的提示,执行以下操作
1 | # 获取当前配置 |
故障转移
-
集群组建完成后,集群内的各个节点会开启心跳响应定时器,默认每隔2秒
(heartbeatIntervalMillis)
会向其它成员发起心跳。 -
同时各成员节点还会启动一个选举超时检测定时器,默认每隔10秒
(electionTimeoutMillis)
会发起一轮选举调度
。 -
当主节点心跳响应成功,则当前节点就会取消上一次的
选举调度
,即会重置选举超时检测定时器的倒计时 -
若主节点心跳响应失败,并且在
选举超时检测
的时间内仍然没有响应成功,则当前节点就会发起一轮选举调度
-
MongoDB的复制集选举使用Raft算法来实现,选举成功的必要条件是大多数投票节点存活。简单来说就是如果超过半数的节点都给我投票了,那么我就会成为主节点。
为什么推荐集群连接的方式?
-
考虑这样的场景,如果我连接主节点并且插入10000条说,若此时主节点宕机,则客户端会立刻停止操作。
1 | # 只连接主节点 |
-
若连接采用集群的方式连接,则不会中断操作,其原因就是集群连接方式会自动完成故障转移
1 | mongosh "mongodb://user:password@10.1.2.26:27017,10.1.2.142:27017,10.1.2.41:27017/test?authSource=admin&replicaSet=rs0" |
-
这里有一点需要注意,当你创建集群时使用的是各个节点的内网ip地址,则客户端必须可以访问这些内网ip才能通过集群连接,否则会出现连接失败的情况。因为通过集群连接的方式,实际上是会先在集群内部判断出主节点的ip地址返回给客户端,然后客户端再通过这个主节点ip访问主节点。
小贴士
- 在复制集发生主备节点切换的情况下,会出现短暂的无主节点阶段,此时无法接受业务写操作。
- 早期的版本中需要添加
retryWrites
参数,现在已经默认开启了
1 | mongosh "mongodb://user:password@10.1.2.26:27017,10.1.2.142:27017,10.1.2.41:27017/test?authSource=admin&replicaSet=rs0&retryWrites=true" |
SpringBoot集成MongoDB集群
1 | spring: |