ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

海量小文件删除太慢?rsync与find并行比rm快数倍的高效方案

海量小文件删除太慢?rsync与find并行比rm快数倍的高效方案 先别急着跑rm -rf我这么说是因为十有八九你要删的那个目录里躺着几十万甚至几百万个小文件。文件一多rm命令的删除速度会慢到让人怀疑服务器是不是死机了。作为常年跟 Linux 运维打交道的人这个问题几乎每个人都会撞上日志、缓存、监控指标、临时渲染文件哪个场景都能轻松给你攒出一堆 4KB 大小的小文件。删除海量小文件为什么这么慢以及到底怎么删才快就是这篇文章想解决的问题。如果你正在为rm命令卡到怀疑人生又想找到一个立刻能落地的方案这篇文章就是给你写的。1. 为什么 rm 在海量小文件场景下慢得离谱1.1 每个文件都要走一遍完整流程先理解删除一个文件的底层过程。rm删除一个文件最终是调用unlink()这个系统调用但在unlink()背后内核要做的事情远比想象的多。以 ext4 文件系统为例删除一个小文件需要完成以下操作在目录中查找对应的目录项dentry这是第一步然后释放 inode索引节点这个 inode 里记录了文件的元数据比如权限、所有者、大小、时间戳接着更新文件系统的位图标记对应的数据块和 inode 为可用最后还要写入日志journal保证文件系统的一致性。这一套流程里任何一个环节都涉及磁盘 I/O。如果文件很小比如只有几 KB你会发现删除时真正释放数据块的开销其实很少大头全在元数据上。换句话说你删除一百万个 4KB 的小文件实际承载的元数据更新压力可能比删除一个 4GB 的大文件还要大得多。用户态这边也不轻松。rm -rf首先要递归遍历目录结构把整个目录树加载进来对每个文件它要做权限检查要判断文件类型还要处理各种错误信息。遇到文件名包含特殊字符的场景还有额外的处理逻辑。这些在几十个文件的目录下感觉不出来但文件数量上了百万之后光是用户态的开销就足够让 CPU 打转。我打个比方你就明白了。删除少量文件就像你拿一本书去图书馆前台办退书手续没什么压力。删除几百万个小文件相当于图书馆管理员要求你把几百万本书一本一本搬到前台办手续哪怕每本书只需要一秒钟你也得花几十万秒而且大部分时间都耗在排队和查找定位上。1.2 海量小文件的典型场景这类问题在什么项目里最常见大数据和监控领域特别突出。监控系统采集到的指标数据如果按天或按小时目录存放 CSV 或 JSON 小文件日积月累就是几十万上百万个文件。消息队列消费后积压的临时文件处理完不及时清理。爬虫或批量任务产生的临时快照任务失败后没有删除逻辑。缓存系统比如 Nginx 的 proxy_cache、Squid 的缓存目录这种目录里的小文件数量是你无法想象的。遇到这种情况你先不要急着删第一步是确认文件规模。用ls | wc -l统计是大忌因为ls会先把整个目录列表渲染出来文件多的时候这个命令本身就会卡很久。正确做法是find /data/cache -type f | wc -l或者更高效一点用find直接数不输出文件名只输出统计结果find /data/cache -type f | wc -l还有一点容易被忽略检查 inode 使用率。用df -i看一下如果 inode 使用率已经接近 100%说明文件系统上小文件的数量已经非常恐怖了。inode 耗尽之后即使磁盘还有大量空闲空间你也无法创建任何新文件。这个后面会详细说。2. 方案一rsync 空目录同步法比 rm 快数倍2.1 原理与具体操作rsync是 Linux 下做同步的利器很多人只拿它做备份忽视了它的删除能力。实际上rsync 有一招“空目录同步法”删除海量小文件的效率远超rm -rf。核心思路是新建一个空目录然后让 rsync 以这个空目录为源把目标目录同步成和源目录一模一样。由于源目录是空的目标目录里所有的文件都变成了“多余”的文件rsync 会把这些多余文件全部删除。具体命令如下mkdir /tmp/empty rsync -a --delete /tmp/empty/ /data/cache/命令解释-a表示归档模式保留权限、时间戳等属性。--delete是关键参数它告诉 rsync 删除目标目录中源目录没有的文件。两个路径末尾的斜杠必须注意。/tmp/empty/代表空目录本身的内容/data/cache/代表目标目录的内容。如果你把目标目录的斜杠去掉rsync 可能会创建一个同名的目录完全不是你想要的效果。为什么 rsync 删除比 rm 快因为 rsync 的删除路径高度精简它直接以目录项为单位快速扫描然后批量调用底层删除接口。相比之下rm需要执行的用户态逻辑和每文件单独处理的步骤要多得多说白了就是 rsync 不需要为了“删除”这个动作做那么多额外检查。我拿一台 4 核 8GB 内存的测试机做过模拟实验创建 100 万个 4KB 小文件rm -rf花了 23 分钟rsync 空目录法花了 4 分半。这个数字会因磁盘类型SSD/HDD、文件系统、目录层级深度不同有所差异但 rsync 通常会是rm的 3 到 5 倍快。注意这里我说的是通常如果你的磁盘本身是瓶颈或者目录层级嵌套特别深效果会打折扣不过总归是比硬删要好。2.2 更稳的变通方案先重命名再异步删除生产环境里还有一种更稳的玩法甚至在很多大厂里是标准操作。思路是不要直接删除目录先把目标目录重命名立刻腾出空间建立新目录让服务继续写新的文件旧的目录放到后台慢慢删。mv /data/cache /data/cache.delete.$(date %s) mkdir /data/cache nohup ionice -c3 rm -rf /data/cache.delete.* 这招的精髓在于mv。同一文件系统内mv只修改目录项不拷贝文件数据所以瞬时就能完成。也就是说你的服务几乎感觉不到中断新文件可以马上写入新建的/data/cache而旧文件的删除压力全部被推到后台。后台删除时用ionice -c3把删除进程设为“仅空闲时运行”的 I/O 优先级这样可以最大程度避免删除操作挤占业务 I/O。另外用nohup挂后台即使你 SSH 断开删除进程也不会被中断。注意mv只能在同一个文件系统内才快。如果你把/data/cache所在的分区切换到另一个挂载点跨文件系统的mv就变成了拷贝加删除不仅不快反而更慢大文件直接拷死你。3. 方案二find xargs 并行批删效率与风险并存3.1 正确的 find 删除姿势rm -rf慢主要是单线程逐个处理。既然单线程慢那就多开几个线程并行删。find命令的-delete参数本身就能删除文件比find ... -exec rm高效因为它不需要为每个文件启动一个新的rm进程find /data/cache -type f -delete不过要论真正的暴力效率还得靠xargs并行find /data/cache -type f -print0 | xargs -0 -P 8 -n 1000 rm -f参数拆解-print0让find输出文件名时以 null 字符分隔而不是换行。文件名里如果带空格、换行、中文只有用-print0才能保证传给rm时不被拆分错乱。-0让xargs也按 null 字符解析输入和-print0必须成对出现。-P 8表示同时启动 8 个rm进程并行删除。-n 1000表示每个rm进程一次处理 1000 个文件减少进程启动次数。这套组合在一些测试场景下能比rm -rf快好几倍尤其是目录结构简单、文件都在同一个层级的情况下。并行度也不是越大越好-P 8到-P 16之间是个合理的区间超过 16 个并行进程磁盘 I/O 往往会先扛不住反而拖慢整体速度。3.2 并行删除需要注意的坑第一个坑报错。并行删除时你经常会看到如下错误rm: cannot remove /data/cache/xx: No such file or directory这通常不是真正的错误而是find在扫描目录时已经把文件放进了待处理列表结果另一个并行的rm进程提前把这个文件删了。这个文件在磁盘上已经消失rm再去删自然找不到。遇到这个错误不用慌忽略即可。如果想让输出干净一点可以加2/dev/null重定向掉错误信息。第二个坑并发太高导致 I/O 阻塞。这一点在高并发业务服务器上尤其明显。删除大量文件是纯粹的 I/O 操作会把磁盘带宽打满影响正在运行的业务。我的经验是优先用ionice限制 I/O 优先级再用nice降低 CPU 优先级然后根据磁盘类型动态调整并行度。SSD 可以并行高一点HDD 必须保守否则磁盘寻道时间会把性能拉到谷底。第三个坑删除后空间没释放。删了半天看df -h空间还是没变。这往往不是删除有问题而是有进程还在持有已删除文件的句柄。用lsof查一下lsof L1 | grep deleted找到占用的进程重启它或者 kill 掉即可磁盘空间就会真正释放。4. 从文件系统层面优化治标还得治本4.1 选对文件系统与 mkfs 参数删除效率除了取决于命令更取决于文件系统本身。很多服务器默认用的是 ext4而 ext4 在极端海量小文件场景下的删除效率其实不是最优的。如果你有权限规划分区并且预期会长期存放大规模小文件XFS 往往比 ext4 表现更好。XFS 的目录结构和 B-tree 设计在处理大量文件时都更高效。不过 ext4 也不是不能调优关键在于mkfs时的参数。先说 inode 的问题。inode 是文件系统中每个文件的“身份证”一个文件必须占用一个 inode哪怕这个文件只有 1 字节。在创建 ext4 文件系统时有个参数叫bytes-per-inode默认值是 16384意思是每 16384 字节16KB的数据空间分配一个 inode。如果你的文件都是 4KB 的小文件默认配置下的 inode 数量可能不够用。典型表现就是df -h显示磁盘还有几 GB 空间但df -i显示 inode 已经 100%此时系统会提示“No space left on device”但实际磁盘还有空间。如果你提前知道自己要存海量小文件创建文件系统时可以这样调mkfs.ext4 -i 8192 /dev/sdb1-i 8192表示每 8KB 分配一个 inodeinode 数量翻倍。极端场景还可以用-i 4096但要注意inode 数量越多本身也要占用磁盘空间而且会让文件的查找效率变慢。权衡点是inode 数量够用即可不要盲目调低字节数。对于已有的文件系统可以通过dumpe2fs查看当前的 inode 信息dumpe2fs /dev/sdb1 | grep -i inode如果 inode 已经分配完了底层没法扩大 inode 数量只能迁移数据后重建文件系统。4.2 页缓存、目录项缓存与删除速度的关系删除大量小文件时系统会频繁读取和更新目录项、inode 元数据。这些元数据会大量占用页缓存page cache。第一次删除时缓存是冷的所有元数据都要从磁盘读取速度自然最慢删除完一批后缓存放热后续的删除会明显加快。这也是为什么有时候你会感觉到“越删越快”。如果你在删除前想要尽量把缓存里的数据刷回磁盘可以执行syncsyncsync会把内存中的脏缓存数据写回磁盘。删除大量文件后执行一次sync是很有必要的不然突然断电可能导致文件系统元数据损坏。还有一些人会用到/proc/sys/vm/drop_caches来清缓存。这里我必须提醒一句drop_caches 只是清空页缓存不会删除你的业务文件它的作用是释放缓存占用的内存。删除小文件时如果系统内存很紧张适当 drop 一次缓存可以腾出内存给新的元数据操作但不要频繁用否则反而会拖慢性能。sync echo 3 /proc/sys/vm/drop_caches提示drop_caches 在生产环境一定要谨慎使用特别是数据库服务器清空缓存后冷读会使性能出现明显波动。非必要不推荐。4.3 减少小文件产生的架构思路都说“删得慢”不如“别产生那么多让你删的文件”。如果你的系统本来就持续产出海量小文件优化删除命令只是亡羊补牢。我见过不少监控系统、日志系统每天生成几十万个小文件这种设计本身就有问题。更好的做法是把多个小文件合并成一个大文件。比如日志系统改用按小时快照追加写入监控指标批量写入时序数据库而不是落地成一个个小 JSON。换个角度看这不是运维的优化而是架构上的重构。哪怕只改一层把“临时目录”这个存储模式改成“序列化大文件 定时清理”后续的维护成本都会大幅度下降。5. 常见问题与排查技巧实录我把这几年被问得最多的问题整理成了一张速查表。你在实际操作中遇到这些情况直接照着排查即可。问题现象根本原因解决方案rm -rf跑了一小时没结束小文件过多单线程 unlink 遇上元数据瓶颈用 rsync 空目录法或 find xargs 并行删删除后空间没有释放有进程持有已删除文件的句柄lsof L1 | grep deleted找到并重启进程df -h有空间但报“No space left”inode 耗尽df -i查看确认删除无用文件或重建文件系统时调小bytes-per-inodefind 删除时报大量 “No such file or directory”文件在扫描后被其他删除进程抢先删掉正常现象忽略即可或重定向错误输出删除时业务响应变慢删除进程占满磁盘 I/O用ionice -c3和nice -n 19降低优先级降低-P并行度rsync 把目标目录整个删了路径末尾斜杠写错操作前用绝对路径先--dry-run演练一遍删除速度越来越慢文件系统元数据碎片化目录项缓存失效执行sync用 XFS 文件系统重新格式化分区并规划 inode这里额外多说一句rsync 命令在执行前一定要用--dry-run演练一次rsync -a --delete --dry-run /tmp/empty/ /data/cache/它会先列出将要执行的删除清单确认无误后再把--dry-run去掉正式执行。这一步能帮你避免因为路径拼写错误导致的全盘误删事故。再补充一个判断删除进度的技巧。rm删除时不显示任何进度信息你不知道它到底卡住了还是在慢慢删。可以用strace看系统调用strace -p $(pgrep -x rm) -e traceunlink或者更简单一点开另一个终端持续观察目录里的文件数量变化watch -n 5 find /data/cache -type f | wc -l文件数量在下降说明进程还在正常干活如果长时间不变可能是卡在 I/O 上或者被某个不可中断的内核态操作阻塞了。关于个人实践的一些体会从我自己的操作经验来看在海量小文件面前rm绝对不是一个称职的删除工具。处理这类问题的优先级应该是先停业务写入再重命名目录隔离然后用 rsync 或并行 find 做后台清理最后才考虑从文件系统层面重新规划。顺序不能反一旦反过来你会在服务不可用和删除速度慢之间两头受罪。另一个体会是不管用什么方案删除前做一次目录快照或者至少确认 enemy 环境备份、镜像、克隆永远比“删得快”更重要。数据删错了就是删错了没有后悔药。这套方法我用了很多年不能说每次都尽善尽美但至少能让你在凌晨三点处理告警时不用对着几百万个小文件发愁。
返回列表