大批量文件自动清理的难点

Linux 文件删除机制

Linux 是通过 link 的数量来控制文件删除,只有当一个文件不存在任何 link 的时候,这个文件才会被删除。每个文件都有 2 个 link 计数器—— i_count 和 i_nlink。i_count 的意义是当前使用者的数量,i_nlink 的意义是介质连接的数量;或者可以理解为 i_count 是内存引用计数器,i_nlink 是硬盘引用计数器。再换句话说,当文件被某个进程引用时,i_count 就会增加;当创建文件的软连接或者硬连接的时候,i_nlink 就会增加。

对于 rm 而言,就是减少 i_nlink。这里就出现一个问题,如果一个文件正在被某个进程调用,而用户却执行 rm 操作把文件删除了,会出现什么结果呢?当用户执行 rm 操作后,ls 或者其他文件管理命令不再能够找到这个文件,但是进程却依然在继续正常执行,依然能够从文件中正确的读取内容。这是因为,`rm` 操作只是将 i_nlink 置为 0 了;由于文件被进程饮用的缘故,i_count 不为 0,所以系统没有真正删除这个文件。i_nlink 是文件删除的充分条件,而 i_count 才是文件删除的必要条件。

对于单个文件删除而言,我们可能完全不需要关心这个机制,但是对于大批量文件删除,这却是一个非常重要的因素,请允许我在随后章节中详细阐述,此处请先记下 Linux 的文件删除机制。

生成待删除列表

当一个文件夹下面有 10 个文件的时候,`ls` 可以一目了然,甚至可以用 `ls – alt` 查看所有文件的详细属性;当文件变成 100 个的时候,`ls` 可能只能看一看文件名了;文件数量上涨到 1000,多翻几页可能还能接受;如果是 10,000 呢? `ls` 可能需要等上半天才能有结果;再扩展成 100,000 的时候,绝大多数系统可能都没有反应,或者“Argument list too long”了。不止是 `ls` 会遇到这样的问题,其它的常用 Linux 系统管理命令都会遇到类似的问题,Shell 有参数来限制命令的长度。就算我们可以通过修改 Shell 参数来扩展命令长度,但这并不能提高命令的执行效率。对一个超大规模的文件系统而言,等待 `ls` 和 `find` 等常用文件管理命令的返回的时间是不可接受的.

那么我们如何能够在更大数量级的文件系统上生成删除文件列表呢?一个高性能的文件系统索引是一个好方法,不过高性能的文件索引是少数人的专利这也解释了为什么 google 和 baidu 能这么赚钱)。好在如此规模的文件系统一般都只存在于高性能文件系统里面,这些文件系统都提供了非常强大的文件管理功能。譬如前面提到的 IBM 通用并行文件系统GPFS)的 mmapplypolicy,通过直接扫描 inode 来快速扫描整个文件系统,并能够根据指定条件返回文件列表。下面演示如何根据时间戳和文件类型来获取文件列表

死锁对文件删除性能的影响

对于一个每天定时执行文件删除任务系统,首先生成待删除文件,然后把该列表作为输入执行删除操作;如果某天待删除列表特别大,导致第一天的删除任务还没有完成,第二天的删除任务就启动了,会有什么结果呢?

第一天还没有来得及被删除的文件会出现在第二天的删除文件列表中,然后第二天的文件删除进程会把它做为输出执行删除操作。此时,第一天的删除进程和第二天的删除都会尝试去删除相同的文件,系统抛出大量的 unlink 失败的错误,删除性能会受到很大的影响。删除性能的下降,会导致第二天的文件依然没有被删除,第三天的删除进程会加剧删除文件的死锁,进入删除性能下降的恶性循环。

如果简单的删除第一天生成的待删除列表,能够解决上述问题吗?不能。如前文所述的 Linux 文件删除机制,删除第一天的文件列表文件只能把该文件的 i_nlink 清零,当第一天的文件删除进程没有结束的时候,该文件的 i_count 就不为零,因而该文件不会被删除。直到该进程处理完列表中的所有文件,进程退出,第一天的待删除列表文件才真正被删除了。

我们至少需要在新的文件删除进程启动以前,把系统中其它的文件删除进程终止,才能保证不会发生删除死锁的情况。但是这样做,依然存在一些弊端。考虑到极端情况下,如果连续一段时间删除进程都无法在一个周期内完成删除任务,那么待删除列表就会不断增长,文件扫描时间会延长,进而挤占文件删除进程的工作时间,陷入另外一个恶性循环。

而且实战经验告诉我们,当删除列表特别巨大时,删除进程的工作性能也有所下降。而一个适当大小的参数输入文件,能够保证进程有效执行。所以,按照固定尺寸将待删除列表文件分割成一系列文件,能够让删除操作稳定高效的执行。而且,在存储和主机性能允许的前提下,分割为多个文件还可以允许我们并发执行多个删除进程。


相关内容