经验谈:用cp命令复制大量文件(432,000,000个,共39TB)

最近,我有一个复制大量文件的需求,虽然我已经在Unix各种变种上有超过20年的工作经验,但是我仍然被cp指令的行为震撼,我认为这些心得很值得和大家分享下。

首先介绍下机器:一台戴尔服务器(双核,内存最初是2G,后来扩展到10G,运行ubuntu系统),服务器配备的是全新戴尔存储套件MD1200,该套件包括12个4TB硬盘,其中40TB采用RAID 6配置,这样整个系统就可以容忍两块硬盘同时down掉。这台服务器主要用作异地备份,唯一的操作是IO写,由于我使用rsnapshot完成这项工作,因此大部分文件有较高的链接计数(30+)。

一天早上,我被告知一个硬盘down掉了。这不是什么大事,这种事情经常发生。我打电话给戴尔,戴尔第二天就给送来了一块替换盘。但我安装替换盘时发现,替换盘根本不能工作,并且另一块磁盘也down掉了。戴尔技术服务部门富有经验地建议我不要只换down掉的硬盘,因为整个磁盘阵列可能已经损毁了。就我所知,磁盘只有在有足够多坏块的情况下才会告警,设想这样一个场景:短时间内,一个文件位于三个磁盘上的备份块全部坏掉,那么很不幸,RAID引擎短时间内难以完成检测坏块、重新计算备份数据并存储这样一个流程,这样你就有丢失数据的风险了。因此,如果两个硬盘显式告警,你的数据可能已经丢失了。

现有存储套件容量已经难以达到要求,我们决定扩容,把文件从旧存储套件拷贝到新套件中。正常情况下,我会在块级别拷贝用dd指令或者pvmove指令拷贝数据,但是考虑到坏块,我决定采用文件级别的拷贝,因为这样的话,我就可以知道那些文件包含坏块了。我上网搜了相关经验,发现cp可以完美地解决这个问题。若想保存硬链接信息,需要记录有哪些文件已经被复制,所以我预订了8G的RAM,并配置了更大的交换区。

当新的存储套件到货,我就着手复制了,起初,根据iotop的测量数据,复制速度接近于300-400MB/s。不一会儿速度便显著下降了,因为大部分时间花在了建立硬链接上,并且要花时间去处理文件系统的一致性问题。为保持一致性,我们使用了XFS,由于没有关闭写屏障,我们深受性能之苦,但如果RAID控制器配置了带有备份电源的写缓存,一致性问题便可更好地解决。正如所料,cp的内存占用量稳步增加,很快便达到了GB级。

经历了几天的拷贝,问题来了:我发现系统已经停止拷贝,根据strace的显示,cp指令没有调用任何的系统调用函数。阅读相关源码后发现,cp指令会以一个哈希表跟踪有哪些文件被复制了,这个表需要时常地扩展自身容量以避免太多的冲突。当RAM被耗尽,哈希表的扩容过程会成为性能瓶颈。

我们相信cp的哈希表扩容后,cp指令会继续执行,果然不一会它又“复活”了,但是它会进入周期性的“扩容-拷贝-扩容-拷贝”的循环中,且扩容的时间变得越来越长。经过10天的拷贝,根据dd结果,新文件系统的块数量和inode节点的数量已经和原系统一样,但是让人吃惊的是cp指令并没有退出。再次阅读源码,我发现cp正在认真地解构哈希表(使用forget_all函数),由于cp进程需要的虚拟内存大小达到了17GB+,但服务器总共才10G内存,所以解构过程执行了很多RAM和swap区间的换页操作。

上面整个过程中,我使用了cp的-v选项,并使用tee把日志重定向到日志文件(很大的文件)中。cp的输出信息会被缓存,为使这些缓存信息全部刷新到日志文件中,我给了cp多于一天的时间进行解构哈希表。

运行”ls –laR”指令,查看下两个文件系统中的文件是否一致,发现除了日志文件的一些错误外,只有一个文件发生了io错误(还好,我们有它的另一个备份)。

这种错误不会马上复现,但是如果cp能够设计一种数据结构,在等待io的时候处理那些已经被记录的文件,那样处理效率会更高。此外,对于cp最后解构哈希表的forget_all函数,除了那些缺少可用内存管理模块的老式服务器一定需要它之外,我还没发现去掉这个函数会对整个拷贝过程产生什么不好的影响。

总结一下:

1、拷贝整个文件系统时,如果确定你的硬件和文件系统都OK,应该使用块级别的拷贝。这种拷贝方式更快,除非你有许多空闲块,但是无论如何它耗费的内存都是较少的;

2、如果要拷贝许多文件,并想要保留硬链接的信息,可以采用文件级拷贝。拷贝之前需要保证机器有足够多的内存;

3、比起简单粗暴地停止进程,认真地解构数据结构会花费更多的时间;

4、告警的硬盘的数量并不等同于有坏块的硬盘的数量。如果不走运,就算配置了RAID6,没来得及等3块磁盘告警,你的数据可能已经丢失。这个时候我们只能赌一把了。同样的情况适用于RAID 5,那怕只有一块磁盘甚至没有磁盘告警,如果不走运,你的数据照样丢!

希望这篇文章对你有益!

收藏 3 评论

关于作者:honpey

(新浪微博:@Honpey) 个人主页 · 我的文章 · 1

相关文章

可能感兴趣的话题



直接登录
最新评论
  • btsw   2015/03/19

    大兄弟阿,

    现求一句Linux下的文件|文件夹拷贝命令,还望赐教。

    现状
    现有大量文件(3TB))包括子文件夹,子子文件夹等在目录/home1/src/下面
    目的
    将/home1/src/下所有文件以及文件夹复制到/home2/dest/下

    需求
    同时保持中文文件名
    文件复制出错时继续复制
    将所有复制过程中出错的文件夹或文件(全路径)导出一个列表放在/home1/status.txt中
    最好完全复制完成后生成一个/home1/done.txt的空文件 或 把 done 加入status.txt 中

    谢谢各位

    PS,
    /home1/src/下可能有不同用户组的一些文件,复制不了的直接输出全路径到错误列表即可
    系统是DSM 5.1,相信一般的Linux命令应该可以用吧。
    CPU: MARVELL Armada 375 88F6720 2Core 800MHz
    RAM: 512 MB DDR3

    我写到 西面这样就写不下去了,也不敢测试不知有没问题 机器内存不大
    yes | /bin/cp -r /home1/src/* /home2/dest/ > /home1/status.txt 2>&1

  • 这文章写得真垃圾……
    还20多年的经验!这问题搞了起码超过半个月……

跳到底部
返回顶部