Devops一般很少时间会花在数据库的部署上,只有到了不得不去考虑的情况下,才会去考虑如何调整数据库,以适应业务的发展。mongodb本身 就很适合Devops,大部分情况下,部署基本按照说明操作一下即可。但实际操作起来,其实还有会有一些坑,比如Resync。
mongo副本集,在正常工作的情况下,是以一种不断的、异步的方式进行同步。每个副本都会不断的记录自己进行的操作进入到oplog表 里;Secondary副本就会不断的从Primary副本那里同步最新的操作,从而完成整个副本集的同步。当然Secondary的副本也可以选择从其 他的Secondary副本同步,因为每个副本都会记录oplog。详细的描述可以参考官方文档Replica Set Data Synchronization。
Oplog是 一个特殊的固定大小的collection,固定大小意味着,新的操作记录的写入会导致最老的操作记录的删除,以保证oplog的大小。这个值如果不去设 置,mongo会自动根据硬盘大小的5%来设定。大部分情况下没有什么问题,但有一个非常重要的设(da)定(keng):
oplog一旦大小固定,那么只能通过重启+配置来进行修改
为什么这会是一个坑,后面会继续讨论。
副本集之间一般通过网络连接,副本集之间的性能也有可能有差异(当然最好不要有),所以同步操作有可能出现毫秒级别的延迟,甚至到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还额外提供了两个数据:
tFirst 第一条oplog的时间
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机制官网有详细的介绍,基本思路就是把数据拷贝过来,再进行上面的oplog的同步。例如:
使用磁盘快照,把快照的数据库文件直接覆盖到Secondary上,重启Secondary
使用Initial Sync,把Secondary的数据清空,让mongo自动从0开始,重新同步所有数据
磁盘快照看起来很美好,但是是mongo的数据存储是分配后就不返回的,也就是说实际占用的磁盘空间要比真实数据的大小要大,使用操作系统的scp 也好rsync也好,这些无用的空间也会被复制,耽误复制的时间。除此之外,建立快照本身也需要耗时,反正在阿里云上建快照并不快,200G的数据大约要 1小时多。
而Initial Sync是mongo之间拷贝表数据,拷贝完了就地重建索引,所以相当于只传输了真实的表数据,连索引数据都不用传输,从整体的复制时间来看更加节省,但是会对拷贝对象Primary有一些性能影响,但毕竟只是读,而且不需要Primary停机。
无论使用上面的哪种Resync机制,思路都是一致的,通过某种快速的方式同步更多的数据,然后剩下的使用oplog弥补在执行操作时的新操作。看起来很美好,而实际的执行过程中,如果操作的时间,要大于oplog所记录的时间,怎么办?将永远无法不停机Resync成功!
这里Initial Sync为什么也不能成功呢?其实在Initial Sync的日志中,就可以看出来,在STATUP2的 状态,就是一张表一张表的拷数据,也就是说,就算拷贝过程中的数据已经同步过去了,当拷贝下一张表时,上一张表的数据其实已经过期了。而当数据量很大的情 况下(其实不需要太大,几百G),整个拷贝过程也要持续数小时,此时如果oplog的记录时间低于STARTUP2所需要花费的时间,恭喜你,你中奖了。
当然,如果你能有一台正常同步数据的Secondary,新的机器指向这台也是可以的,但是你的架构是一台Arbiter一台Primary一台Secondary的话……就没办法了,只能指望Primary了。别问我为什么知道,我就是知道。(╯‵□′)╯︵┴─┴
遇到这种情况怎么办?
半夜Primary停机,同步数据。这对于DBA也都不奇怪,只是在用了mongo集群后没享受到mongo的便利。当然,到了半夜负载下降,相当于oplog允许的操作时间变长了,也许不用停机。
如果有很多冗余数据、日志数据什么的,可以删除,从而降低Initial Sync花费的时间,那也是很值得尝试的!
上面的坑,其实主要是在于oplog的值相对于数据量过小的时候会出现。一般默认情况下,oplog取磁盘大小的5%似乎没太大问题。坑在哪呢?
磁盘扩容
高负载
一开始我就提到,oplog的存储大小一旦确定是不会改的,也就是说,一旦随着业务的发展,进行了磁盘扩容,或者移动到了一块更大的硬盘上,oplog的大小不会随之改变!
一旦两件巧合的事情遇到了一起:磁盘曾经扩容且没有额外考虑oplog;需要新增副本或者副本stale了;此时正常的机器只有一台Primary;就会变成一件解决起来不那么轻松的事情了。
而高负载也是潜在的另外一个可能,由于负载过高,虽然oplog存储很大,但是实际上oplog所支持的停机操作时间变少了,此时也会遇到相同的情况。
总结一下,在用mongo副本集群的时候,随着数据的增长、磁盘的扩容,一方面在考虑sharding的同时,一定要注意当前的oplog存储是否够用,提前为下一次部署策略更换准备好,给下一次的操作留够时间。特别是Devops,平时没时间管的,一定要未雨绸缪呀。