52.MongoDB复制(副本)集实战及其原理分析

MongoDB复制集架构

高可用

在生产环境中,不建议使用单机版的MongoDB服务器。

Mongodb复制集(Replication Set)由一组Mongod实例(进程)组成,包含一个Primary节点和多个Secondary节点,Mongodb Driver(客户端)的所有数据都写入Primary,Secondary从Primary同步写入的数据,以保持复制集内所有成员存储相同的数据集,提供数据的高可用。复制集提供冗余和高可用性,是所有生产部署的基础。它的现实依赖于两个方面的功能:

  • 数据写入时将数据迅速复制到另一个独立节点上

  • 在接受写入的节点发生故障时自动选举出一个新的替代节点

三节点复制集模式

PSS模式(官方推荐模式)

PSS模式由一个主节点和两个备节点所组成,即Primary+Secondary+Secondary。

PSA模式

PSA模式由一个主节点、一个备节点和一个仲裁者节点组成,即Primary+Secondary+Arbiter

安全认证

shell 复制代码
 #mongo.key采用随机算法生成,用作节点内部通信的密钥文件。
 openssl rand -base64 756 > /data/mongo.key
 #权限必须是600
 chmod 600 /data/mongo.key  
 # 启动mongod
 mongod -f /data/db1/mongod.conf --keyFile /data/mongo.key

复制集连接方式

通过高可用 Uri 的方式连接 MongoDB,当 Primary 故障切换后,MongoDB Driver 可自动感知并把流量路由到新的 Primary 节点

shell 复制代码
mongosh mongodb://fox:fox@192.168.139.135:27017,192.168.139.136:27017,192.168.139.137:27017/admin?replicaSet=rs0

复制集成员角色

成员角色的属性

Priority = 0 当 Priority 等于 0 时,它不可以被复制集选举为主,Priority 的值越高,则被选举为主的概率更大。通常,在跨机房方式下部署复制集可以使用该特性。假设使用了机房A和机房B,由于主要业务与机房A更近,则可以将机房B的复制集成员Priority设置为0,这样主节点就一定会是A机房的成员。

Vote = 0 不可以参与选举投票,此时该节点的 Priority 也必须为 0,即它也不能被选举为主。由于一个复制集中最多只有7个投票成员,因此多出来的成员则必须将其vote属性值设置为0,即这些成员将无法参与投票。

成员角色

  • Primary:主节点,其接收所有的写请求,然后把修改同步到所有备节点。一个复制集只能有一个主节点。

  • Secondary:备节点,与主节点保持同样的数据集。当主节点"挂掉"时,参与竞选主节点。分为以下三个不同类型:

    • Hidden = false:正常的只读节点,是否可选为主,是否可投票,取决于 Priority,Vote 的值;
    • Hidden = true:隐藏节点,对客户端不可见, 可以参与选举,但是 Priority 必须为 0,即不能被提升为主。 由于隐藏节点不会接受业务访问,因此可通过隐藏节点做一些数据备份、离线计算的任务,这并不会影响整个复制集。
    • Delayed :延迟节点,必须同时具备隐藏节点和Priority0的特性,会延迟一定的时间(secondaryDelaySecs 配置决定)从上游复制增量,常用于快速回滚场景。
  • Arbiter:仲裁节点,只用于参与选举投票,本身不承载任何数据,只作为投票角色。比如你部署了2个节点的复制集,1个 Primary,1个Secondary,任意节点宕机,复制集将不能提供服务了(无法选出Primary),这时可以给复制集添加⼀个 Arbiter节点,即使有节点宕机,仍能选出Primary。 Arbiter本身不存储数据,是非常轻量级的服务,当复制集成员为偶数时,最好加入⼀个Arbiter节点,以提升复制集可用性。

shell 复制代码
#配置隐藏节点
cfg = rs.conf()
cfg.members[1].priority = 0
cfg.members[1].hidden = true
rs.reconfig(cfg)

#配置延时节点
cfg = rs.conf()
cfg.members[1].priority = 0
cfg.members[1].hidden = true
#延迟1分钟
cfg.members[1].secondaryDelaySecs = 60
rs.reconfig(cfg)

#添加投票节点
# 为仲裁节点创建数据目录,存放配置数据。该目录将不保存数据集
mkdir /data/arb
# 启动仲裁节点,指定数据目录和复制集名称
mongod --port 30000 --dbpath /data/arb --replSet rs0 
# 进入mongo shell,添加仲裁节点到复制集
rs.addArb("ip:30000")
# 执行命令
db.adminCommand( {"setDefaultRWConcern" : 1, "defaultWriteConcern" : { "w" : 2 } } )


#移除复制集节点
rs.remove("ip:port")
#通过 rs.reconfig() 来移除节点
cfg = rs.conf()
cfg.members.splice(2,1)  #从2开始移除1个元素
rs.reconfig(cfg)

#更改复制集节点
cfg = rs.conf()
cfg.members[0].host = "ip:port"
rs.reconfig(cfg)

MongoDB复制集原理

数据同步

MongoDB的复制集选举使用Raft算法(https://raft.github.io/)来实现,选举成功的必要条件是大多数投票节点存活。

MongoDB对raft协议添加了一些自己的扩展

  • 支持chainingAllowed链式复制,即备节点不只是从主节点上同步数据,还可以选择一个离自己最近(心跳延时最小)的节点来复制数据。
  • 增加了预投票阶段,即preVote,这主要是用来避免网络分区时产生Term(任期)值激增的问题
  • 支持投票优先级,如果备节点发现自己的优先级比主节点高,则会主动发起投票并尝试成为新的主节点。

一个复制集最多可以有50 个成员,但只有 7 个投票成员。

自动故障转移

一个影响检测机制的因素是心跳,在复制集组建完成之后,各成员节点会开启定时器,持续向其他成员发起心跳,这里涉及的参数为heartbeatIntervalMillis,即心跳间隔时间,默认值是2s。如果心跳成功,则会持续以2s的频率继续发送心跳;如果心跳失败,则会立即重试心跳,一直到心跳恢复成功。

一个影响检测机制的因素是心跳,在复制集组建完成之后,各成员节点会开启定时器,持续向其他成员发起心跳,这里涉及的参数为heartbeatIntervalMillis,即心跳间隔时间,默认值是2s。如果心跳成功,则会持续以2s的频率继续发送心跳;如果心跳失败,则会立即重试心跳,一直到心跳恢复成功。

在electionTimeout任务中触发选举必须要满足以下条件

(1)当前节点是备节点。

(2)当前节点具备选举权限。

(3)在检测周期内仍然没有与主节点心跳成功。

业务影响评估

  • 在复制集发生主备节点切换的情况下,会出现短暂的无主节点阶段,此时无法接受业务写操作。
  • 对于非常重要的业务,建议在业务层面做一些防护策略,比如设计重试机制。
shell 复制代码
# MongoDB Drivers 启用可重试写入
mongodb://localhost/?retryWrites=true
# mongo shell
mongosh --retryWrites

如何优雅的重启复制集

  • 逐个重启复制集里所有的Secondary节点
  • 对Primary发送rs.stepDown()命令,等待primary降级为Secondary
  • 重启降级后的Primary

复制集数据同步机制

MongoDB oplog 是 Local 库下的一个集合,用来保存写操作所产生的增量日志(类似于 MySQL 中 的 Binlog)。

primary ---------- write ----------》 local.oplog.rs ---------- read----------》secondary ---------- write ----------》 local.oplog.rs

​ ---------- read----------》secondary ---------- write ----------》 local.oplog.rs

oplog 中的 ts 是备节点实现增量日志同步的关键

每个备节点都分别维护了自己的一个offset,也就是从主节点拉取的最后一条日志的optime,在执行同步时就通过这个optime向主节点的oplog集合发起查询。

MongoDB在4.0版本之后提供了replSetResizeOplog命令,可以实现动态修改oplogSize而不需要重启服务器。

shell 复制代码
# 将复制集成员的oplog大小修改为60g  
db.adminCommand({replSetResizeOplog: 1, size: 60000})
# 查看oplog大小
use local
db.oplog.rs.stats().maxSize

幂等性

某文档x字段当前值为100,用户向Primary发送一条{KaTeX parse error: Expected 'EOF', got '}' at position 12: inc: {x: 1}}̲,记录oplog时会转化为一条...set: {x: 101}的操作,才能保证幂等性。

幂等性的代价 : oplog的写入被放大,导致同步追不上

使用数组时,尽量注意:

  1. 数组的元素个数不要太多,总的大小也不要太大
  2. 尽量避免对数组进行更新操作
  3. 如果一定要更新,尽量只在尾部插入元素,复杂的逻辑可以考虑在业务层面上来支持

复制延迟

为了尽量避免复制延迟带来的风险,我们可以采取一些措施

  • 增加oplog的容量大小,并保持对复制窗口的监视。
  • 通过一些扩展手段降低主节点的写入速度。
  • 优化主备节点之间的网络。
  • 避免字段使用太大的数组(可能导致oplog膨胀)。

数据回滚

shell 复制代码
mongorestore --host 192.168.192:27018 --db test --collection emp -ufox -pfox 
--authenticationDatabase=admin rollback/emp_rollback.bson

同步源选择

在settings.chainingAllowed开启的情况下,备节点自动选择一个最近的节点(ping命令时延最小)进行同步。

shell 复制代码
#默认情况下备节点并不一定会选择主节点进行同步,这个副作用就是会带来延迟的增加,可以通过以下命令关闭
cfg = rs.config()
cfg.settings.chainingAllowed = false
rs.reconfig(cfg)

#使用replSetSyncFrom命令临时更改当前节点的同步源
db.adminCommand( { replSetSyncFrom: "hostname:port" })
相关推荐
鸠摩智首席音效师3 分钟前
MySQL ERROR 1114 (HY000): The table is full
数据库·mysql
数据大魔方7 分钟前
【期货量化实战】豆粕期货量化交易策略(Python完整代码)
开发语言·数据库·python·算法·github·程序员创富
Codeking__28 分钟前
Redis的value类型介绍——zset
数据库·redis·缓存
muddjsv29 分钟前
SQLite3 核心命令全解析 (从入门到精通)
数据库
難釋懷33 分钟前
认识NoSQL
数据库·nosql
亿坊电商36 分钟前
利于SEO优化的CMS系统都有哪些特点?
前端·数据库
阿阿阿安36 分钟前
MySQL(一)数据库风险操作场景总结
数据库·mysql
心丑姑娘1 小时前
使用ClickHouse时的劣质SQL样例
数据库·sql·clickhouse
什么都不会的Tristan1 小时前
redis篇
数据库·redis·缓存
only°夏至besos1 小时前
MySQL 运维实战:常见问题排查与解决方案
运维·数据库·mysql