nocache在文件备份时的应用

背景介绍

我目前负责运维的一个项目,目前搭建在国内某云平台(12核28G内存)上。经过一段时间的运行,可以确定当前最大的瓶颈在于内存,平均一个进程大概需要3G内存,内存消耗最多的可能要用到16G左右,且为了节约成本,一台机器上可能有3到5个这样的进程。

这些进程所使用的数据库每三小时必须备份一次,这些数据库在磁盘中占用空间的大小平均是1G。备份的原理很简单,就是将数据库文件通过rsync拷贝到本地的某个固定目录,然后用tar打包压缩。为什么用rsync而不是cp,主要是为了减少读写。

备份时会产生大量的文件cache。导致可用内存大量减少。当然这里的可用内存指不包含buffercached的部分(下同)。虽然我们知道,文件cache会在一定条件下(可用内存少于内核参数/proc/sys/vm/min_free_kbytes,我们系统默认是67584,也就是66MB)由内核进程kswapd释放,如果不达到,系统选择不作为。因此每当备份时,内核将文件读入内存,但又不会释放,久而久之,内存使用率经常维持在90%以上。

内存是否够用,我认为有两方面:

  1. 可用内存(指立即可以给用户层进程使用的内存)是否足够;
  2. 内存分配是否足够快。

如果当用户进程突然需要一大片内存时,很明显,由于需要kswapd进行页扫描再释放内存页,内存分配是有一定lantency的,可能会对业务进程的会造成影响。对于我们项目来说,如果突然有大量用户上线时,或者是大量用户操作频繁时,可能就会出现用户投诉使用过程会卡,甚至掉线。

根据我们备份的场景,这些文件cache再被读一遍的情况几乎没有。因此,这些文件cache不仅没有起到所谓的cache作用,同时还会损害业务性能。


手动释放cache

解决cache对系统的影响,最直接的办法就是在备份脚本最后,增加手动释放cache的步骤:

sync && sync && echo 3 > /proc/sys/vm/drop_caches  

这种方法属于事后补救的做法。在我们的项目中使用了相当长一段时间。我们为什么要改变这种方式呢,主要是以下三个方面:

  1. 根据一些文档的介绍,这种操作对系统性能、特别是文件I/O对伤害巨大,不宜频繁操作。
  2. 况且这种一刀切的做法是将所有的文件cache都释放了,包括一些需要在内存中当cache的部分,而我们只需要释放备份过程中的cache。
  3. 另外在某次业务端更新了程序后,出现进程申请内存产生大量内存碎片,每个业务进程比之前多使用了2到4G内存。每次备份的时候,由于内存紧张,内核将内存中的不常使用的部分先换出到swap分区,然后才能将文件读入内存作为文件cache,备份完后释放cache,但是swap分区没办法释放,随着备份次数的增加,久而久之,swap分区的利用率也在持续上涨,如图:
    甚至100%:

nocache

既然事后补救的方式并不靠谱,我们就要考虑在备份过程中如何做到不带cache进行备份。

万能的google之后,我发现了一个nocache的项目,实现起来非常简单,只要编译安装nocache之后,在要调用的命令前面加nocache就可以了,比如nocache tar czf db_backup.tar.gz database

然而这里有个问题,nocache对于rsync是不起作用的,从nocache的github页面上是这么说的:

Most of the application logic is from Tobias Oetiker's patch for rsync http://insights.oetiker.ch/linux/fadvise.html. Note however, that rsync uses sockets, so if you try a nocache rsync, only the local process will be intercepted.  

于是我从这个链接中了解到有--drop-cache选项的rsync补丁,又是一番google之后,发现了这个项目可以解决问题。

于是最终我们的方案是:

  1. 利用打过补丁的rsync --drop-cache同步需要备份的文件;
  2. 使用nocache调用tar命令进行打包压缩。

修改过后的脚本:

if [ -e "/usr/bin/new_rsync" -a -e "/usr/local/bin/nocache" ]; then  
    rsync_bin="/usr/bin/new_rsync --drop-cache --drop-cache-remote --rsync-path=/usr/bin/new_rsync"
    nocache_bin="/usr/local/bin/nocache"
else  
    rsync_bin="/usr/bin/rsync"
    nocache_bin=""
fi  
...
    ${rsync_bin} -av /data/database/ /data/backup/ 
...
    cd /data/backup/ && ${nocache_bin} tar czf ${backup_file} ${db}

实施效果

看下内存的情况:

可以看到剩余内存可以稳定在10G以上,而之前除了释放cache之后短暂上升之外,基本上都是在1G以下。关键是pgscank这个预示内存瓶颈的指标现在都是0,说明当前内存性能良好,另外其他的性能指标,包括CPU、磁盘I/O等均未出现大幅度变化,经过了近3个星期的实施后,下一步就可以在一个服务器上多塞几个业务进程进一步减少机器成本了。