258_Mongodb_集合_副本集2

举报
alexsully 发表于 2021/12/22 16:10:09 2021/12/22
【摘要】 副本集理论

MongoDB副本集模式

通过实时数据同步提高数据的可用性, 包括写入策略(write concern) 和 读取策略(read preference)来提升数据的读写性能

1 副本集角色

  • 主节点(primary)
  • 副节点(secondary)
  • 仲裁节点(Arbiter)

一个副本集, 至少包括一个主节点(primary), 与一个副节点(secondary),通过不同的节点与数量,可以配置不同效果的副本集

主节点与副节点

主节点与副节点均属于数据节点,保存完整的数据

  • 主节点: 接收数据写入需求, 然后通过同步机制同步到所有的副节点, 节点通过心跳机制进行沟通, 确保所有节点正常运行
  • 仲裁节点 当主宕机后, 参与投票,选出新主,本身不存储数据, 这样在资源有限的情况下 给了副本集模式很大弹性

副本集的基本架构: 1个主节点 + 1个副节点 + 一个冲裁节点 (奇数 3个具有投票权的节点 可进行有效的投票)

 

2 高可用(节点故障转移)

在副本集中实现高可用 通过以下重要机制

  •   Oplog 同步
  •   心跳机制(Hearbeat)
  •   选举策略(vote
  •   副本集回滚(RollBack)

Oplog 同步

主节点的oplog异步同步到从节点,副本集可以在一个或多个成员失败的情况下继续运行 从数据库成员将这些操作复制并应用本身。所有副本集成员都将心跳(ping)发送给所有其他成员,任何从成员都可以从任何其他成员应用操作日志,如果副本集只剩一个节点,该节点会变成从节点(不能写)操作日志中的每个操作都是幂等的,也就是说oplog操作会产生相同的结果,无论是一次还是多次应用于目标数据集

  • Oplog(操作日志)是一个特殊的capped集合,保持所有修改存储在数据库中的数据的操作的记录,类似于MySQLBinlog
  • Oplog 记录操作记录, 是副本集成员特有集合,默认固定大小(5% 且不超过50G)可以通过oplogSizeMB 进行设置, 此参数仅在集群初始化前配置有效, 一旦创建完成无法进行修改
  • Oplog 本质是一个Capped Collections(环形队列), 新操作进来,会写入下一个位置(偏移量 & 时间戳)
  • Oplog 会被记录在数据节点 local库中 oplog.rs的集合中

Oplog 初始化

新加入副本节点如果落后primary太多, 会从副本节点进行完整的数据复制(整个数据文件&oplog, 新节点用全备进行创建,然后构建主从)

Oplog 同步

节点同步会对比其它节点状态, 选择数据比自己更完整的节点作为同步源进行同步(可指定:use admin; db.adminCommad({replSetSyncFrom: “hostname<:port>”}))

设置同步源后三种情况会恢复默认同步机制:

  •   此节点实例重新启动
  •   此节点与同步源节点之间断开连接
  •   同步源节点数据落后其它副本节点超过30S

Oplog状态查看

rs.printReplicationInfo() 查看oplog的状态,输出信息包括oplog日志大小,操作日志记录的起始时间

configured oplog size: oplog文件大小
log length start to end: oplog日志的启用时间段
oplog first event time: 第一个事务日志的产生时间
oplog last event time: 最后一个事务日志的产生时间
now: 现在的时间

db.getReplicationInfo()   可以用来查看oplog的状态、大小、存储的时间范围

在primary/secondary上查看从库落后信息
rs.printSlaveReplicationInfo()

查看slave状态
通过"db.printSlaveReplicationInfo()"可以查看slave的同步状态
副本节点中执行db.printSlaveReplicationInfo()命令可以查看同步状态信息
source——从库的IP及端口
syncedTo——当前的同步情况,延迟了多久等信息

bertram:PRIMARY> use local;
switched to db local
bertram:PRIMARY> show tables;
me
oplog.rs
replset.election
replset.minvalid
startup_log
system.profile
system.replset


local库下面的me集合保存了服务器名称
local库下面的replset.minvalid集合保存了数据库最新操作的时间戳
local库下面的startup_log集合记录这mongod每一次的启动信息
local库下面的system.indexes集合记录当前库的所有索引信息
local库下面的system.replset记录着复制集的成员配置信息rs.conf()读取这个集合
local库下面的oplog.rs集合记录着所有操作,MongoDB就是通过oplog.rs来实现数据同步的。当Primary节点插入一条数据后,oplog.rs集合中就会多一条记录

bertram:PRIMARY> db.oplog.rs.findOne();
{
        "ts" : Timestamp(1465879171, 238),
        "h" : NumberLong("-2275413922284641862"),
        "v" : 2,
        "op" : "u",
        "ns" : "MyDB.SyncTable",
        "o2" : {
                "_id" : "bbf80260-3d58-49f1-9c8c-e093d5d57527"
        },
        "o" : {
                "_id" : "bbf80260-3d58-49f1-9c8c-e093d5d57527",
                "EntityId" : "362569",
                "TypeName" : "Product",
                "Times" : 14208,
                "CreateTime" : ISODate("2014-11-15T14:35:51.916Z"),
                "LastModified" : ISODate("2016-06-14T04:38:21.708Z"),
                "LastOperationTime" : ISODate("2016-06-14T04:39:30.957Z")
        }
}

字段含义

ts:8字节的 时间戳,由4字节unix timestamp + 4字节自增计数表示。在选举(如master宕机时)新primary时,会选择ts最大的那个secondary作为新primary。
op:1字节的操作类型,例如i表示insert,d表示delete。
ns:操作所在的namespace。
o:操作所对应的document,即当前操作的内容(比如更新操作时要更新的的字段和值)
o2: 在执行更新操作时的条件,仅限于update时才有该属性。

其中op,可以是如下几种情形之一:
"i": insert
"u": update
"d": delete
"c": db cmd
"db":声明当前数据库 (其中ns 被设置成为=>数据库名称+ '.')
"n": no op,即空操作,其会定期执行以确保时效性。修改配置,会产生 "n" 操作

同步过程

大致流程:

检查配置、初始化local库的各个集合、转换各个成员的状态(STARTUP -> STARTUP2 -> RECOVERING -> SECONDARY)、10秒后开始选举(SECONDARY -> PRIMARY

 

初始化同步:

  • 复制除local数据库外的所有数据库,mongod扫描每个源数据库中的每个集合,并将所有数据插入这些集合的自己的数据库中
  • 版本3.4中更改:在为每个集合复制文档时,初始同步将构建所有集合索引(先同步索引,再同步数据) 在早期版本中,在此阶段仅构建_id索引(先同步数据,再同步索引)
  • 版本3.4中更改:初始同步在数据复制期间提取新添加的操作日志记录。
  • 确保目标成员在local数据库中具有足够的磁盘空间,以在此数据复制阶段持续时间临时存储这些操作日志记录。
  • mongod使用源中的操作日志,将所有更改应用于数据集。
  • 初始同步完成后,成员将从STARTUP2转换为SECONDARY

 

初始化时,同步源的选择取决于mongod启动参数initialSyncSourceReadPreference的值(4.2.7中的新增功能)。

如果无法选择同步源,将记录错误并等待1秒钟,然后重新选择,从mongod最多可以重新初始同步源选择过程10次,然后错误退出。

 

复制:多线程

MongoDB使用多线程批量应用写入操作以提高并发性。 MongoDB按文档IDWiredTiger)对批次进行分组,并同时使用不同的线程来应用每组操作,MongoDB始终以原始写入顺序对给定文档应用写入操作

副本集的完整配置信息和状态

副本集的完整配置信息和状态通过 rs.status & db.admincommand({replSetGetStatus : 1 }) 查看

https://docs.mongodb.com/manual/reference/command/replSetGetStatus/#std-label-rs-status-output

rs.status命令新增了一些可参考监控项,例如:

replSetGetStatus.optimes.lastCommittedOpTime:已写入大多数副本集成员的最新操作时间;

replSetGetStatus.optimes.appliedOpTime:已应用于副本集的该成员的最新操作时间;

replSetGetStatus.optimes.durableOpTime:已写入该副本集的该成员的journal日志的时间

MongoDB数据节点状态编号

状态值

状态名

状态说明

0

STARTUP

节点刚启动未完成加载副本集,配置初期为该状态

1

PRIMARY

有投票权主节点状态,唯一支持写的节点。有投票资格

2

SECONDARY

有投票权 副本节点状态,可进行数据同步,拥有投票权

3

RECOVERING

有投票权 特殊原因导致数据落后,节点自我检查/修复,无法读取数据

5

STARTUP2

有投票权 节点加载完副本集配置,并已经运行初始同步

6

UNKNOWN

无投票权 从副本集的另一个成员的角度来看,该成员的状态尚不清楚

7

ARBITER

有投票权 仲裁节点

8

DOWN

无投票权 节从副本集的另一个成员的角度来看,该成员不可访问。例网络问题

9

ROLLBACK

有投票权 执行回滚, 暂不能读取数据,回滚后进入recovering状态

10

REMOVED

节点曾在副本集中, 但被移除

非投票成员的属性如

{
   "_id" : <num>,
   "host" : <hostname:port>,
   "arbiterOnly" : false,
   "buildIndexes" : true,
   "hidden" : false,
   "priority" : 0,
   "tags" : {

   },
   "slaveDelay" : NumberLong(0),
   "votes" : 0
}
rs.status()

{
   "set" : "replset",
   "date" : ISODate("2019-12-04T04:49:18.693Z"),
   "myState" : 1,
   "term" : NumberLong(3),    # 代表副本集成员数量
   "syncingTo" : "",
   "syncSourceHost" : "",    # 代表primary
   "syncSourceId" : -1,      # 代表primary
   "heartbeatIntervalMillis" : NumberLong(2000),
   "majorityVoteCount" : 2,
   "writeMajorityCount" : 2,
   "optimes" : {
      "lastCommittedOpTime" : {  #已写入大多数副本集成员的最新操作时间
         "ts" : Timestamp(1575434954, 1),
         "t" : NumberLong(3)
      },
      "lastCommittedWallTime" : ISODate("2019-12-04T04:49:14.378Z"),
      "readConcernMajorityOpTime" : {
         "ts" : Timestamp(1575434954, 1),
         "t" : NumberLong(3)
      },
      "readConcernMajorityWallTime" : ISODate("2019-12-04T04:49:14.378Z"),
      "appliedOpTime" : {  #已应用于副本集的该成员的最新操作时间
         "ts" : Timestamp(1575434954, 1),
         "t" : NumberLong(3)
      },
      "durableOpTime" : {  #已写入该副本集的该成员的journal日志的时间
         "ts" : Timestamp(1575434954, 1),
         "t" : NumberLong(3)
      },
		"members" : [
      {
         "_id" : 0,
         "name" : "m1.example.net:27017",
         "ip" : "198.51.100.1",
         "health" : 1,
         "state" : 1,
         "stateStr" : "PRIMARY",
         "uptime" : 2019,
         "optime" : {
            "ts" : Timestamp(1575434954, 1),
            "t" : NumberLong(3)
         },
         "optimeDate" : ISODate("2019-12-04T04:49:14Z"),
         "syncingTo" : "",
         "syncSourceHost" : "",
         "syncSourceId" : -1,
         "infoMessage" : "",
         "electionTime" : Timestamp(1575434944, 1),
         "electionDate" : ISODate("2019-12-04T04:49:04Z"),
         "configVersion" : 1,
         "self" : true,
         "lastHeartbeatMessage" : ""
      },

3 选举和投票机制

 选举(election)与投票(Vote)机制, MongoDB primary发生异常时, 促使副本集自动修复, 从有投票权中的secondary节点挑选出最适合的节点来接替primary工作

投票机制:大多数原则 (总节点数/2) +1 取整数; 例 5个节点的副本集,5/2+1=3; 至少得三票,最高可以容错2台机器down

 

4 回滚机制

Primary A写操作未同步给secondary节点,网络异常导致中断, secondary选取新主primary B,原A 扔写入数据 加入副本集后 成为secondary 这样数据就有了差异,需要回滚

回滚的前提

  • 在同步源上没有查到比其更新的oplog(我们刚刚通过一系列麻烦的规则选出它作为同步源,但是我们的oplog却比它还新)
  • 返回的的第一条oplog和其最新的oplogOpTimehash都不同,注意这里是比较整个OpTime,即除了时间戳之外还包括term,首先会比较term,如果term不同,那就不同

大概流程: 首先找到BA的共同操作点,A将共同操作点后的操作写入 rollback目录下的BSON文件文件, 并撤销Oplog记录, 然后继续同步B节点数据

BSON 文件格式:<database>.<collection>.<timestamp>.bason

回滚的限制

  •     4.0 之后版本,回滚数量无限制, 回滚时间默认24h rollbackTimeLimitSecs
  •     4.0 之前版本, 数量不能超过300M  回滚时间不能超过30min

 

5 数据读写策略

  5.1 写入策略(write Concern)

  不同场景对应写入策略不同,要求最终一致性(日志业务),要求很高的响应时间(支付业务)

客户端 使用write concern来配置,通过写入响应(acknowledgement)层级,决定数据写入何时返给客户端

Write concern 包含字段 {w: <值>, J: <布尔值>, wtimeout:<数字>}

 W字段 整数N 表示反馈完成操作必须同步到N个副本集上

  • 1 表只写入主节点
  • 0 不需要写入任何节点, 可能会出现未写入成功但未收到报错信息
  • 大于1 代表必须写入大于1的节点数,不然会报错 “not enough data-bearing nodes at….”
  • Majority: 写入大多数节点(半数+1

 

J 字段 表示写操作是否需要记录到日志文件中(journal) 若写入日志中,则服务意外时, 可通过日志恢复数据

Wtimeout  表示等待时间阈值,单位ms , 超过阈值代表写入失败

db.collection.insert({name:”alex”},writeConcern:{w:1})  # 代表写入主节点

 

5.2 读取策略

类似写入策略,可以配置读取策略 Read Concern & Read Preference 非互斥, 可配合使用

Read concern 的作用是让客户端指定什么样的数据可以被读取,如 数据写入primary A 后,A异常,集群重新选B, 则A要回滚,这时A的数据是不可取的

语法

db.collection.find().readConcern(<level>)

level参数配置如下

  •  local primary节点读取时的默认值, 在未写入大部分节点前就反数据(此做法会导致脏读)
  •  available secondary读取的默认值,在未写入大部分节点前就反数据(此做法会导致脏读)
  •    majority  数据操作必须更新至大多数节点才能被读取
  •    linearizable  只允许在主节点读取(readConcern(“majority”)配置时候 能保证读到已经确认的数据且查询结果为单个文档生效)
  •    snapshot  4.0 版本提供 多文档事物中使用

 

read Preference

配置该参数,可以让客户端驱动知道从哪个节点去读取数据,可以配置读写分离,就近读取数据的负载均衡效果

Mongo.setReadPref(<mode>,<tagset>)

Mode可设置

  •  Primary 主节点读取
  •  PrimaryPreferred  优先从主节点读取,若主节点无法读取, 从副节点读取
  •  Secondary 从副节点读取
  •  SecondaryPreferred 优先从副节点驱动,若无法读取,从primary读取
  •  Nearest 根据网络情况从最近的节点读取, 可搭配知道副本的tags限制读取来源 db.collection.find().readPref(“nearest”,[‘source’:’rpt’])
【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。