Devops必须留意的Mongo副本集Resync的坑

lieee 8年前
 

Devops一般很少时间会花在数据库的部署上,只有到了不得不去考虑的情况下,才会去考虑如何调整数据库,以适应业务的发展。mongodb 本身就很适合Devops,大部分情况下,部署基本按照说明操作一下即可。但实际操作起来,其实还有会有一些坑,比如Resync。

正常同步和oplog

mongo副本集,在正常工作的情况下,是以一种不断的、异步的方式进行同步。每个副本都会不断的记录自己进行的操作进入到oplog表里;Secondary副本就会不断的从Primary副本那里同步最新的操作,从而完成整个副本集的同步。当然Secondary的副本也可以选择从其他的Secondary副本同步,因为每个副本都会记录oplog。详细的描述可以参考官方文档 Replica Set Data Synchronization

Oplog 是一个特殊的固定大小的collection,固定大小意味着,新的操作记录的写入会导致最老的操作记录的删除,以保证oplog的大小。这个值如果不去设置,mongo会自动根据硬盘大小的5%来设定。大部分情况下没有什么问题,但有一个非常重要的设(da)定(keng):

oplog一旦大小固定,那么只能通过重启+配置来进行修改

为什么这会是一个坑,后面会继续讨论。

同步延迟lag和optime

副本集之间一般通过网络连接,副本集之间的性能也有可能有差异(当然最好不要有),所以同步操作有可能出现毫秒级别的延迟,甚至到1s以上,这个可以通过在任意一个副本集上执行

rs.status()

来查看所有副本集的同步状态。这个打印出的各个副本的optime,就是这个副本最后一条操作执行的时间,Secondary和Primary之间optime的时间差,其实就是同步延迟。

同步延迟一般情况下,顶多也就一两秒,但是一些异常情况,例如宕机、长时间过载overload,可能会导致这个时间越来越长。这里,我们的oplog的巨大作用就显现出来了!

Secondary和Primary之间的同步差,最大不能超过 Primary 的oplog能存储的条数

注意!因为Secondary是从Primary同步oplog,所以,这里只与Primary的oplog大小有关, 与Secondary自身记录的oplog无关 !当然,如果Secondary是从其他的Secondary同步数据,那么至于同步目标的oplog有关。

为了帮助用户直观的理解oplog里面存储了多少条操作,mongo还额外提供了两个数据:

  1. tFirst 第一条oplog的时间

  2. tLast 最后一条oplog的时间

这两个数据,可以通过在primary上执行:

db.getReplicationInfo()

获得。tLast - tFirst就是mongo同步机制所允许你进行停机同步数据的最大时间,当然这个时间不是固定的,如果当前的负载很低,相当于相同的oplog表可以存更长时间内的操作数据,就会给你留更多的停机操作时间。

上图中,Secondary落后于Primary,也就是说,同步延迟有10分钟(两个optime相减),但此时,Primary上存有最近20分钟的oplog,那么Secondary通过获取这些oplog,仍然能够在短时间内赶上Primary的进度。

但是,一旦optime的差距超出了Primary的tFirst,情况就不妙了,如下图:

此时,自动的同步已经无法完成同步了(stale),必须执行手动操作Resync。而且Secondary已经降级为Recovering,将无法接受请求,以及不能变成master。

Resync机制

Resync机制 官网有详细的介绍,基本思路就是把数据拷贝过来,再进行上面的oplog的同步。例如:

  1. 使用磁盘快照,把快照的数据库文件直接覆盖到Secondary上,重启Secondary

  2. 使用Initial Sync,把Secondary的数据清空,让mongo自动从0开始,重新同步所有数据

磁盘快照看起来很美好,但是是mongo的数据存储是分配后就不返回的,也就是说实际占用的磁盘空间要比真实数据的大小要大,使用操作系统的 scp也好rsync也好,这些无用的空间也会被复制,耽误复制的时间。除此之外,建立快照本身也需要耗时,反正在阿里云上建快照并不快,200G的数据大约要1小时多。

而Initial Sync是mongo之间拷贝表数据,拷贝完了就地重建索引,所以相当于只传输了真实的表数据,连索引数据都不用传输,从整体的复制时间来看更加节省,但是会对拷贝对象Primary有一些性能影响,但毕竟只是读,而且不需要Primary停机。

Resync的大坑

无论使用上面的哪种Resync机制,思路都是一致的,通过某种快速的方式同步更多的数据,然后剩下的使用oplog弥补在执行操作时的新操作。看起来很美好,而实际的执行过程中,如果操作的时间,要大于oplog所记录的时间,怎么办? 将永远无法不停机Resync成功!

这里Initial Sync为什么也不能成功呢?其实在Initial Sync的日志中,就可以看出来,在 STATUP2 的状态,就是一张表一张表的拷数据,也就是说,就算拷贝过程中的数据已经同步过去了,当拷贝下一张表时,上一张表的数据其实已经过期了。而当数据量很大的情况下(其实不需要太大,几百G),整个拷贝过程也要持续数小时,此时如果oplog的记录时间低于STARTUP2所需要花费的时间,恭喜你,你中奖了。

当然,如果你能有一台正常同步数据的Secondary,新的机器指向这台也是可以的,但是你的架构是一台Arbiter一台Primary一台Secondary的话……就没办法了,只能指望Primary了。别问我为什么知道,我就是知道。(╯‵□′)╯︵┴─┴

遇到这种情况怎么办?

  1. 半夜Primary停机,同步数据。这对于DBA也都不奇怪,只是在用了mongo集群后没享受到mongo的便利。当然,到了半夜负载下降,相当于oplog允许的操作时间变长了,也许不用停机。

  2. 如果有很多冗余数据、日志数据什么的,可以删除,从而降低Initial Sync花费的时间,那也是很值得尝试的!

坑的真正原因

上面的坑,其实主要是在于oplog的值相对于数据量过小的时候会出现。一般默认情况下,oplog取磁盘大小的5%似乎没太大问题。坑在哪呢?

  1. 磁盘扩容

  2. 高负载

一开始我就提到,oplog的存储大小一旦确定是不会改的,也就是说,一旦随着业务的发展,进行了磁盘扩容,或者移动到了一块更大的硬盘上,oplog的大小不会随之改变!

一旦两件巧合的事情遇到了一起:磁盘曾经扩容且没有额外考虑oplog;需要新增副本或者副本stale了;此时正常的机器只有一台Primary;就会变成一件解决起来不那么轻松的事情了。

而高负载也是潜在的另外一个可能,由于负载过高,虽然oplog存储很大,但是实际上oplog所支持的停机操作时间变少了,此时也会遇到相同的情况。

防坑指南

总结一下,在用mongo副本集群的时候,随着数据的增长、磁盘的扩容,一方面在考虑sharding的同时,一定要注意当前的oplog存储是否够用,提前为下一次部署策略更换准备好,给下一次的操作留够时间。特别是Devops,平时没时间管的,一定要未雨绸缪呀。