定期备份大文件

近期线上积累了不少远程不方便排查的问题,需要去现场一并排查,下面说的会话日志自动备份偶尔会失败就是其中一个。该功能大致是定期将生成的会话日志文件通过自主开发的FTP客户端上传到预先配置好的标准FTP服务器上。

我们查看了文件备份记录,发现备份失败的都是些比较大的文件,查看操作相关的日志信息,显示打开文件失败,看样子是系统能打开的文件有大小限制,上网搜索确有此事,32位系统默认情况下fopen只能打开大小不超过2GB的文件,若要打开更大的文件,可以定义宏_FILE_OFFSET_BITS=64突破限制。为慎重起见,我执行getconf LONG_BIT确认系统确实是32位,并用dd命令快速生成一个2G多的文件,写程序用fopen打开它,发现确实会失败。

看来问题已经定位了,我把宏定义加入Makefile中编了一个新的文件,本地测试没问题后替换到环境中,重新备份失败记录中的会话,刚开始的几条还正常,接着出现一条仍然失败的记录,查看日志并未发现异常。我放弃图形界面,到命令行中执行备份操作,居然出了段错误,备份对象是一个4G多的文件。

由于客户一直在旁边盯着,我感到很不自在,查代码半个多小时仍没有任何进展。受挫后我决定在本地开GDB调试,很快就找到了出错点,是在一个sprintf语句中出的段错误。

sprintf(buf, "\r%-12lu\r%s%s\n", file_stat.st_size, remotepath, filename + length);

仔细观察这行代码,我并没有发现有什么不妥,查看三个变量的的值都是正常的,考虑到后两个变量都是简单的字符串类型,我把目光转向了第一个变量,用ptype查看其类型,发现是long long int,联想到之前加的_FILE_OFFSET_BITS=64宏,而st_size的类型定义是off_t,我猜想off_t的类型可能与size_t类似,在32位环境中默认是long int,占4字节,因此用%lu打印是正确的,而使用_FILE_OFFSET_BITS=64宏后,off_t的类型由long int变成long long int,占8字节,此时再用%lu打印就不对了,但光打印一个数字并不致命,充其量打出来的数值与实际不符,问题的关键在于后面还跟了%s的字符串输出。经验证,确实如此,我将上述代码中的%-12lu改成%-12llu就没再报段错误了。

问题并没有就此完结,在等待上传一段时间后,页面仍提示备分失败,我沮丧到了极点。查看日志,显示信息处理函数被触发,发送或接收超时。原来为了避免在传输过程中出现因网络异常等原因导致一直卡住问题,在每个文件传输之前程序都会设置个5分钟的定时器,如果时间到了仍没有传完,则认为上传失败。一般来讲,会话日志也就几MB,不会有问题,但这个客户用的都是图形协议,且每次运维操作时间很长,甚至下班后也不关掉,导致会话日志好几个GB,5分钟传完这么大个文件不现实。

把定时器时间改长似乎也不妥,我采用另外一种方法,将定时机制稍做修改,取消原来外层的5分钟定时器,改成每发一个数据包前设置1分钟定时器,完美地解决了这个问题。

总结

Table of Contents