您的位置:首页 > 数据库 > Mongodb

MongoDB实战-复制(副本集的复制原理)

2017-08-29 18:35 148 查看
    学习了如何创建副本集,如何使用副本集,认知了其故障转移策略。我们还需从复制的基础原理了解副本集的工作方式。副本集主要依赖于两个基础机制:oplog和“心跳“。oplog让数据的复制成为可能,而“心跳”则监控健康情况并触发故障转移,后续将看到这些机制时如何运作的。你应该已经逐渐开始理解并能预测副本集的行为了,尤其在故障发生的情况下。

1.关于oplog

    oplog是MongoDB复制的关键。oplog是一个固定集合,位于每个复制节点的local数据库里,记录了所有对数据的变更。每次客户端向主节点写入数据,就会自动向主节点的oplog里添加一个条目,其中包含了足够的信息来再现数据。一旦写操作被复制到某个从节点上,从节点的oplog,从节点的oplog中也会有一条关于写入的数据。每个oplog条目都是由一个BSON时间戳进行标识的,所有的从节点都使用这个时间戳来追中他们最后应用的条目。

   为了更好的理解其原理,我们仔细看看真实的oplog以及其中的记录。连接上主节点后,切换到local数据库。local数据库中保存了所有的副本集元数据和oplog。当然,数据库本身不能被复制。正如其名,local数据库里的数据对本地节点而言是唯一的,因此不该复制。查看local数据库,会发现一个叫oplog.rs的集合,每个副本集都会把oplog保存在这个集合里。如下图所示:

 

 

    replset.minvalid包含了指定副本集成员的初始同步信息。system.replset保存了副本集配置文档。me和slaves用来实现写关注。startup_log是启动信息。还有replset.election包含了节点的选举信息。我们会将主要的精力集中在oplog上,查询一条前面你插入数据的那个操作相关的op条目。执行:
db.oplog.rs.find({op:"i"})在主从节点执行的结果相同,如下图:





结果中,第一个字段为ts,保存了该条目BSON时间戳。这里要特别注意,shell使用TimeStamp对象来显示时间戳的,包含两个字段,前面一部分是从纪元开始的秒数,后面一个市计数器。h表示该操作的一个唯一的ID,每个操作的该字段都是一个不同的值。字段op表示操作码,它告诉从节点该条目表示什么操作,本例子中i表示插入。op后的ns标明了有关的命名空间(数据库和集合)。o对插入操作而言包含了所插入的文档的副本。

     在查看oplog条目时,对于那些影响多个文档的操作,oplog会将各个部分都分析到位。对于多项更新和大批量删除来说,会为每个影响到的文档创建单独的oplog条目。例如,你想集合中插入几本狄更斯的数

db.books.insert({title:"A Tale of Two Cities"})
db.books.insert({title:"Great Expections"})
db.books.udpate({},{$set:{author:"Dickens"}},false,true)
执行完上述语句后,oplog中查询op:"u"的选项,结果如下:





      如你所见,每个节点都有自己的oplog条目。这种正规化是更通用的一种策略中的一部分。它会保证从节点总是能和主节点拥有一样的数据。要确保这一点,每次应用的操作就必须是幂等的--一个指定的oplog条目被应用多少次都无所谓:结果总是一样的。其他文档操作的行为是一样的,比如删除,你可以试试不同的操作,查看其在oplog中最终是什么样的。要取得oplog的当前状态的基本信息,可以运行

db.getReplicationInfo()



    这里有oplog中第一条河最后一条时间戳,你可以使用$natural排序修饰符手工找到这些oplog条目。例如下面这句用于获取最后一个条目
db.oplog.rs.find().sort({$natural:-1}).limit(1)


     关于复制,还有一件重要的事情。即从节点是如何确定它们在oplog里的位置的。答案在于从节点自己也有一份oplog。这对主从复制的一项重大改进。因此值得深究其中的原理。假设向副本集的主节点发起写操作,接下来会发生什么?写操作先被记录下来,添加到主节点的oplog里。与此同时,所有从节点复制oplog。因此,当某个从节点准备更新自己时,他做了三件事:首先,查看自己oplog中最后一条的时间戳;其次,查询主节点oplog中所有大于此时间戳的条目;最后,把那些条目添加到自己的oplog中并应用到自己的库中。也就是说,万一发生故障,任何被提升为主节点的从节点都会有一个oplog,其他从节点都能以他为复制进行复制。这项特性对副本集的恢复是必须的。
   从节点使用长轮询(long polling)立即应用来自主节点oplog的新条目。因此从节点的数据通常是最新的。由于网络分区或者从节点本身进行维护造成数据陈旧的,可以使用从节点oplog的最新的时间戳来检测网络延迟。

2. 停止复制

      如果从节点在主节点的oplog里找不到它所同步的点,那么就会永久停止复制。发生这种情况是,你会从日志中看到如下异常:

repl:replication data too stale,halting
Tus Aug 28 14:19:27[replsecondary]caught SyncException     回忆一下,oplog是一个固定集合,也就是说集合里的条目最终都会过期。一旦某个从节点没能在主节点的oplog找到它已经同步的点,就无法保证这个从节点是主节点的完美副本了。因为修复停止复制的唯一途径是重新完整同步一次主节点的数据,所以要竭尽全力避免这个状态。为此,要监测从节点的延时情况,针对你的写入要有足够大的oplog。下面我们需要讨论oplog的大小设置。

3. 调整复制OPLOG大小
     因为oplog是一个固定集合,所以一旦创建就无法重新设置大小,为此要慎重选择oplog的大小。默认的oplog大小会随着环境发生变化。在32系统上,oplog默认是50MB,而在64位系统上,oplog会增大到1GB或空余磁盘空间的5%。对于多数部署环境,空闲磁盘空间的5%绰绰有余,对于这种尺寸的oplog,要意识到一旦重写20次,磁盘可能就满了。

    因此默认大小并非适用所有应用程序。如果知道应用程序写入量会很大,在部署之前就应该做些测试。配置好复制,然后以生产环境的写入量向主节点发起写操作,向这样对服务器施压起码一小时,完成后,连接到任意副本集成员上,获取当前复制信息:

db.getReplicationInfo()

    一旦了解了每小时会生成多少oplog,就能决定分配多少oplog空间了。你应该为从节点至少下线八小时做好准备。发生网络故障或类似事件时,要避免任意节点重新同步完整数据,增加oplog的大小能够为你争取更多时间。如果要修改默认的oplog的大小,必须每个成员节点首次启动时使用mongod的-oplogSize选项,其值的单位是兆。可以像下面这样启动一个1GB oplog的mongod实例

mongod -replSet mySet -oplogSize 10244. "心跳"检测和故障转移
      副本集的“心跳”检测有助于选举和故障转移。默认情况下,每个副本集成员每两秒钟ping一次其他所有成员。这样一来,系统就可以弄清楚自己的健康状况。在运行rs.status()时,你可以看到每个节点上次“心跳”检测的时间戳和健康状况(1表示健康,0表示没有应答)

     只要每个节点都保持健康且有应答,副本集就能快乐地工作下去。但如果哪个节点失去了相应,副本集就会采取措施。每个副本集都希望确认无论何时都恰好存在一个主节点。但这仅在大多数节点可见时才有可能。例如:如果杀掉从节点,大部分节点依然存在,副本集不会改变状态,只是简单地等待从节点重新上线;如果杀掉主节点,大部分节点依然存在,但没有主节点了。因此从节点自动提升为主节点,如果碰巧有多个从节点,那么会推选状态最新的从节点为主节点。

     但还有其他可能的场景,如果从节点和仲裁节点都被杀掉了,只剩下主节点,但是没有多数节点--原来的三个节点里只有一个节点仍处于健康状态。在这种情况下,主节点的日志中会有如下信息 

Tus Aug 28 19:19:27 [rs Manager] replSet can't see a majority of the set relinquishing primary
Tus Aug 28 19:19:27 [rs Manager] replSet relinquishing primary state
Tus Aug 28 19:19:27 [rs Manager] replSet SECONDARY



   没有了多数节点,主节点会把自己降级为从节点。刚开始有点费解,但仔细想想,如果该节点仍然作为主节点存在的话会发生什么情况呢?如果出于某些网络原因心跳检测失败了,那么其他节点仍然是在线的。如果仲裁节点和从节点依然健在,并能看到对方,那么根据多数节点原则,剩下的从节点会自动变为主节点。要是原来的主节点没有降级,那么就会陷入不堪一击的局面:副本集中有两个主节点。如果应用程序继续运行,就可能对两个不同的主节点做读写操作。肯定会有不一致,并伴随着奇怪的现象。因此,当主节点看不到多数节点时,必须降级为从节点。
5. 提交与回滚  关于副本集,还有最后一部分需要了解,那就是提交的概念。本质上,你可以一直向主节点做写操作,但是那些写操作在被复制到大多数节点前,都不会被认为是已提交的。这里说的已提交是什么意思呢?举例来说,你向主节点发起一系列写操作,出于某些原因(连接问题、从节点为备份而下线、从节点有延迟等)没有被复制到从节点。现在假设从节点突然被提升为主节点,你向新的主节点写数据,而最终老的主节点再次上线,尝试从新的主节点做复制。这里的问题在于老的主节点里有一系列写操作并未出现在新的主节点的oplog中,这就会触发回滚。

   在回滚时,所有未复制到大多数节点的写操作都会被撤销。也就是说会将它们从从节点的oplog和它们所在的集合里删掉。要是某个从节点登记了一条删除,那么该节点会从其他副本里找到被删除的文档并进行恢复。删除集合以及更新文档的情况也是一样的。

    相关节点数据路径的rollback子目录中保存了被回滚的写操作。针对每个有回滚写操作的集合,会创建一个单独的BSON文件,文件名里包含了回滚的时间。在需要恢复被回滚的文档时,可以用bsondump工具来查看这些BSON文件,并可以通过mongorestore手工进行恢复。

    万一你真的不得不恢复被回滚的数据,你就会意识到应该避免这种情况。幸运的是,从某种程度上来说,这是可以办到的。要是应用程序能够容忍额外的写延时,那么就能用后面会介绍学习的写关注,以此确保每次(也可能是每隔几次)写操作都能被复制到大多数节点上。使用写关注,或者更通用一点,监控复制的延迟,能帮助你减轻甚至避免回滚带来的全部问题。

    
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息