Code前端首页关于Code前端联系我们

MongoDB用简单的方式解释了复制到所有架构的基本概念

terry 2年前 (2023-09-26) 阅读数 180 #数据库

最近,我在生产环境中遇到了很多与复制相关的问题。查阅网上资料,发现官方文档很系统,但不够深入。网上有些深入的文章是直接用源码呈现的,不适合大家理解。 。于是文则将前两者结合起来,最终向读者展示了MongoDB抄袭的整个结构,简单易懂。本文分为以下5个步骤:

  1. MongoDB复制介绍
  2. MongoDB添加从数据库
  3. MongoDB复制流程详细讲解
  4. MongoDB高可用复制MongoDB复制介绍

    这个本章将为大家简单介绍一下MongoDB文案的一些基本概念,以方便理解后面的内容。

    1.1。基本介绍

    MongoDB有两种模式:副本集和主从复制。今天给大家介绍一下副本集模式,因为主从模式在MongoDB3.6中已经被彻底抛弃了。 MongoDB 副本集分为三种类型:主副本集、辅助副本集和任意副本集。今天给大家介绍一下主备数据同步的内部原理。 MongoDB副本集架构如下:

    MongoDB复制基本概念到整个架构深入浅出

    1.2、MongoDB Oplog

    MongoDB Oplog是MongoDB Primary和Secondary复制期间和复制后的复制介质。即Primary中的所有写操作都会被记录下来供MongoDB Oplog使用。 ,那么从库就会来主库拉取Oplog并应用到自己的数据库中。这里的oplog是MongoDB本地的数据库集合。这是一个上限集合。通俗的意思就是大小固定,循环使用。如下图:

    MongoDB复制基本概念到整个架构深入浅出

    MongoDB Oplog内容及字段介绍:{
    "ts" : Timestamp(1446011584, 2),
    "h" : NumberLong("1687359108") "v": 2,
    "op" : "i",
    "ns" : "test.nosql",
    "o" : { "_id" : ObjectId("563062c02f345ab733" mongodb", "score" : "100" }
    }
    ts:操作时间,时间戳+当前计数器,计数器每秒重置
    h:操作的全局唯一标识符 oplog 版本信息
    op:操作类型
    i:插入操作
    u:更新操作
    d:删除操作

    MongoDB复制基本概念到整个架构深入浅出

    命令 数据库) n :无操作,特殊用途
    ns:操作 的对象集合 o:操作内容,if update操作
    o2:操作查询条件,只有update操作包含这一栏 1.3,MongoDB复制开发

    MongoDB重复了好几个版本,下图中,我总结了MongoDB的一些重要改进复制市场上常用的版本。

    MongoDB复制基本概念到整个架构深入浅出

    具体详情可以查看MongoDB官方的Release Notes:https://docs.mongodb.com/manual/release-notes/3.6/

    2。MongoDB添加了从库

    2.1。添加从库命令

    对于MongoDB来说添加从库还是比较简单的。安装从库后,立即在主库上执行命令 rs.add() 或 replSetReconfig 进行添加。事实上,这两个命令最终都是通过调用replSetReconfig命令来执行的。如果有兴趣可以阅读MongoDB客户端JS代码。

    2.2。具体步骤

    然后看添加新从库到副本集的一般步骤,如下图。右边的次要是我新添加的从库。

    MongoDB复制基本概念到整个架构深入浅出

    从上图可以看出,一共有7个步骤。让我们看看 MongoDB 在每一步都做了什么:

    1. 主库接收从库添加命令
    2. 主库更新副本集配置并与新的从库创建心跳机制
    3. 从库接收主库发送的心跳消息并创建一个与主库心跳
    4. 另一个从库收到主库发送的新版本副本集配置信息,并更新自己的配置
    5. 另一个从库与新的从库建立心跳机制
    6. 新的从库接收其他slave的心跳信息,并与其他slave建立心跳机制
    7. 新增节点更新副本集配置信息到本地。在system.replset集合中,MongoDB会在循环中查询local.system.replset,看看是否已经设置了replset信息。一旦找到相关信息,就会触发一个复制线程的启动,然后判断是否需要进行全量复制。如有必要,需要完全复制。不需要增量副本。
    8. 最后一次同步建立

    注意:

    1. 副本集中所有节点共同有一次心跳机制,每2秒一次。 MongoDB 3.2版本之后,我们可以通过heartbeatIntervalMillis参数来控制心跳频率。

    3。MongoDB复制流程详解

    上面我们了解了添加从库的大致流程,那么现在我们来看看主从数据同步的具体细节。当一个从库被添加到克隆平面时,它会确定是否需要初始同步(全同步)或附加同步。他们是在什么条件下被评判的?

    3.1。指定全量同步和增量同步

    1. 如果本地数据库的oplog.rs集合为空,则执行全量同步。
    2. 如果_initialSyncFlag存储在minValid集合中,则执行全同步(用于init同步失败处理)
    3. 如果initialSyncRequested为true,则执行全同步(用于resync命令,resync命令仅用于主/从架构,不能使用副本集)

    如果满足以上三个条件之一,则需要全同步。

    我们可以得出结论,第一个从库加入副本集时,只能先进行Initial Sync。我们看一下Initial Synchronization

    3.2的具体流程。完成同步过程(初始化同步)

    1。 查找同步源

    让我先解释一件事。 MongoDB默认采用级联复制架构,即默认不必选择主库作为同步源。如果不想做级联复制,可以通过 chainingAllowed 参数进行控制。如果是级联复制,您还可以通过 replSetSyncFrom 命令指定要复制的同步源。所以这里所说的同步源实际上是主库相对于从库而言的。那么,选择同步源的流程是怎样的呢?

    MongoDB从库会通过以下条件过滤与集合副本中其他节点匹配的同步源。

    • 如果 chainingAllowed 设置为 false,则只能选择主数据库作为同步源
    • 找到 ping 时间最小且数据比自己更新的节点(副本集启动时,或者新节点加入副本集,新节点至少乘以副本集中其他节点的两倍)
    • 将此同步源与主数据库的最新 optime 进行比较。如果主库延迟超过30s,则不会选择同步源。
    • 在第一个过滤器中,比您自己的数据更早的节点将首先被删除。如果不是第一次,则必须对该节点进行两次计数,以免遗漏任何可以作为同步源的节点。
    • 最后确认该节点是否被禁止参与选举,如果是则跳过该节点。

    将经过上述过滤的最后一个节点作为新的同步源。

    事实上,MongoDB的同步源在被选择后并不总是稳定的,除了初始同步和额外副本期间。以下情况可能会改变同步源:

    • 无法 ping 通自己的同步源
    • 自己的同步源 角色 发生了变化
    • 自己的同步源与副本节点上任意节点延迟 30 秒内

    2、删除MongoDB中除本地以外的所有数据库

    3、拉取主要库存数据

    这里就到了初始同步的核心逻辑了。我将以图表和步骤的形式向大家展示MongoDB进行初始同步的具体过程。

    MongoDB复制基本概念到整个架构深入浅出

    同步过程如下:

    1. 将_initialSyncFlag添加到minValid集合中,告诉它如果我们在此过程中崩溃则重新启动初始同步
    2. 记录初始时间。 (记录当前主库最新的oplog时间)
    3. 克隆。
    4. 设置minValid1同步目标的最新oplog时间。
    5. 应用从start到minValid1的ops,找回缺失的文档)应用Oplog)
    6. 设置 minValid2 以对齐目标的最新操作时间。
    7. 应用从 minValid1 到 minValid2 的操作。(应用 Oplog 2)
    8. 构建最新的 minValid。 minValid 的时间索引。
    9. 应用从minValid2到minValid3的操作。(应用Oplog 3)
    10. 清理minValid集合:删除_initialSyncFlag列,为minValid3 OpTime设置ts

    注意:上述步骤直接复制MongoDB源代码中的注释。

    上述步骤在 Mongo 3.4 初始同步中有所改进:

    1. 创建集合时一起创建索引(与主库相同)。 MongoDB 3.4版本之前,只创建了_id索引,其他索引等待Create数据完成后的副本。
    2. 创建集合并复制数据时,oplog也会被复制到本地数据库。数据复制完成后,本地oplog数据开始生效。
    3. 添加了由于网络问题导致初始同步失败时的重试机制。
    4. 如果在初始同步期间发现集合被重命名,初始同步将重新启动。

    以上四个新功能提高了Early Sync的效率,提高了Early Sync的可靠性。因此,大家最好使用最新版本的MongoDB 3.4或3.6。 MongoDB 3.6 有一些令人兴奋的功能。这里不再解释。

    完全同步完成后,MongoDB接下来将进入附加同步过程。

    3.3。额外的同步流程

    上面我们介绍了Initial Synchronization,也就是说同步源股票数据已经带过来了。如何同步主库接下来写入的数据?以下是帮助您入门的图表和具体步骤:

    MongoDB复制基本概念到整个架构深入浅出

    注意:这不一定是主要的。刚才提到同步的来源可能是Secondary。这里使用Primary主要是为了讨好大家。

    我们可以看到上面有6个步骤,每个步骤具体做的事情是:

    1. Secondary 初始同步完成后,开始增量复制,并在Primary oplog中设置游标。通过yield线程进行rs收集。并实时获取数据。
    2. Primary 将 oplog 数据返回到 secondary。
    3. Secondary 读取 Primary 发送的 oplog 并写入队列。
    4. Secondary同步线程将通过tryPopAndWaitForMore方法继续使用队列。如果每次都达到某个条件,则条件如下:
      1. 总数据超过100MB
      2. 已取部分数据但未到100MB,但当前队列无数据。这时候就会阻塞等待一秒。如果还没有数据,此时数据检索就完成了。

      满足上述两个条件之一后,数据将被prefetchOps方法处理。 prefetchOps方法主要是在数据库层面进行数据的拆分,方便后续多线程写入数据库。如果使用WiredTiger引擎,则使用Document ID进行分段。

    5. 最后将分割后的数据以多线程的方式批量写入数据库(MongoDB在从数据库批量写入数据时会阻塞所有的读取)。
    6. 然后将Queue中的Oplog数据写入Secondary中的oplog.rs集合中。

    4。 MongoDB高可用

    上面我们介绍了MongoDB复制数据同步。我们知道,除了数据同步之外,复制的另一个重要方面就是高可用性。公共数据库要求我们自己管理这些解决方案或使用第三方开源解决方案。MongoDB在内部实施了高度可用的解决方案。接下来我就给大家详细介绍一下MongoDB的高可用。

    4.1。触发切换场景

    首先我们来看看MongoDB进行主从切换的条件。

    1. 刚启动副本集
    2. 从库无法连接主库(默认超过10s,可以通过heartbeatTimeoutSecs参数控制),从库开始选举rs.stepdown命令
    3. 时主库无法与大部分节点通信
    4. 修改副本集配置时(Mongo 2.6 版本会触发,其他版本确定) 修改以下配置时:
      • _id
      • votes
      • priority
      • arbiterOnly
      • slaveDelayIndex
  5. 删除从库时(会触发MongoDB 2.6,MongoDB 3.4不起作用,其他版本等待OK)

4.2。心跳机制

通过上面的触发场景我们知道,MongoDB的心跳信息是MongoDB判断对方是否还活着的重要条件。当满足一定条件时,MongoDB的主数据库或从数据库将会触发切换。下面给大家详细介绍一下心跳机制

我们知道MongoDB副本集中的所有节点都会保持一次心跳,心跳频率设置为每2秒一次,也可以通过heartbeatIntervalMillis来控制。当添加新节点时,副本集中的所有节点都必须与新节点创建心跳。心率信息具体内容是什么?

心率信息内容:

BSONObjBuilder cmdBuilder;
cmdBuilder.append("replSetHeartbeat", setName);
cmdBuilder.append("v", myCfgVersion);
cmdBuilder.append("pv", 1);
cmdBuilder.append("checkEmpty", checkEmpty);
cmdBuilder.append("from", from);
if (me > -1) {
    cmdBuilder.append("fromId", me);
}

注:以上代码取自MongoDB的源码,构建心率信息片段。

MongoDB日志中具体表现如下: command admin.$cmd command: replSetHeartbeat { replSetHeartbeat: "shard1", v: 21, pv: 1, checkEmpty: false, from: "10.13.32.2144",: 400. fromId: 3 } ntreturn: 1 keyUpdates: 0

默认情况下,副本集中的所有节点每 2 秒向其他剩余节点发送一次上述信息。另一个节点收到信息后,会调用命令ReplSetCommand进行处理。心率信息,处理完成后,会返回以下信息:

result.append("set", theReplSet->name());
MemberState currentState = theReplSet->state();
result.append("state", currentState.s);  // 当前节点状态
if (currentState == MemberState::RS_PRIMARY) {
    result.appendDate("electionTime", theReplSet->getElectionTime().asDate());
}
result.append("e", theReplSet->iAmElectable());  //是否可以参与选举
result.append("hbmsg", theReplSet->hbmsg());
result.append("time", (long long) time(0));
result.appendDate("opTime", theReplSet->lastOpTimeWritten.asDate());
const Member *syncTarget = replset::BackgroundSync::get()->getSyncTarget();
if (syncTarget) {
    result.append("syncingTo", syncTarget->fullName());
}

int v = theReplSet->config().version;
result.append("v", v);
if( v > cmdObj["v"].Int() )
    result << "config" << theReplSet->config().asBson();

注:以上信息是正常情况下返回的,存在一些异常处理场景,这里不再详述。

4.3。切换过程

之前我们先了解一下MongoDB复制品结结之前触发切换的场景和心跳机制。我们看一下切换的具体过程:

  1. 从库无法连接主库,或者主库离开Primary 角色。
  2. 从库会根据心跳消息从当前节点获取角色,并与之前的比较
  3. 如果角色发生变化,就会开始执行msgCheckNewState方法
  4. 在msg方法中,最后调用CheckNewState方法selectSelf(最后会有一些判断来决定是否调用selectSelf方法)
  5. selectSelf方法最终向副本集中的其他节点发送replSetElect命令,请求投票。命令如下:
    BSONObj electCmd = BSON(
                           "replSetElect" << 1 <<
                           "set" << rs.name() <<
                           "who" << me.fullName() <<
                           "whoid" << me.hbinfo().id() <<
                           "cfgver" << rs._cfg->version <<
                           "round" << OID::gen() /* this is just for diagnostics */
                       );
    

    具体日志表现如下: 2017-12-14T10:13:26.917+0800 [conn27669] run command admin.$cmd { replSetElect: 1, set: "shard1", sing: "10.13. 32.244:40015", whoid: 4, cfgver: 27, round: ObjectId('5a31de4601fbde95ae38b4d2') }

  6. 当其他副本集收到replSetElect时,不会比较cf,不会比较cf。位于副本平面并确认节点优先级。该级别是否是副本集中所有节点中优先级最高的。只有满足条件,才会将投票信息发送给节点。
  7. 开始投票的节点最终计票数超过副本可参与投票数的一半,抢占成功,成为新的Primary。
  8. 如果其他从库发现角色同步源被改变,就会触发同步源的重新选举。

4.4、回滚

我们知道,切换时会出现数据丢失的情况,特别是主库宕机了,但新写入的数据没有时间同步到从库。此时,可能会出现数据丢失的情况。

针对这种情况,MongoDB增加了回滚机制。主数据库恢复后,将合并回复制集。此时,旧主库会将oplog信息与同步源进行比较。这种情况分为以下两种情况:

  1. 同步源中没有找到比旧主库更新的oplog信息。 。
  2. 同步源最新的oplog信息与最优hash内容以及旧主库中的oplog不一样。

针对以上两种情况,MongoDB会做回滚。回滚过程就是比较oplog信息,直到在主库和旧的同步源中找到匹配的oplog,然后将这段时间的所有oplog记录到回滚目录中。在这些文件中,如果出现以下情况,回滚将停止:

  1. 比较旧主库最优和同步源最优。如果超过30分钟,则放弃回滚。
  2. 回滚过程中,如果发现一条oplog超过512M,则放弃回滚。
  3. 如果有dropDatabase操作,则放弃回滚。
  4. 如果最后创建的回滚记录超过300M,回滚也会继续。

上面我们已经知道了MongoDB的回滚原理,但是在生产环境中如何避免回滚操作,因为毕竟回滚操作比较麻烦,对于顺序业务逻辑来说是不可接受的。那么MongoDB也提供了一个合适的解决方案,那就是WriteConcern。这里我就不详细说了。有兴趣的朋友可以详细了解一下。事实上,这也是CAP做出的选择。

5。MongoDB文案总结

MongoDB文案的内部原理已经给大家介绍完了,但是上面其实包含了很多细节,无法一一列举。如果你有兴趣,可以自己安排。这里还需要注意的是,MongoDB 版本速度更快,因此本文仅关注 MongoDB 2.6 到 MongoDB 3.4 版本。不过,某些版本可能会有一些细节上的变化,但大体逻辑保持不变。

赵静波,3年全职DBA经验,2017年DTCC讲师,目前主要负责新浪NoSQL服务的运维和研发。热衷于探索开源DB的内部原理。

版权声明

本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

热门