微信扫码
添加专属顾问
我要投稿
AI训练领域两大存储方案深度解析,助你选择最合适的文件系统。 核心内容: 1. DeepSeek开源3FS文件系统背景及架构 2. 3FS与JuiceFS对比分析 3. 3FS创新技术点及适用场景
近期,DeepSeek 开源了其文件系统 Fire-Flyer File System (3FS),这一举措让文件系统这一已有70多年历史的技术再次成为焦点。在AI领域,企业需要处理大量非结构化数据,如文本、图像和视频,同时面临数据量的急剧增长,分布式文件系统因此成为AI训练中不可或缺的存储技术。
本文旨在通过深入分析3FS的实现机制,并与JuiceFS进行对比,帮助用户理解两者的差异及其适用场景。此外,我们还将探讨3FS中值得借鉴的创新技术点。
3FS[1] (Fire-Flyer File System)是一款专为AI训练和推理任务量身打造的高性能分布式文件系统,它通过整合高速NVMe存储和RDMA网络技术,构建了一个高效的共享存储层。该系统由DeepSeek于2025年2月正式开源,旨在满足AI领域对大规模数据处理的需求。
3FS的核心架构由以下几个关键模块组成:
• 集群管理服务(Cluster Manager):负责系统的整体协调与资源管理。
• 元数据服务(Metadata Service):管理文件和目录的元数据,确保高效的数据访问。
• 存储服务(Storage Service):处理实际数据的存储与读取,优化性能。
• 客户端 (FUSE Client、Native Client):包括FUSE客户端和原生客户端,为用户提供灵活的文件系统访问接口。
这些模块共同协作,使3FS能够高效应对AI工作负载中的复杂存储需求。
3FS 架构
3FS的所有模块均通过RDMA网络进行通信,确保高效的数据传输。元数据服务和存储服务会定期向集群管理服务发送心跳信号,以维持系统的健康状态。集群管理服务负责处理集群成员变更,并将最新的集群配置分发给其他服务和客户端。为了提高系统的可靠性和避免单点故障,3FS部署了多个集群管理服务,其中一个作为主节点运行。如果主节点发生故障,系统会自动选举另一个管理器接替主节点角色。集群配置信息通常存储在可靠的分布式服务中,如ZooKeeper或etcd,以确保配置数据的高可用性。
在进行文件元数据操作(如创建或打开文件/目录)时,请求会被发送到元数据服务,由它负责实现文件系统的语义。元数据服务是无状态的,多个元数据服务实例可以同时运行,它们并不直接存储文件元数据,而是依赖支持事务的键值数据库FoundationDB来管理这些数据。这种设计使得客户端可以灵活地连接到任意元数据服务,从而提升了系统的可扩展性和可靠性。
存储服务负责管理本地SSD,并提供chunk存储接口。为了确保数据的强一致性,存储服务采用了CRAQ(Chain Replication with Apportioned Queries)技术。在3FS中,文件被分割为默认大小为512K的块,并在多个SSD上进行复制,以提高数据的可靠性和访问性能。
3FS客户端提供了两种接入方式:FUSE Client和Native Client。FUSE Client支持常见的POSIX接口,易于使用且兼容性高;而Native Client则提供了更高的性能,但需要用户调用客户端API,具有一定的侵入性。后续我们将对这两种客户端进行更详细的分析。
JuiceFS 是一款云原生分布式文件系统,其数据存储依赖于对象存储服务。社区版[2]于2021年在GitHub开源,支持与多种元数据引擎集成,适用于广泛的场景。企业版则专为高性能需求设计,广泛应用于大规模AI任务,如生成式AI、自动驾驶、量化金融和生物科技等领域。
JuiceFS 文件系统由三个核心部分组成:
• 元数据引擎:负责存储文件元数据,包括文件系统的常规元数据以及文件数据的索引。
• 数据存储:通常采用对象存储服务,既可以是公有云提供的对象存储,也可以是私有化部署的对象存储服务。
• JuiceFS 客户端:提供多种接入方式,包括POSIX(通过FUSE实现)、Hadoop SDK、CSI Driver以及S3网关等,以满足不同场景的需求。
JuiceFS 社区版架构图
CRAQ 一致性算法
在3FS中,数据的写入是通过链式复制(Chain Replication)按顺序逐节点传递的,这种设计虽然保证了强一致性,但也会引入较高的写入延迟。如果链中的某个副本不可用,3FS会将该副本移至链的末尾,并在其恢复可用时进行数据恢复。恢复过程中,系统会复制整个Chunk的内容到该副本,而不是仅同步增量数据。尽管这种设计简化了写入逻辑(相比Ceph等使用pg log实现增量恢复的系统),但它确实会导致更高的写入延迟。不过,对于以读取为主的AI应用场景,这种延迟影响较小。
另一方面,JuiceFS采用对象存储作为数据存储方案,充分利用了对象存储的天然优势,如数据可靠性和一致性。存储模块提供了一组标准的对象操作接口(如GET、PUT、HEAD、LIST等),用户可以根据需求灵活对接不同的存储系统,例如公有云的对象存储(如AWS S3、Azure Blob Storage)或私有化部署的对象存储(如MinIO、Ceph RADOS)。对于AI场景中的高带宽需求,JuiceFS社区版通过本地缓存来优化性能,而企业版则进一步引入了分布式缓存,以满足更大规模的聚合读取带宽需求。这种设计使得JuiceFS在灵活性和性能之间取得了良好的平衡。
JuiceFS 是一款云原生分布式文件系统,其数据存储依赖于对象存储服务。社区版于2021年在GitHub开源,支持与多种元数据引擎集成,适用于广泛的场景。企业版则专为高性能需求设计,广泛应用于大规模AI任务,如生成式AI、自动驾驶、量化金融和生物科技等领域。
JuiceFS 文件系统由三个核心部分组成:
元数据引擎:负责存储文件元数据,包括文件系统的常规元数据以及文件数据的索引。
数据存储:通常采用对象存储服务,既可以是公有云提供的对象存储,也可以是私有化部署的对象存储服务。
JuiceFS 客户端:提供多种接入方式,包括POSIX(通过FUSE实现)、Hadoop SDK、CSI Driver以及S3网关等,以满足不同场景的需求。
在3FS中,文件属性以键值对(KV)的形式存储在元数据服务中。元数据服务是一个无状态的高可用服务,底层依赖FoundationDB作为存储引擎。FoundationDB是Apple开源的一款高性能分布式KV数据库,具有出色的稳定性和扩展性。它通过全局排序的键值对并将其均匀分布到不同节点上来实现高效的数据管理。
为了优化目录列表(list)操作的效率,3FS采用了一种特殊的键设计:使用“DENT”前缀结合父目录的inode号和文件名作为目录项(dentry)的键。同时,inode的键通过“INOD”前缀与inode ID(采用小端字节序编码)组合而成,这种设计使得inode数据能够均匀分布在多个FoundationDB节点上。这种设计与JuiceFS使用的 TKV(Transactional Key-Value Database)[3] 存储元数据的方式类似。
JuiceFS社区版的元数据模块与存储模块类似,提供了一组操作元数据的接口,支持多种元数据服务,包括Redis、TiKV等KV数据库,以及MySQL、PostgreSQL等关系型数据库,甚至也可以使用FoundationDB。而JuiceFS企业版则采用了自研的高性能元数据服务,能够根据负载情况动态平衡数据和热点操作,有效避免在大规模训练中因频繁操作临近目录文件元数据而导致的节点热点问题。这种设计显著提升了系统的稳定性和性能。
3FS 的客户端不仅支持通过 FUSE 进行文件操作,还提供了一组 Native Client API,允许用户绕过 FUSE 直接访问数据。这些 API 的设计类似于 Linux AIO(异步 I/O),旨在避免使用 FUSE 时产生的数据拷贝,从而减少 I/O 延迟并降低内存带宽的占用。以下将详细解析这组 API 如何实现用户进程与 FUSE 进程之间的零拷贝通信。
3FS 使用 hf3fs_iov
结构来保存共享内存的大小、地址以及其他相关属性,并通过 IoRing
机制在两个进程之间进行高效通信。这种设计使得数据可以直接在用户进程和存储服务之间传递,无需经过额外的拷贝操作,从而显著提升了性能。
3FS NATIVE Client API
当用户调用接口创建 hf3fs_iov
时,系统会在 /dev/shm
上分配一块共享内存,并创建一个指向该内存的软链接。软链接的地址位于 /mount_point/3fs-virt/iovs/
,这是一个虚拟目录。当 3FS 的 FUSE 进程接收到创建软链接的请求,并发现其地址位于该虚拟目录时,会根据软链接的名称解析出共享内存的相关参数,并将内存地址注册到所有 RDMA 设备(除了 IoRing
)。ibv_reg_mr
返回的结果存储在 RDMABuf::Inner
数据结构中,用于后续的 RDMA 请求。
对于 IoRing
的内存,同样使用 hf3fs_iov
进行管理,但在创建对应的软链接时,文件名中会包含更多与 IoRing
相关的信息。如果 FUSE 进程发现该内存用于创建 IoRing
,它也会在自身进程内创建对应的 IoRing
。通过这种机制,用户进程和 FUSE 进程可以共享同一个 IoRing
,从而实现高效通信。
在进程间协作方面,3FS 在 /mount_point/3fs-virt/iovs/
目录中创建了三个虚拟文件,分别用于共享三个不同优先级的提交信号量(submit sem)。用户进程将请求放入 IoRing
后,会通过这些信号量通知 FUSE 进程有新请求到达。
IoRing
的尾部包含一个请求完成信号量,FUSE 进程通过调用 sem_post
通知用户进程请求已完成。这一整套机制确保了用户进程与 FUSE 进程之间的高效数据通信和操作同步。
3FS 的 FUSE 客户端实现了文件和目录的基本操作,而 JuiceFS 的 FUSE 客户端功能更加全面。例如,在 3FS 中,文件长度是最终一致的,这意味着在写入过程中,用户可能会读取到不正确的文件长度。而 JuiceFS 在每次成功上传对象后会立即更新文件长度,确保数据的一致性。此外,JuiceFS 还支持以下高级文件系统功能:
BSD 锁(flock)和 POSIX 锁(fcntl)
file_copy_range
接口
readdirplus
接口
fallocate
接口
除了 FUSE 客户端,JuiceFS 还提供了多种接入方式,包括 Java SDK、S3 Gateway 和 CSI Driver 等。企业版还提供了 Python SDK,该 SDK 将 JuiceFS 客户端直接运行在用户进程中,避免了通过 FUSE 带来的额外性能开销。更多详细信息可参考相关文档:Python SDK[4]。
3FS 将每个文件划分为固定大小的 chunk,每个 chunk 位于一个由 CRAQ 算法管理的链(chain)上。用户可以通过 3FS 提供的脚本生成一个 chain table,并将其提交到元数据服务。在创建新文件时,系统会从 chain table 中选取特定数量的 chain(数量由 stripe 参数决定),并将这些 chain 的信息存储到文件的元数据中。
由于 3FS 中的 chunk 大小固定,客户端只需在首次访问文件时获取 inode 的 chain 信息,之后便可以根据文件的 inode 和 I/O 请求的 offset 及 length 计算出请求涉及的 chunk,从而避免了每次 I/O 操作都需要查询数据库的开销。具体来说,客户端可以通过 offset / chunk_size
计算出 chunk 的索引,而 chunk 所在的 chain 索引则通过 chunk_id % stripe
确定。有了 chain 索引,客户端就可以获取 chain 的详细信息(例如 chain 由哪些 target 组成),并根据路由信息将 I/O 请求发送到相应的存储服务。存储服务在收到写请求后,会以写时复制(Copy-on-Write, COW)的方式将数据写入新位置,而原始数据在引用计数清零之前仍然可读。
为了应对数据分布不均的问题,3FS 对每个文件的第一个 chain 采用轮询(round-robin)方式选择。例如,当 stripe 为 3 时,第一个文件的 chain 可能为 chain0、chain1 和 chain2,而下一个文件的 chain 则为 chain1、chain2 和 chain3。系统会对选中的 chain 进行随机排序,并将结果存储到元数据中。下图展示了 stripe 为 3 时一个文件的分布示例,chain 随机排序后的顺序为 1、3、2。这种设计有效平衡了数据分布,提升了系统的整体性能。
3FS 文件分布
JuiceFS 采用 Chunk、Slice 和 Block 三层结构来管理数据块。每个 Chunk 的大小固定为 64MB,主要用于优化数据的查找和定位。实际的文件写入操作在 Slice 上执行,每个 Slice 代表一次连续的写入过程,属于特定的 Chunk,并且不会跨越 Chunk 的边界,因此其长度不超过 64MB。Chunk 和 Slice 是逻辑上的划分,而 Block(默认大小为 4MB)则是物理存储的基本单位,用于在对象存储和磁盘缓存中实现数据的最终存储。更多详细信息可以参考 JuiceFS 的官网介绍[5]。
JuiceFS 文件分布
JuiceFS 中的 Slice 是一个独特的设计,主要用于记录文件的写入操作并将数据持久化到对象存储中。由于对象存储不支持原地修改文件,JuiceFS 通过引入 Slice 结构来实现文件内容的更新,而无需重写整个文件。这种机制类似于日志文件系统(Journal File System),其中写入操作仅创建新对象,而不是覆盖现有对象。当文件被修改时,系统会创建一个新的 Slice,并在上传完成后更新元数据,将文件内容指向新的 Slice。被覆盖的 Slice 内容会通过异步压缩过程从对象存储中删除,这可能导致对象存储的使用量暂时超过文件系统的实际使用量。
此外,JuiceFS 的所有 Slice 都是一次性写入的,这种设计减少了对底层对象存储一致性的依赖,并显著简化了缓存系统的复杂性,使数据一致性更易于保证。同时,这种设计还为文件系统实现零拷贝语义提供了便利,支持诸如 copy_file_range
和 clone
等高级操作。
3FS 采用 RDMA 作为底层网络通信协议,而 JuiceFS 目前尚未支持这一技术。以下是对 3FS 实现方式的分析:
3FS 通过实现一个 RPC 框架来管理底层 IB(InfiniBand)网络的操作。除了网络通信外,RPC 框架还提供了序列化、小包合并等功能。由于 C++ 不支持反射机制,3FS 使用模板实现了一个反射库,用于序列化 RPC 请求和响应的数据结构。需要序列化的数据结构只需通过特定的宏定义其属性即可。由于 RPC 调用是异步的,序列化后的数据必须从堆上分配内存,并在调用完成后释放。为了提高内存分配和释放的效率,3FS 使用了缓存机制。缓存由两部分组成:一个线程本地存储(TLS)队列和一个全局队列。从 TLS 队列获取缓存时无需加锁,但当 TLS 缓存为空时,则需要加锁并从全局队列中获取缓存。因此,在最优情况下,缓存获取是无锁的。
与 I/O 请求不同,缓存对象的内存并未注册到 RDMA 设备中。因此,当数据到达 IBSocket 后,会被拷贝到一个已在 IB 设备注册的缓冲区中。多个 RPC 请求可能会被合并为一个 IB 请求发送到对端。下图展示了 FUSE 客户端调用元数据服务的 RPC 过程。
3FS FUSE Client 调用 Metadata服务的 RPC 过程
在大规模 AI 训练中,高读带宽是最核心的需求之一。为了满足这一需求,3FS 采用了性能优先的设计策略,将数据存储在高速磁盘上,同时要求用户自行管理底层存储。这种设计虽然显著提升了性能,但也带来了较高的成本和更复杂的维护工作。为了充分发挥底层硬件的性能,3FS 实现了客户端到网卡的零拷贝机制,利用共享内存和信号量来减少 I/O 延迟和内存带宽占用。此外,通过带有 TLS 的 I/O 缓冲池和合并网络请求,3FS 优化了小 I/O 和文件元数据操作的性能,并引入了 RDMA 技术以进一步提升效率。我们将持续关注 3FS 在性能优化方面的进展,并探索如何将这些技术应用到实际场景中。
相比之下,JuiceFS 使用对象存储作为底层数据存储,显著降低了用户的存储成本和维护负担。为了满足 AI 场景对读性能的高要求,JuiceFS 企业版引入了分布式缓存、分布式元数据服务和 Python SDK,从而在提升文件系统性能和扩展能力的同时,保持了低存储成本的优势。在即将发布的 v5.2 企业版中,JuiceFS 还在 TCP 网络中实现了零拷贝,进一步提高了数据传输效率。
JuiceFS 提供了完整的 POSIX 兼容性和成熟活跃的开源生态,能够适应更广泛的使用场景。其支持 Kubernetes CSI,极大简化了在云平台上的部署和运维工作。此外,JuiceFS 还提供了 Quota、安全管理和数据灾备等多项企业级功能,使企业能够更便捷地在生产环境中部署和应用 JuiceFS。
53AI,企业落地大模型首选服务商
产品:场景落地咨询+大模型应用平台+行业解决方案
承诺:免费场景POC验证,效果验证后签署服务协议。零风险落地应用大模型,已交付160+中大型企业
2025-02-04
2025-02-04
2024-09-18
2024-07-11
2024-07-09
2024-07-11
2024-07-26
2025-02-05
2025-01-27
2025-02-01
2025-03-20
2025-03-16
2025-03-16
2025-03-13
2025-03-13
2025-03-11
2025-03-07
2025-03-05