ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Oans:btrfs与XFS文件系统块级去重工具实战

Oans:btrfs与XFS文件系统块级去重工具实战 Oans 是一个面向 btrfs 和 XFS 文件系统的快速去重工具。去重这个词听起来很技术实际场景却很直接你的磁盘上放着大量内容相同的文件块比如虚拟机镜像的多个副本、容器镜像层、备份目录里反复出现的同名文件、编译产物、日志归档。它们明明是一模一样的数据却各自占了一块物理空间。Oans 这类工具要做的就是把重复的物理块合并掉让多个逻辑文件共享同一份真实数据。文件还在目录结构没变磁盘可用空间却会明显回升。如果你正好在用 btrfs 或 XFS并且一直在为磁盘膨胀发愁这篇内容会按实际落地顺序把前提检查、安装编译、单文件测试、批量去重、报错排查和适用边界完整过一遍。我对这类工具的判断标准很简单能不能在普通环境里稳定跑通比功能列表上多几个参数更重要。1. 先搞清楚 Oans 到底在做什么不是删文件是合并物理块1.1 文件级去重和块级去重的区别很多人听到“去重”第一反应是找重复文件然后删掉。文件级去重工具比如 fdupes、rmlint 的某些模式确实是查找完全相同或部分相似的文件然后通过删除、硬链接或符号链接来节省空间。这种思路在重复文件多、文件体积大的场景下确实有效但它有一个明显局限只在整个文件完全相同时才能节省空间。如果两个文件 90% 内容相同只有开头或结尾有一点差异文件级去重通常就无能为力了。Oans 从名称和定位来看走的是另一条路块级去重。它扫描的不是整个文件而是把文件拆成逻辑上的块找到内容相同的块然后让这些块共享同一个物理位置。在 btrfs 上这种操作通常依赖 reflink 或者 FIDEDUPERANGE 这类 ioctl在 XFS 上则是利用内核的 reflink 能力。整个过程不会修改文件大小不会修改 inode 里记录的逻辑地址。你用 stat 看文件size 还是原来的值但用 du 或者文件系统内部分配信息看物理占用明显下降。这也是为什么块级去重比文件级去重更适合虚拟机镜像、容器层、数据库备份这种场景。镜像文件内部可能出现大量重复的 block或者多个镜像中间层共享同一段基础数据。如果只看文件是否相同你很难发现这些零散的重复但块级去重可以一张一张地把物理块映射合并掉。1.2 它和 duperemove、bees 的定位差异btrfs 和 XFS 上去重工具并不是一个空白市场。老牌工具 duperemove 已经存在很多年它支持对 btrfs 和 XFS 做基于 hash 的重复块检测。bees 则走的是持续后台扫描路线常驻进程当文件系统空闲时自动去重。Oans 是这个列表里的新面孔而且是以 Show HN 形式出现的说明作者更倾向于展示一个可运行、能实际使用的工程工具而不是概念原型。我对这类工具的判断是Oans 很可能更适合“手动批量执行”的场景。你想整理硬盘时跑一次指定目录看到空间立刻释放然后结束。不想要一个常驻进程去干涉系统。这跟 bees 的哲学不同也和 duperemove 的定位有重叠但不完全一样。具体支持哪些参数、是否支持增量扫描、是否包含白名单过滤需要以项目的 README 和--help输出为准。不要因为它叫“快”就默认它一定比 duperemove 快。在真实业务环境里文件系统状态、数据分布、扫描并发都会影响最终耗时。1.3 什么时候用它收益最明显备份目录里存放了大量相同或部分相同的文件副本。虚拟机镜像有多个快照快照之间大量数据块相同。容器镜像层、软件包缓存、编译产物反复出现相似数据。日志和数据库转储中存在大量重复内容块。反过来说如果磁盘上的数据基本都是随机数据、压缩包、加密文件重复块比例很低去重能释放的空间非常有限。尤其加密文件和已压缩文件内部数据几乎不可能出现大范围重复块扫描成本完全可以省下来。实用建议是先用一个小目录测试看看按当前数据量能省多少空间再决定是否全盘跑。2. 运行前提文件系统不支持工具再快也没用2.1 内核和文件系统的功能开关去重工具的核心不是它自己有多聪明而是它依赖内核提供的 reflink / dedupe 能力。如果文件系统没有开启相关特性工具扫描完之后什么都做不了。btrfs 对 reflink 的支持比较早但也要确认内核版本不是太老。XFS 的 reflink 是后来加入的特性而且文件系统在格式化时可能已经决定了是否支持 reflink。我建议在运行 Oans 之前先做三个确认# 1. 确认当前文件系统类型 df -T /path/to/your/data # 2. 查看挂载选项里有没有 reflink 或 noreflink mount | grep /path/to/your/data # 3. 查看内核版本 uname -r如果文件系统类型是 btrfs 或 xfs最好再确认 reflink 在实际挂载中可用。XFS 在重新格式化时可以使用-m reflink1来显式开启但要注意格式化会清空数据只能在测试盘或数据备份之后执行。不要为了验证 Oans 而对生产盘做冒险操作。2.2 权限、挂载状态和测试范围另一个容易被忽略的前置条件是权限。去重要修改文件的物理块映射涉及元数据操作不是随便什么用户都能做。最稳妥的方式是用文件 owner 执行或者用 root 执行。如果你把命令放在普通用户下跑目录可读但不可写工具可能一路报权限错误或者干脆什么都不做。我建议永远先准备一个专用于测试的挂载点。比如用一个空闲分区格式化 btrfs挂载到/mnt/oans-test然后在里面做实验。这样你不用担心误伤系统分区也不怕递归扫描时跑到/proc、/sys这些特殊挂载点里。# 示例用测试磁盘格式化 btrfs注意会清空该盘数据 sudo mkfs.btrfs -f /dev/sdX sudo mount -o reflink /dev/sdX /mnt/oans-test如果你的实际数据在 XFS 上可以用xfs_info查看文件系统特性或者用xfs_io试探 reflink 是否可用。这里不需要把每一个命令背下来关键是明白先确认文件系统能力再跑去重工具。2.3 数据安全建议去重操作本身不会改变文件内容但它确实修改了文件系统元数据把多个文件的物理块映射合并到一起。这种操作逻辑上安全但一旦遇到磁盘损坏、断电或者工具自身 bug问题可能被放大。真实实践中我一般会在测试盘上跑通全部流程再决定是否用到真实数据。更稳妥的做法是在真实分区上跑之前已经有备份或者快照。btrfs 本身支持快照先给目录做一个快照再跑去重遇到异常可以从快照恢复。XFS 没有内置快照需要依赖 LVM 快照或者文件级备份。不要因为觉得去重是只读扫描就忽略备份。3. 编译与安装先看 README再构建3.1 获取源码和了解构建方式Oans 的具体源码地址、语言和构建方式我没有办法在文章里给出精确版本因为项目可能会更新。但这类工具的安装流程高度一致先用 git clone 把仓库拿到本地然后打开 README 看依赖说明再执行构建。不要凭经验直接 make因为有的项目是 C 写的有的是 Rust 写的构建命令完全不同。通用的获取流程类似这样git clone 项目仓库地址 cd 源码目录 # 查看 README 中的依赖要求和构建步骤 less README新手最容易犯的错误是把仓库克隆下来发现没有 README 或者构建脚本就开始乱猜命令。其实大多数开源项目只要肯花五分钟读 README一半以上的坑都能避开。3.2 常见依赖和构建方式如果 Oans 是 C 项目构建通常依赖 gcc、make、pkg-config以及内核头文件。如果缺少头文件编译会报找不到某个.h文件。这时候首先检查是不是没有安装完整构建工具链而不是找源码问题。如果项目是 Rust 写的那大概率要靠 cargocargo build --release sudo cp target/release/oans /usr/local/bin/不管哪种方式核心思路是先把依赖装齐再把构建跑通最后把二进制放到 PATH 能找到的位置。安装时可以使用/usr/local/bin也可以不安装直接在源码目录执行./oans。我建议先不 install直接从源码目录运行这样后续想看版本、看源码、重新编译都比较方便。3.3 安装后的第一件事看帮助任何工具安装完都先执行一次帮助命令./oans --help这一步能看到工具支持哪些参数比如扫描目录参数、文件大小阈值、并发数、hash 方式、dry-run 模式。不同工具的参数命名差别很大有的用-r表示递归有的直接默认递归有的用-t表示线程数有的用--jobs。以实际输出来为准千万别照着其他工具的教程硬套。如果帮助信息很少也没有给出示例那就去项目的 README 找。实在不行先拿一个最小目录试运行观察输出信息里有没有出现“not supported”之类的话。至少能确认二进制能启动、能扫描目录。4. 第一次去重测试先跑通一个小目录4.1 准备重复数据第一次测试不要搞得太复杂。我建议在一个干净的测试目录里生成一个大文件再复制两份然后用 Oans 去重。如果去重生效你会在磁盘占用上看到立竿见影的变化。mkdir -p /mnt/oans-test/data cd /mnt/oans-test/data # 生成一个 128MB 的文件 dd if/dev/urandom ofa.bin bs1M count128 # 复制两份内容完全相同 cp a.bin b.bin cp a.bin c.bin # 查看当前磁盘占用和文件系统分配信息 du -sh /mnt/oans-test/data btrfs filesystem du /mnt/oans-test/data注意/dev/urandom生成的是随机数据复制之后三份字节完全相同非常适合做去重测试。如果你用文本文件或日志文件做测试因为文件本身可能很小或者内部重复块比例不高反而看不出明显效果。4.2 执行 Oans 并检查结果接下来运行工具。由于不同工具的参数名可能不同下面用通用调用方式展示实际运行时请用--help输出替换参数cd /mnt/oans-test /path/to/oans data跑完之后按照这个顺序验证命令是否正常退出有没有报错。du -sh /mnt/oans-test/data是否明显下降。如果 384MB 左右缩到了 128MB 左右说明机制生效了。btrfs filesystem du /mnt/oans-test/data里的 shared 数值是否增加。shared 表示有多少数据被多个逻辑文件共享。用sha256sum a.bin b.bin c.bin确认内容是否保持一致。去重不应该改变内容。如果第一个小目录都跑不通或者跑完空间没有任何变化不要急着上批量先回头检查文件系统支持、权限和参数设置。这一步排查的成本最低。4.3 为什么先跑单文件/小目录能省很多时间很多朋友拿到新工具第一反应就是直接对全盘或者大目录跑。结果工具跑了一个小时最后发现根因是文件系统不支持 reflink或者某个参数把符合条件的文件都过滤掉了白白浪费大量时间。更麻烦的是扫描范围太大时工具中途被中断你就很难判断哪些文件已经被处理过哪些还没有。先跑小目录的好处是能快速隔离问题如果是权限问题日志里很快会出现 permission denied如果是文件系统不支持立刻会报功能错误如果是参数写错了输出规模足够小很容易对比前后变化。小目录跑通了再慢慢扩展范围成功率会高很多。5. 批量去重从测试目录扩展到真实数据5.1 先找扫描范围控制参数进入真实数据之前先确认工具是否支持这几个关键参数。不同工具不一定都支持但了解它们能让运行时更有把握。参数类型作用为什么重要最小文件大小阈值小于该大小的文件跳过扫描小文件的 hash 和 ioctl 开销可能比省下的空间还贵dry-run / preview只计算并显示可去重块不实际执行先估算收益避免把文件系统突然改变并发控制控制扫描或去重线程数高并发不一定更快可能让元数据锁成为瓶颈路径过滤 / 排除只扫指定目录排除挂载点或临时目录避免把 /proc、/sys 或其它文件系统扫进来hash 方式使用不同哈希算法如 sha256、xxhash哈希越强越慢通常场景下 fast hash 已足够不同工具对这些参数的支持程度不一样Oans 具体支持哪些要以--help和 README 为准。如果项目支持 dry-run那我强烈建议先跑一次预览记录预计节省空间再决定是否执行正式去重。5.2 并发、时间与资源占用的取舍去重任务不是“并发数越大越好”。当你对文件系统执行大量 reflink/dedupe ioctl 时文件系统内部需要锁定相关元数据多个线程同时操作同一批 extent反而可能互相等待。我见过不少场景并发数从 1 提到 8CPU 占用确实上去了但总耗时并没有下降多少甚至不升反降。原因是扫描阶段是 CPU/IO 密集去重阶段则受限于文件系统元数据锁。合理的调参思路是先用默认并发跑一个小目录观察 CPU、内存、磁盘 IO 和耗时如果 CPU 使用率不高磁盘 IO 也没到饱和再逐步提高并发。不要一上来就把参数拉满尤其当磁盘本身是机械硬盘时随机扫描可能让 IO 等待变得非常严重。5.3 批量执行时的任务组织批量去重不是在命令行里写一个路径就完事。真实环境通常要处理三个问题日志记录、失败重试和输出确认。我建议把运行输出重定向到日志文件/path/to/oans /data --something /var/log/oans-run.log 21如果工具中途因为某个文件报错而退出你可以通过日志定位是哪一个路径出了问题。另外扫描范围越大越要留意磁盘剩余空间。去重过程中如果有意外写入或者日志占空间可能导致磁盘写满影响整个任务。还有一个经验不要在快照链上轻率执行批量去重。btrfs 快照本身依赖共享 extent 来节省空间你在快照目录里跑去重可能会把不同快照之间原本共享的块重新合并或改变映射关系导致某些快照的“保留旧版本”语义变得混乱。这个结论不是绝对的但值得谨慎对待。6. 常见报
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进