尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Oans:面向btrfs与XFS的快速去重工具,从原理到实战验证

Oans:面向btrfs与XFS的快速去重工具,从原理到实战验证 如果你维护过 Linux 服务器大概率遇到过这样的情况磁盘分区明明很大却被一堆重复文件慢慢吃满。比如同一份虚拟机镜像被复制了多份备份脚本在不同目录里留下了相同的文件容器镜像、日志归档、开发环境的复制目录都在悄悄占用双份甚至多份空间。靠人工清理不仅效率低还容易误删直接删文件又担心影响业务。面对这种场景文件系统层的去重Deduplication是一种比“找文件 删除”更优雅的方案而 btrfs 和 XFS 作为 Linux 环境下两种常见文件系统正好提供了实现去重所需要的底层能力。本文将围绕 Oans 这个在 Hacker News 上展示的快速去重工具展开讲解它面向 btrfs 和 XFS 的定位、去重的基本原理、环境准备、验证流程和常见问题。由于 Oans 项目的公开资料目前还不像 duperemove、rmlint 那样丰富这篇文章会更多从“去重工具共性的实现思路 btrfs/XFS 文件系统特性”的角度来帮你建立一套完整的认知。读完之后你不仅能用测试环境验证去重效果也能在拿到 Oans 源码或发布包之后快速理解它做了什么、为什么这样做。1. 背景为什么要做文件系统级去重1.1 重复数据从哪里来重复数据并不是个别现象。在开发和运维场景中重复文件通常来自几个固定途径虚拟机磁盘镜像、容器镜像层经常被整体复制产生完全相同的块数据。备份脚本为了保留历史版本会把相同内容的文件复制到不同日期目录。数据迁移、测试环境复制的项目目录往往包含大量未修改的静态资源。日志轮转、数据导出任务会生成同内容但不同时间戳的副本。这些重复数据在普通文件系统上表现为多个逻辑文件占用了多份物理空间。如果要节省空间可以压缩但压缩对已经压缩过的镜像、视频、数据库文件收益有限也可以做去重让多个文件共享同一份物理数据。1.2 去重的不同层次去重可以发生在不同层级理解它们之间的差异才能明白 Oans 这类工具的价值。去重层次工作方式典型工具/机制效果应用层去重业务代码判断相同内容只保存一份对象存储、备份软件对应用透明但只能覆盖特定业务文件级去重对整个文件计算哈希相同文件仅保留一份fdupes、rdfind、jdupes实现简单但无法处理“大部分相同”的文件块级去重将文件按固定大小或变长分块识别重复块duperemove、rmlint、Oans空间收益更高适合镜像、数据库备份文件系统内联去重写入时自动检测重复部分存储系统/文件系统特性实时性高但实现复杂开销较大btrfs 和 XFS 本身并不会在写入时对所有文件做全局去重。它们提供的是“去重能力”的底层机制例如 reflink 和 FIDEDUPERANGE 接口但真正完整的去重流程需要用户态工具来扫描、计算哈希、识别重复数据并调用内核接口完成合并。Oans 做的就是这个工作。1.3 为什么 btrfs 和 XFS 需要专门工具Linux 上的通用文件系统很多但支持高效去重的并不算多。btrfs 从设计之初就强调子卷、快照、校验和等特性reflink 更是它的核心能力XFS 在较新的内核中也加入了 reflink 和 dedupe 支持。两者都通过 VFS 层的 FIDEDUPERANGE ioctl 向外提供去重接口允许用户态工具把一组文件范围合并到同一个物理 extent 上。既然内核有接口为什么还需要 Oans 这样的工具因为 FIDEDUPERANGE 只负责“执行合并”它不知道哪些文件内容相同。要发现重复数据需要扫描目录、读取文件内容、计算哈希、按哈希分组、再逐块比对。这个过程涉及大量磁盘 IO 和 CPU 计算如果用低效方式实现扫描速度会很慢。Oans 的定位就是“fast deduplication”也就是在扫描和去重执行上尽量做得更快从而更适合真实环境中的大量数据。2. Oans 是什么面向 btrfs 与 XFS 的快速去重工具2.1 项目定位Oans 是在 Hacker News 的 Show HN 板块出现的一个开源项目从标题可以看出它的核心定位为 btrfs 和 XFS 提供快速去重能力。简单的说它就是一个文件系统层的重复数据删除工具帮助管理员在数据不丢失的情况下把重复的物理块合并释放磁盘空间。Oans 这个名字并不算常见目前公开资料也不多所以本文不会强行编造它的命令行参数和配置项。不过去重工具的基本流程是相通的扫描文件、计算哈希、查找重复、调用内核接口去重。理解这套流程后无论 Oans 的最终实现如何变化你都能快速上手。2.2 与其他去重工具的对比在 btrfs 和 XFS 上已经有一些成熟工具Oans 并不是第一个玩家。常见的去重工具有duperemove专注于 btrfs 和 XFS 的块级去重支持自动去重和只扫描两种模式。rmlint功能更丰富除了去重还能查找空文件、损坏文件、重复目录等。jdupes/fdupes文件级去重工具适合处理完全相同的文件。bees一个面向 btrfs 的连续运行型去重工具会作为守护进程持续扫描。这些工具各有侧重。Oans 如果希望脱颖而出大概率会在扫描速度、内存占用、增量扫描、以及对 btrfs/XFS 底层特性的利用上做优化。比如用更快的哈希算法、并行处理多个文件、利用文件系统元数据跳过未变化的数据块等。这些都属于工程实现层面的优化也是“快速去重”这个定位的核心。2.3 对 Oans 的合理预期在公开资料有限的情况下你可以把 Oans 当作一个“专注于 btrfs 和 XFS 的去重工具”来评估。它大概率会提供类似下面的工作流扫描指定目录或文件系统收集文件列表。按文件大小初筛跳过大小不一致的文件。对可能重复的文件计算哈希或块级指纹。对哈希相同的范围进一步做数据比对。调用 FIDEDUPERANGE 将重复 extent 合并。如果你下载到 Oans 的源码或二进制可以优先查看它的 README、命令行帮助和代码目录结构。通常一个去重工具的核心逻辑集中在扫描器、哈希器、去重执行器三个模块理解了这三个部分基本就理解了整个工具。3. 环境准备与版本说明3.1 基础环境本文的验证流程以 Linux 环境为例。建议准备一台可以获取 root 权限的测试服务器或虚拟机因为挂载文件系统、执行 mkfs、调用去重 ioctl 都需要较高的权限。这里的环境只是为了演示不建议直接在重要生产分区上运行实验命令。操作系统任意主流 Linux 发行版例如 Ubuntu、Debian、Rocky Linux、openSUSE。文件系统btrfs 或 XFS推荐先在一个空闲分区或 loop 设备上测试。内核需要支持 reflink 和 FIDEDUPERANGE现代主流发行版通常已经满足。编译工具如果从源码编译 Oans需要 gcc、make 等基础工具不同项目可能还有额外依赖。验证工具btrfs-progs、xfsprogs、filefrag、duperemove 等用于准备数据与查看结果。3.2 内核与文件系统支持检查在开始之前可以先确认当前内核和文件系统是否支持去重。使用 uname 查看内核版本uname -r然后查看挂载的文件系统类型findmnt -T /mnt/btrfs-test如果文件系统是 btrfs 或 XFS并且内核支持 FIDEDUPERANGE那么用户态工具就可以调用去重接口。不同发行版的内核特性开启情况不同最好在测试环境里用一个小文件验证避免在正式环境才发现不支持。3.3 创建 btrfs 测试文件系统为了避免影响现有数据可以使用一个文件作为 loop 设备来创建 btrfs 文件系统。下面的命令会创建 2GB 的测试镜像文件并挂载到 /mnt/btrfs-test# 需要 root 权限请确保 /tmp 下有足够空间 truncate -s 2G /tmp/btrfs-test.img mkfs.btrfs -f /tmp/btrfs-test.img mkdir -p /mnt/btrfs-test mount -o loop /tmp/btrfs-test.img /mnt/btrfs-test如果使用 XFS可以将 mkfs 命令替换为truncate -s 2G /tmp/xfs-test.img mkfs.xfs -f /tmp/xfs-test.img mkdir -p /mnt/xfs-test mount -o loop /tmp/xfs-test.img /mnt/xfs-test注意mkfs 会格式化设备或镜像文件操作前一定要确认路径正确。在测试环境里用 loop 设备是比较安全的做法因为即使操作失误也不会影响物理磁盘上的真实数据。4. btrfs/XFS 去重的核心原理4.1 extent 与 reflink要理解去重先要理解 extent。一个文件在文件系统中并不是简单地按“文件内容”存放而是由多个 extent 组成的。每个 extent 描述了一段逻辑文件数据映射到物理存储上的哪一段。普通情况下两个内容相同的文件各自拥有独立的 extent即使数据完全一样物理空间也占用两份。reflink 是一个关键机制它允许用户创建一个新的文件或文件范围但初始时并不复制数据而是指向同一组物理 extent。只要任何一方不修改数据两者就会共享物理块当某一方写入修改时文件系统再根据需要复制出独立的块这就是写时复制CoW的思路。btrfs 和 XFS 都支持 reflink所以在它们上面做去重可以避免真正的数据拷贝只需要修改元数据映射开销远小于“复制一份再删除原文件”。4.2 FIDEDUPERANGE 接口内核为文件系统去重提供了一个通用接口FIDEDUPERANGE ioctl。用户态程序可以传入源文件范围、目标文件范围和目标文件描述符内核会检查这一段范围内是否存在完全相同的物理数据。如果相同就把目标范围重新映射到与源范围相同的 extent 上从而实现去重。这个接口是整个去重流程的“最后一公里”。它本身不负责发现重复数据只负责执行去重动作。工具需要先确定哪些文件范围可能相同然后调用它完成合并。在 btrfs 和 XFS 上这个接口都被支持所以同一个工具可以同时覆盖两种文件系统。4.3 去重工作流一个典型的去重工具工作流如下遍历目标目录收集所有普通文件。记录文件的大小、路径、inode 等信息。根据文件大小初筛只有大小相同的文件或范围才可能重复。对候选文件读取数据计算哈希或块级指纹。如果哈希相同不能立刻确定数据一致还需要做字节级比对因为哈希碰撞理论上存在。确认重复后调用 FIDEDUPERANGE 执行去重。记录执行结果更新文件系统空间统计。Oans 作为“快速去重”工具主要优化空间就在第 4 步到第 6 步之间怎么减少读取的数据量、怎么让哈希计算更快、怎么利用文件系统接口跳过已经被共享的 extent、怎么并行处理多个文件。4.4 为什么“快速”是难点去重的成本主要在扫描阶段。假设磁盘有 10TB 数据工具要把这些数据全部读一遍计算哈希即使不执行去重也要消耗大量时间和 IO 带宽。如果实现得很笨重比如每次读 4KB 就调用一次文件系统接口速度会非常慢。快速去重工具的常见优化方向包括使用更快且安全的哈希算法例如 xxHash、BLAKE3而不是较慢的 SHA-256。只对大小相同的文件或文件块做哈希减少无意义的计算。利用文件系统元数据例如 btrfs 的 extent 信息跳过已经共享的范围。多线程并行扫描不同目录或不同文件充分利用 CPU 和磁盘队列。内存映射文件避免反复调用 read 系统调用。这些优化方向并不是 Oans 特有而是去重工具工程化的通用经验。Oans 标题中的 “fast” 说明项目作者大概率在这些方面下了功夫。4.5 常见误区很多新手会把“哈希相同”等同于“数据完全相同”。实际上哈希碰撞虽然概率低但理论上存在。更重要的是文件系统在去重时需要确保比较范围内的字节完全一致所以工具不应该只凭哈希就执行合并。另一个误区是认为去重会压缩数据。去重不会改变文件内容也不会让数据变小它只是让多个逻辑范围共享物理块。文件系统上看到的文件总大小可能不变但磁盘占用会下降。还有一个容易忽略的点去重之后文件仍然可以通过原来的路径正常访问。一个文件的数据可能被合并到另一个文件的 extent 上但只要文件系统元数据正确读写对用户是透明的。这也是去重比手工删除副本安全得多的原因之一。5. 实战在 btrfs 与 XFS 上验证去重5.1 准备重复数据在创建好的 btrfs 测试分区中准备一个随机数据文件然后复制成两个副本。为了确保副本不自动使用 reflink复制时要显式禁用 reflinkcd /mnt/btrfs-test dd if/dev/urandom ofrandom.bin bs1M count256 cp --reflinknever random.bin copy1.bin cp --reflinknever random.bin copy2.bin sync如果使用默认的 cp 命令在 btrfs 上可能会自动采用 reflink副本会立刻共享 extent这样就看不到去重的效果了。使用--reflinknever可以强制复制完整数据模拟真实场景中占用多份空间的重复文件。查看当前文件的磁盘占用情况btrfs filesystem du /mnt/btrfs-test/*在去重之前random.bin、copy1.bin、copy2.bin 各自占用 256MB 空间磁盘占用合计约 768MB。5.2 执行去重这里先用 duperemove 演示完整的去重执行流程因为它是 btrfs 和 XFS 上比较成熟的去重工具可以验证整个环境是否正常。如果你已经拿到 Oans 的二进制或源码可以把下面的命令替换为 Oans 对应的命令验证思路不变。# 安装 duperemove不同发行版包名可能不同 # apt install duperemove 或 dnf install duperemove duperemove -r -d /mnt/btrfs-test命令中-r表示递归扫描目录-d表示执行去重如果不希望立即去重可以只扫描并输出重复数据确认无误后再执行。如果你的数据量很大建议先做一次 dry-run 扫描。5.3 验证去重结果去重完成后再次执行btrfs filesystem du /mnt/btrfs-test/*这次可以看到三个文件在逻辑上都还是 256MB但磁盘占用可能已经大幅下降random.bin 的 exclusive 数据仍然是 256MBcopy1.bin 和 copy2.bin 的 exclusive 数据会变成 0 或接近 0因为它们已经和 random.bin 共享物理 extent。整个文件系统的实际占用会减少约 512MB。如果使用 XFS可以执行xfs_io -r -c fiemap /mnt/xfs-test/random.bin通过 fiemap 查看文件物理映射也能观察到去重后的 extent 共享情况。核心思路是去重前三个文件有各自独立的物理块映射去重后多个文件的文件范围映射到同一个物理块。5.4 内核接口调用示例对于想理解 Oans 底层实现的开发者了解 FIDEDUPERANGE 的调用方式是很有帮助的。下面是一个 C 语言核心函数片段展示了如何将两个文件描述符对应的范围提交给内核进行去重// 文件路径dedupe_demo.c核心函数片段 #include linux/fs.h #include sys/ioctl.h #include fcntl.h #include stdlib.h #include stdio.h /* * 将 src_fd 中 [offset, offsetlen) 范围的数据与 dst_fd 中相同偏移范围合并。 * 仅当内核确认两个范围数据完全一致时才会实际执行去重。 */ int dedupe_range(int src_fd, int dst_fd, off_t offset, size_t len) { struct file_dedupe_range *range; struct file_dedupe_range_info *info; size_t size sizeof(struct file_dedupe_range) sizeof(struct file_dedupe_range_info); int ret; range calloc(1, size); if (!range) { return -1; } range-src_offset offset; range-src_length len; range-dest_count 1; info range-info[0]; info-dest_fd dst_fd; info-dest_offset offset; ret ioctl(src_fd, FIDEDUPERANGE, range); if (ret 0) { printf(dedupe status: %d\n, info-status); } free(range); return ret; }这段代码只是核心调用片段不能单独编译运行实际使用时需要包含必要的头文件、打开文件并处理错误状态。重点在于理解FIDEDUPERANGE 是内核提供的原子去重接口用户态工具只是“发现重复数据 提交合并请求”的角色。5.5 如何把通用流程替换为 Oans如果你拿到了 Oans建议先看它的帮助信息./oans --help如果发布包提供的是动态链接版本可能还需要设置 LD_LIBRARY_PATH。Oans 大概率会提供类似“扫描模式”和“去重模式”的选项例如先扫描生成报告再执行去重。你可以在测试环境里先对一个小目录运行扫描查看输出格式确认它能识别重复数据后再对整个分区执行去重。由于 Oans 项目信息较少写死某个具体参数并不是负责任的做法。更稳妥的方式是通过项目 README、博客文章或源码中的选项解析代码了解它支持的参数。去重工具的核心流程是通用的一旦跑通了最小示例后续的上手过程会非常快。6. 从工程角度看 Oans 可能做了哪些优化6.1 哈希算法选择去重工具的性能瓶颈通常是哈希计算和磁盘 IO。在早期工具中SHA-1、MD5 是常见选择但它们的计算速度在现代 CPU 上并不算快。对于大数据量去重使用 xxHash、BLAKE2 或 BLAKE3 这类高速哈希能显著降低 CPU 开销。不过不是哈希越快越好。如果哈希算法强度太低碰撞概率会增加工具就需要做更多数据比对来避免误合并。Oans 既然强调“fast”很可能在哈希算法的选择上做了权衡比如使用一种快速哈希做初筛再使用更强校验或字节级比对做最终确认。6.2 并行扫描与 IO 调度单线程遍历目录和读取文件在大分区上会很慢。并行扫描是提升速度最直接的方式。但并行并不是“无脑开线程”因为磁盘 IO 带宽是有限的如果同时读取太多文件反而可能导致 IO 队列拥塞增加延迟。一个合理的做法是控制并发度例如根据磁盘类型调整并发线程数。SSD 可以承受更高的并发读HDD 则需要控制顺序读和随机读的比例。Oans 如果实现了自适应并发就能在不同存储介质上都有较好表现。6.3 元数据感知与增量去重全量扫描每次都比较耗时。更高效的做法是记录上一次扫描的状态跳过没有变化的文件。这需要工具保存文件大小、mtime、inode、ctime 等元数据在下一次扫描时快速判断是否值得重新哈希。另外btrfs 本身提供子卷、快照等特性工具可以识别已经被快照共享的 extent避免重复扫描已经共享的数据。Oans 如果支持这些优化就能在“二次去重”场景下节省大量时间。6.4 在 btrfs 与 XFS 上的差异处理虽然 btrfs 和 XFS 都支持 FIDEDUPERANGE但两者在行为上仍有差异。btrfs 是 CoW 文件系统数据块支持校验和去重后如果某个文件写入新数据文件系统需要分配新的 extentXFS 的 reflink 机制相对更接近传统“共享物理块”模型对已有文件系统的特性支持也与 btrfs 不同。一个优秀的去重工具应该识别当前文件系统类型并根据特性选择不同的数据块查询方式。例如在 btrfs 上可以使用BTRFS_IOC_TREE_SEARCH查看 extent 信息在 XFS 上则可以借助 fiemap 来分析物理映射。Oans 如果针对两种文件系统做了适配就能做到比通用工具更“快”和更“安全”。7. 常见问题与排查思路7.1 常见问题表格问题现象常见原因解决思路去重后空间没有减少文件系统不支持 reflink 或 dedupe检查内核版本和挂载选项确认 btrfs/XFS 支持 FIDEDUPERANGE扫描很慢文件数据量大哈希算法开销高使用并行扫描、增量扫描或只扫描指定目录去重工具报权限错误当前用户没有足够权限使用 root 或具备 CAP_SYS_ADMIN 的用户执行文件被持续修改数据库、日志等活跃文件在去重过程中发生变化将活跃目录排除在扫描范围外去重后出现数据不一致工具 bug 或硬件问题立即停止去重检查校验和从备份恢复无法挂载测试镜像loop 设备或内核模块问题检查 /dev/loop 节点加载 btrfs/xfs 内核模块7.2 去重后空间没有减少怎么办这种情况最常见的原因是文件实际上已经被 reflink 共享了。比如使用 cp 默认参数复制文件btrfs 会自动创建 reflink这时从文件系统角度看副本和源文件已经共享 extent再去重不会释放空间。可以通过filefrag -v查看文件的物理 extent 编号判断多个文件是否已经共享物理块。另外某些文件大小不一致即使内容相同块级去重工具也可能不会把它们识别为重复。如果期望的是文件级去重可以使用 jdupes 等工具先做一次清理。7.3 去重过程中的数据安全风险去重操作会修改文件系统的元数据映射所以存在一定风险。虽然 FIDEDUPERANGE 接口设计为只有确认数据相同才会执行合并但在极端情况下如果文件系统 bug、内核 bug 或硬件损坏导致错误合并仍然可能造成数据读取异常。因此在生产环境执行去重前必须确认备份可用并尽量在维护窗口执行。在执行去重时建议关闭正在写入大量数据的服务或者至少将活跃目录排除在外。对于数据库文件、虚拟机的在线磁盘镜像最好不要直接在线去重而是等业务低峰期或停机维护时再处理。8. 最佳实践与生产建议8.1 先扫描再执行大多数去重工具都支持只扫描模式。第一次使用时建议先扫描输出重复数据报告确认工具识别到的重复文件确实是预期中的内容再执行去重。这一步看起来多花时间实际上能避免很多误操作。在测试环境里可以准备少量重复文件查看工具输出的哈希、文件路径和重复块数量熟悉工具的输出格式。之后再扩大到真实数据范围。8.2 排除活跃文件与特殊目录生产环境中并不是所有文件都适合去重。数据库的数据文件、WAL 日志、正在写入的临时文件、监控系统实时写入的日志在去重过程中如果被读取和比较可能产生额外 IO甚至导致性能抖动。更稳妥的做法是在工具配置中排除这些目录。常见的需要排除的项目包括MySQL/PostgreSQL 数据目录。Docker/containerd 运行中的容器层目录。Elasticsearch、Kafka 等频繁写入的数据目录。所有服务正在持续写入的目录。如果无法排除至少要在备份工具能及时恢复的前提下选择业务低峰期执行。8.3 备份与回滚去重不是删除文件但仍然会改变文件系统的物理布局。在执行大规模去重前最好做一次可用的备份。如果文件系统是 btrfs可以创建只读快照如果是 XFS则依赖外部备份工具。执行完去重后不要立刻删除备份而是先观察一段时间确认文件访问正常、应用运行稳定后再清理备份。尤其要留意那些被去重合并过的文件是否还能被数据库、虚拟机管理程序等正确读取。8.4 控制扫描和去重开销去重工具在扫描阶段会读取大量数据可能影响正在运行的业务。可以采取以下措施使用 nice 或 ionice 降低进程优先级。限制并发线程数量。分批扫描例如每天只处理一部分目录。在监控系统中观察磁盘 IO 使用率如果超过阈值就暂停或降低速率。如果 Oans 支持增量模式可以通过定时任务在夜间运行避免频繁全量扫描。8.5 关注文件系统特性变化btrfs 和 XFS 的 reflink、dedupe 支持与内核版本强相关。内核升级后去重工具的行为可能发生变化新增的内核特性也可能带来更好的性能和稳定性。建议在每次内核升级后先在测试环境跑一遍去重验证流程再决定是否在生产环境执行。此外btrfs 的校验和特性会让去重后的数据仍然具备校验保护这是 btrfs 相比部分文件系统的一个优势。如果你对数据完整性要求较高可以考虑优先在 btrfs 上做去重并定期运行btrfs scrub检查数据一致性。9. 总结与下一步学习路线这篇文章从重复数据产生的场景讲起介绍了 Oans 在 btrfs 和 XFS 去重领域中的定位也梳理了 extent、reflink、FIDEDUPERANGE 这些底层概念。在实战部分通过创建一个测试用的 btrfs 文件系统完成了准备重复数据、执行去重、验证空间释放的完整流程。这些步骤不仅适用于 Oans也适用于 duperemove 等其他去重工具。接下来你可以重点关注三件事一是去 Oans 的项目主页或源码仓库阅读 README了解它的具体用法和设计思路二是搭建一个包含 btrfs 和 XFS 的测试环境亲手跑一遍去重验证让自己对空间变化有直观感受三是如果 Oans 还没有成熟到生产可用可以先掌握 duperemove 等成熟工具的使用再去对比 Oans 的优化点在哪里。去重是一项非常实用的运维和存储优化技术但它不是银弹。对于已经启用压缩的文件额外去重收益可能有限对于频繁写入的数据库盲目去重可能带来额外风险。正确的方式是理解底层原理在测试环境中充分验证再逐步推广到生产环境。希望这篇文章能帮你把去重的思路整理清楚也让你在接触 Oans 这样的新工具时能够更快地上手和判断它是否适合你的场景。
返回列表