MongoDB副本集详解2-数据同步

作者: 峰巢 | 来源:发表于2018-03-21 16:06 被阅读190次

    本人博客同步发表,排版更佳

    数据同步

    Mongodb副本集里的Secondary会从Primary上同步数据,以保持副本集所有节点的数据保持一致。MongoDB副本集数据同步主要包含2个步骤:

    1. init sync 可以理解为全量同步
    2. oplog sync(replication)追同步源的oplog,可以理解为增量同步

    新加入的Secondary先通过init sync同步Primary上的全量数据,再通过oplog sync不断重放Primary上的oplog同步增量数据。


    数据同步过程

    init sync

    Secondary节点当出现如下状况时,需要先进行全量同步

    1. oplog为空
    2. local.replset.minvalid集合里_initialSyncFlag字段设置为true
    3. 内存标记initialSyncRequested设置为true

    这3个场景分别对应

    1. 新节点加入,无任何oplog,此时需先进性initial sync
    2. initial sync开始时,会主动将_initialSyncFlag字段设置为true,正常结束后再设置为false;如果节点重启时,发现_initialSyncFlag为true,说明上次全量同步中途失败了,此时应该重新进行initial sync
    3. 当用户发送resync命令时,initialSyncRequested会设置为true,此时会重新开始一次initial sync

    intial sync流程

    1. 全量同步开始,设置minvalid集合的_initialSyncFlag
    2. 获取同步源上最新oplog时间戳为t1
    3. 全量同步集合数据,local库除外(耗时)
    4. 获取同步源上最新oplog时间戳为t2
    5. 重放[t1, t2]范围内的所有oplog
    6. 获取同步源上最新oplog时间戳为t3
    7. 重放[t2, t3]范围内所有的oplog
    8. 建立集合所有索引 (耗时)
    9. 获取同步源上最新oplog时间戳为t4
    10. 重放[t3, t4]范围内所有的oplog
    11. 全量同步结束,清除minvalid集合的_initialSyncFlag

    oplog sync(replication)

    initial sync结束后,Secondary会建立到Primary上local.oplog.rs的tailable cursor,不断从Primary上获取新写入的oplog,并应用到自身。

    oplog

    Primary与Secondary之间通过oplog来同步数据,Primary上的写操作完成后,会向特殊的 local.oplog.rs 特殊集合写入一条oplog,Secondary不断的从Primary取新的oplog并应用。

    因oplog的数据会不断增加,local.oplog.rs被设置成为一个 capped集合 ,当容量达到配置上限时,会将最旧的数据删除掉。另外考虑到oplog在Secondary上可能重复应用,oplog必须具有幂等性,即重复应用也会得到相同的结果。

    如下oplog的格式,包含ts、h、op、ns、o等字段

    { 
        "ts" : Timestamp(1521169114, 4), 
        "t" : NumberLong(11), 
        "h" : NumberLong(-2875196737885853602), 
        "v" : NumberInt(2), 
        "op" : "i", 
        "ns" : "testdb.table3", 
        "ui" : BinData(4, "A0pnjSbATe+O+H51myfqwQ=="), 
        "wall" : ISODate("2018-03-16T02:58:34.156+0000"), 
        "o" : {
            "_id" : ObjectId("5aab32dc51382146343c0b03"), 
            "id" : NumberInt(3), 
            "type" : "database3", 
            "test" : "testval3", 
            "name" : "name3"
        }
    }
    
    • ts: 操作时间,当前timestamp + 计数器,计数器每秒都被重置
    • h:操作的全局唯一标识
    • v:oplog版本信息
    • op:操作类型
      • i:插入操作
      • u:更新操作
      • d:删除操作
      • c:执行命令(如createDatabase,dropDatabase)
      • n:空操作,特殊用途
    • ns:操作针对的集合
    • o:操作内容,如果是更新操作
    • o2:操作查询条件,仅update操作包含该字段

    oplog sync过程

    Tailable cursor每次会获取到一批oplog,Secondary采用多线程重放oplog以提高效率,通过将oplog按照所属的namespace进行分组,划分到多个线程里,保证同一个namespace的所有操作都由一个线程来replay,以保证统一namespace的操作时序跟primary上保持一致(如果引擎支持文档锁,只需保证同一个文档的操作时序与primary一致即可)。

    1. producer thread,这个线程不断的从同步源上拉取oplog,并加入到一个BlockQueue的队列里保存着。
    2. replBatcher thread,这个线程负责逐个从producer thread的队列里取出oplog,并放到自己维护的队列里。
    3. sync线程将replBatcher thread的队列分发到默认16个replWriter线程,由replWriter thread来最终重放每条oplog。
    • 拉取oplog是单线程进行,所以设计上producer thread只干一件事。
    • oplog重放时,要保持顺序性,而且遇到createCollection、dropCollection等DDL命令时,这些命令与其他的增删改查命令是不能并行执行的,而这些控制就是由replBatcher来完成的。
    • 同一个namespace的所有操作都由一个线程来replay,以保证统一namespace的操作时序跟primary上保持一致

    同步场景分析

    1. 副本集初始化

      • 初始化选出Primary后,此时Secondary上无有效数据,oplog是空的,会先进行initial sync,然后不断的应用新的oplog
    2. 新成员加入

      • 因新成员上无有效数据,oplog是空的,会先进行initial sync,然后不断的应用新的oplog
    3. 有数据的节点加入

      有数据的节点加入有如下情况:

      • 该节点与副本集其他节点断开连接,一段时间后恢复
      • 该节点从副本集移除(处于REMOVED)状态,通过replSetReconfig命令将其重新加入

      此时,如果该节点最新的oplog时间戳,比所有节点最旧的oplog时间戳还要小,该节点将找不到同步源,会一直处于RECOVERING而不能服务;反之,如果能找到同步源,则直接进入replication阶段,不断的应用新的oplog。

      因oplog太旧而处于RECOVERING的节点目前无法自动恢复,需人工介入处理(故设置合理的oplog大小非常重要),最简单的方式是发送resync命令,让该节点重新进行initial sync。

    其他

    oplog大小

    默认下,oplog大小会占用64位的实例5%的可用磁盘空间,在一些场景下oplog太小会导致同步失败等问题。动态修改oplog大小参考如下方法:

    db.runCommand({collMod: "oplog.rs", maxSize: 1024000000})
    

    参考链接

    http://www.mongoing.com/archives/2369
    http://www.mongoing.com/archives/3076

    相关文章

      网友评论

        本文标题:MongoDB副本集详解2-数据同步

        本文链接:https://www.haomeiwen.com/subject/egmgqftx.html