导读:JuiceFS 是一个为云环境而设计的分布式文件系统,在 2021 年初开源后,过去一年在开源社区里发展很快,也受到了很多关注。本次分享希望让大家了解 JuiceFS 的设计背景、设计理念,以及它能够为开发者带来的帮助和价值。
2005 年之前还是一个以局域网为主的阶段,互联网才刚刚开始。这个时期的分布式文件系统或者说文件存储大部分是软硬一体的产品,我们能在市场上看到 NetApp、EMC 还有华为的一些存储柜子。这些产品给很多用户留下了一个印象,那就是存储实际上就是买硬件,买一个存储的柜子就搞定了。
2005 年之后,宽带互联网在全球范围内普及,互联网时代到来了。Web2.0的发展速度非常快,由第一代互联网那种简单呈现消息的门户网站转变成了一个所有用户都可以在网络上互动的产品形态,出现了像社交网络这样的产品。整个互联网产生的数据爆发式增长,这样的趋势也就导致像局域网时代一样去买硬件的存储柜已经跟不上网络时代数据增长的发展了。因为买硬件的柜子会涉及到选型、采购、到货、上架这样一个很长的流程,需要做很严谨的容量规划。
就在这个时间点上 Google 提出了他们的 Google File System 的论文,同时在技术社区里面出现了第一代纯软件定义存储的分布式文件系统产品。大家熟知的像大数据领域的 HDFS,已经被红帽收购的 CephFS、GlusterFS,还有当时面向 HPC 场景的 Lustre、BeeGFS 这些产品都是在 2005 年到 2009 年这样一个很短的时间窗口内诞生的。这也就意味着这些产品都有一个共同的时代背景以及面向当时硬件环境的设计,比如说当时还没有云的存在,所有的这些分布式文件系统都是面向机房里的物理机设计的。而当时的机房环境和今天最大的区别就是网络环境,当时机房里面是以百兆网卡为主的,如今机房里面基本都是万兆网卡了,这些给我们的软件设计和 IT 基础架构的设计带来了很多的改变。
第一代软件定义存储的产品像 HDFS 和 CephFS,以及 AI 流行以后的 Lustre 仍然在被很多公司在不同范围内使用。这些产品在发展过程中面对的新的挑战就是移动互联网的出现。
2010 年之后的十年时间,移动互联网相比 Web2.0 最大的变化就是以前人们只能坐在电脑前使用网络产生数据,而到了移动互联网时代,每个人每天醒着的时候都可以使用手机来产生数据,这就意味着整个互联网上数据的增长速度又快了一两个数量级。
第一代面向机房,用软件搭建分布式存储和分布式系统的方案在移动互联网时代也遇到了挑战,这也就让公有云应运而生。最早的像 AWS 就是在 2006 年发布了第一个产品 S3,在当时都还没有整个公有云的体系,没有 EC2 这样的计算环境,只有一个存储服务 S3。S3 就是想简化之前人们自己搭建机房,采购硬件然后通过软件去搭建分布式系统这个复杂的过程。对象存储在移动互联网时代有了一个非常快速的发展,它的诞生是为了解决之前分布式文件系统的一些问题,但也导致牺牲了一些能力,这个我们后面会再展开讲。
在移动互联网蓬勃发展之后,现在又迎来了物联网的发展,这对存储的规模、易管理性和扩展能力等各方面都有了一个全新的要求。当我们回顾公有云时会发现缺少一个非常适合云环境的分布式文件系统,直接将上一时代的 HDFS、CephFS、Lustre 这样的产品放到公有云上又不能体现出云的优势。
下图展示了这四个时代对应的存储产品的特点,以今天的视角回看,其中红字是每代产品中存在的问题,绿字是其好的特性。
第一代硬件产品在当时都是采用专有的硬件,扩容并不方便,因为兼容问题无法将不同厂商的硬件对接到一起使用。并且当时这一代软硬一体 NAS 也缺少高可用,大部分采取主从互备的方式。对于今天更大负载,更大规模的系统主从互备的可用性是不够的。此外,还存在整体成本较高的问题,因为需要专门的硬件,网络环境和相匹配的维护能力。
当时这一套软硬一体 NAS 有一个最大的优势就是它是 POSIX 兼容的,学习成本很低,对用户而言就和使用 Linux 本地盘的体验是一样的。这就意味着开发上层应用的时候是非常简单的,无论直接通过系统调用还是通过各种语言的框架去开发都是 POSIX 兼容的,会很方便。
到了互联网时代,第一代软件定义分布式文件系统包括 HDFS、CephFS 不再依赖于专有硬件,都是用标准的 X86 机器就可以搞定了。相比第一代软硬一体的扩展性有所提高,但是和后来的云存储相比还是不易扩展,能看到像 CephFS、HDFS 也都有一些单集群的上限,这是在当时的容量规模下进行设计造就的局限。
此外仍然缺少高可用的设计,大部分依旧采用主从互备,这样一来仍然要为机房独立的采购硬件,研究、维护大规模的分布式系统,因此整体的 TCO 还是相对较高的。同时相比上个时代对于运维的挑战更高了,因为不存在厂商的服务了,需要自己构建一套运维团队,拥有足够的运维能力,深入掌握这套开源的文件系统。
好在像 CephFS、Lustre 这些仍然是 POSIX 兼容的,而 HDFS 为了满足大数据 Hadoop 生态架构提出了一套新的存储接口 HDFS API。如果详细的去分析这套 API 能发现它其实是 POSIX 的一个子集,假设说 POSIX 有 200 个接口,到 HDFS 大概就剩下一半。从文件系统的功能上来看,它是对一个完整的文件系统做了一些裁剪,比如 HDFS 是 Append Only 的文件系统,也就是只能写新数据、追加文件,而不能对已有的文件做覆盖写。这是当时 HDFS 为了实现更大规模数据的存储而牺牲掉的一部分文件系统语义上的能力。
对象存储相比之前的文件系统提出了三个核心的能力:易扩展、高可用和低成本。在这三个核心能力的基础上提出要实现服务化,即用户不再需要投入任何成本在搭建、运维、扩容这些事情上了;此外还提出作为一个云服务要能装下足够海量的数据。
第一是接口少了。对象存储提供的 API 往往是 HDFS 的一个子集,相比 POSIX 能提供的能力就更少了。前面举例说假如 POSIX 有 200 个 API,那到 HDFS 可能只有 100 个了,到 S3 的时候大概只剩 20-30 个了。
第二是元数据操作性能下降了。对象存储本身并不是为那些需要操作复杂元数据的应用设计的,最开始设计对象存储只是想实现数据上传再通过 CDN 进行分发这样一个简单的业务模型。最初S3里的接口其实只有 PUT、GET 和 DELETE,后来又衍生出了 LIST、HEAD,但能看到像 RENAME、MV 这些在文件系统里很常见的操作至今对象存储都是不支持的。
POSIX 协议已经内置到 Linux 操作系统的内核里了,可以通过系统调用直接访问一个 POSIX 兼容的存储系统,Open、Write、Read 这些标准的接口也已经内置到各个编程语言里面了。在 POSIX 之上构建出来的应用生态从 Linux 开始流行到现在至少有三十年的时间了,再基于 POSIX 标准去做全新的文件系统上层应用不需要做任何的适配修改,也不需要提供任何特殊的 SDK,各个语言都是可以直接访问的。
HDFS 是一个只能追加的文件系统。它提供了一套自己的 SDK,比如说有大家熟知的用在 Hadoop 里面的 Java SDK。但这给上层的应用开发带来了一些新的挑战,即原来的程序是没办法直接使用 HDFS 的,必须在跟文件系统打交道这一层去做替换。替换的过程中就要去考虑接口之间是不是一一对应的,不对应的时候用什么方式能构建平滑的映射关系。
HDFS 经过十几年的发展已经成为 Hadoop 领域的一个默认标准,但其实在其他领域并没有得到广泛的应用,就是因为 HDFS 有着一套自己的 API 标准。HDFS 今天最成熟的还是用在 Hadoop 体系里面的 Java SDK。而其他的像 C、C++、Python、Golang 这些语言的 SDK 成熟度都还不够,包括通过 FUSE、WebDAV、NFS 这些访问 HDFS 的方式也不是很成熟,所以至今 HDFS 主要还只停留在 Hadoop 体系内去使用。
S3 最开始是基于 HTTP 协议提供了一套 RESTful API,好处是更加的标准和开放,但是同时也意味着它和现有的应用程序是完全不能兼容的,每一个应用都需要面向它做对接适配或者针对性的开发。S3 本身提供了很多语言的 SDK 方便开发者使用,也诞生了一些第三方项目,其中最著名的是 S3FS 和 Goofys。这两个项目都是支持将 S3 的 Bucket 挂载到系统里,像访问本地盘一样读写数据,现在各个公有云上也有提供针对自己对象存储的一些类似方案。
虽然这些能够实现把 Bucket 挂载到操作系统里,但是无法提供的是数据强一致性的保证,然后是高性能的 Listing,以前在本地盘里列目录是一个很轻量的动作,但是在对象存储的 Bucket 里去列目录性能代价是很高的。此外,对象存储也还没有支持原子的 Rename 和对文件进行随机写。POSIX 兼容的文件系统里是有 API 支持对文件的随机写的,打开一个文件然后 Seek 指定的偏移量去更新它的内容。但是在对象存储上面要想实现对数据或者说对文件的局部进行更新,唯一能做的就是先将文件完整的下载到本地,更新其中需要修改的那一部分数据,再把这个文件完整的上传回对象存储里面。整个随机写的操作代价是非常大的,无法支持需要随机写的应用。
现在 S3 原生的 API 里仍然没有 Rename,官方 SDK 和很多第三方工具的确提供了这个功能,比如通过 Goofys 或者 S3FS 把 Bucket 挂载到机器上再执行 Rename。但这里的 Rename 和文件系统原生的 Rename 是有差别的,这里举一个例子。
左边模拟文件系统,这是一个树形结构的目录树。而对象存储是不存在原生的树形结构的,它会通过设置对象的 Key 值(对象名)来模拟出一个路径,实际上对象存储里面的数据结构是扁平的,即右边这样的结构。
如果此时要执行一个 Rename 操作,把 /foo 这个目录名改成 /bar,因为文件系统是树形的数据结构,所以它就能找到这个目录名对应的 Inode 去执行一个原子的更新,把 foo 替换成 Bar。
因为对象存储的数据结构是扁平的,所以需要在对象存储里对这个 Key 的索引去做一次搜索,找到所有以 foo 为前缀的对象,可能是 1 个也可能是 100 万个。搜索出来以后要将这些对象进行一次 lO 拷贝,用新的名字作为 Key 值复制一遍,复制之后再更新相关的索引信息同时删掉旧的对象。
能看到这是一个比较长的操作过程,要先搜索对象,然后拷贝,再更新索引,整个过程其实是没有事务去保证的,有可能会出现一致性问题。目前大部分的对象存储会保证最终一致性,只有少数的对象存储会在自己原生的 API 里面保证强一致性,但是在第三方工具做的像 Rename 这样的衍生 API 里仍然只是保证最终一致性。
这里画了一副漫画,讲了老奶奶的一只猫跑到了树上,她想请这个小朋友帮她救猫,小朋友说没问题,我帮你把猫拿回来。大家可以看到他居然从另外一个画面里把猫给取了出来,老奶奶很疑惑,为什么树上和小朋友手里各有一只猫?小朋友说这只是一个 SYNC 的问题,你再去 Check 一下就好了,老奶奶再往树上看的时候树上的猫已经不见了。这里其实就体现了前面讲的 Rename 的过程,即在一个很长的流程里数据没有保持完整的一致性状态。
对应的命令在左边,首先列了一下这个 Bucket 的目录,发现有一个文件 cat.txt,将它 MV 到新的目录,换完目录之后看到原来的这个目录里面还有这个cat.txt,而新的目录里也有了一个 cat.txt,此时看到了两只猫,这看起来就不像是 MV 的操作了。但其实这是一个 SYNC 状态的问题,可能过了一会再去原来的旧目录里查看文件就会发现 cat.txt 不见了,在操作过程中去观察时数据状态还没有一致,但是系统会保证最终是一致的状态。而如果上层的应用在数据状态不一致的时候就已经去依赖它来进行下一步操作的话,那应用可能就会出错了。
这里举了几个例子来说明虽然今天对象存储是能够给我们提供优秀的扩展性、低廉的成本和便捷的使用方式,但是当我们需要对数据进行更复杂的分析、计算操作时对象存储仍然会给我们带来一些不方便的地方,比如说接口很少、需要针对性的开发、元数据的性能差。以前在文件系统里很轻量的操作,在对象存储里可能会变得很复杂;此外还可能会引入一些一致性的问题,对数据精确性要求很高的场景就不适合了。
从软硬一体到第一代软件定义文件系统再到 S3 各有各的优缺点,所以今天当我们在云的环境里面去看的时候会发觉缺少一个非常完善,非常适合云环境又能很好的支持大数据、AI 还有一些新兴的海量数据处理或者密集计算这些场景的文件系统产品,既可以让开发者非常容易的使用又有足够的能力去应对这些业务上的挑战,这就是我们设计 JuiceFS 的初衷。
前面分析了当前市面上的场景,当我们想去为云环境设计 JuiceFS 这个产品的时候,我们开始考虑自己的设计目标,要比较前面这些产品的一些优劣势和它们的设计思路,同时也要更多的关注用户业务场景的需求。文件系统是在整个 IT 基础架构中非常底层的产品,使用它的场景会非常的丰富,在不同的场景里对文件系统本身的规模、扩展性、可用性、性能、共享的访问能力以及成本甚至还有很多维度的要求都是不一样的,我们要做的是云环境通用型产品,就需要在这个矩阵上去做取舍。
这种设计会为我们在多场景多维度上去做取舍的时候带来更多的灵活性。像 HDFS 是元数据和数据分离的方案,而 CephFS 就更像是元数据和数据一体的方案。
我们提出把 JuiceFS 设计成一种插件式的引擎,让数据管理、元数据管理包括客户端都能提供一种插件式的能力,让用户结合自己的场景去做不同维度上插件的选择,来拼插出一个最适合自身业务场景需求的产品。
我们一直坚持 Linux 的一个最基本的设计哲学“Simple is better”,要尽可能的保持简单,一个基础设施产品只有足够简单才能做的足够健壮,才能给用户提供一个易维护的使用体验。
再细化一层到 JuiceFS 产品设计的关键能力上,我们提出在运维上做服务化,像 S3 一样使用,不需要用户自己维护;能支持多云,而非针对某一个云或者某一种环境设计,要在公有云、私有云、混合云的环境里都能用;支持弹性伸缩,不需要手动扩容缩容;尽可能做到高可用、高吞吐和低时延。
前面提到 POSIX 协议以及 HDFS 和 S3 各自的标准,虽然 POSIX 是一个访问接口上的最大集,但经过这些年的发展后,如今 Hadoop 生态中面向 HDFS 开发的组件非常之多,S3 经过十几年的发展面向它 API 开发的应用也非常多。这三个是今天市面上最流行的访问标准,因此最理想的情况是在一个文件系统上能够对外输出三种不同标准的 API 的访问能力,这样已有的这些程序就都可以直接对接进来,新的应用也可以选择更适合的访问协议进行开发。
强一致性是文件系统必备的能力,最终一致性是不能被接受的;还有海量小文件的管理,这是上一代文件系统普遍都没有解决的问题,因为在上一代的时间点上根本不存在海量小文件的场景,而今天我们看到在 AI 的领域里面,管理海量小文件逐渐变为基础需求。比如说在自动驾驶、人脸识别、声文分析这些场景里面大家都在面对着十几亿,数十亿甚至上百亿的文件,而又没有一个文件系统能够应对这样的规模,我们设计 JuiceFS 就要解决这个核心问题。后面还包括用透明的缓存做加速,尽量保持低成本这些也都在我们的设计目标里。
现在让我们来看一下最终的设计,左边是 High Level 的架构图,右边是数据存取的结构图。
架构图能体现出我们插件式的设计,这里有三个大的虚线框分别表示分布式文件系统里的三大组件:左下角的元数据引擎,右下角的数据引擎以及上方的访问客户端。元数据引擎,数据引擎和客户端三个组件都是插件式的设计,开发者可以结合具体应用和自己熟悉的技术栈去做选择。
在元数据引擎上用户可以选择市面上最流行的开源数据库包括 Redis、MySQL、PostgreSQL、TiKV,最近还支持了单机的 SQLite、BadgerDB 和 Kubernetes 上大家比较熟悉的 ETCD,还包括我们目前正在自研的一个内存引擎,总共已经支持了 9-10 款不同的存储引擎能够去管理 JuiceFS 的元数据。这样设计一是为了降低学习成本和使用门槛,二是可以方便用户结合具体场景对数据规模、性能、持久性、安全性的要求去做取舍。
数据引擎是用来存取数据文件的,我们兼容了市场上所有的对象存储服务。回顾上一代的分布式文件系统,HDFS 里有 Datanode,CephFS 里有 RADOS,几乎每一个文件系统都做了一件共同的事情就是把大量的裸机节点里的磁盘通过一个服务管理好,包括数据内容和数据副本。当我们考虑为云环境设计的时候就发现公有云上的对象存储已经能把数据管理做的非常完美了,它有足够强的扩展性,足够低的成本,足够安全的能力。所以我们在设计 JuiceFS 的时候就不再自己去做数据管理而是完全交给对象存储来做。无论是公有云私有云还是一些开源项目提供的,我们认为它们都是高可用,安全和低成本的。
上方的虚线框代表了 JuiceFS 的客户端,其中最底层这个是 JuiceFS 客户端里面的一个 Core Lib,负责与元数据引擎和数据引擎通信,并面向应用输出四种不同的访问方式:
右侧的图说明了我们是如何利用对象存储做数据持久化的。类似 HDFS,一个文件通过 JuiceFS 写到对象存储里时会被切割,默认是每 4M 为一个数据块存到对象存储里。这样切割的好处是可以提高读写的并发度,提升性能,这是在原来的对象存储里很难做到的。数据切割也有助于我们实现一个机制,即写到对象存储中的所有数据块只新增不修改。当需要对数据做覆盖写的时候,会把覆盖写的内容作为一个新的数据块写到对象存储里并同步更新元数据引擎,这样的话就可以读到新数据但是又不用修改旧数据块。这样的设计既可以支持随机写又可以规避掉对象存储的最终一致性问题,同时利用这个机制还可以为客户端提供细粒度的本地缓存的能力。
我们设计 JuiceFS 的时候还有一个很重要的目标就是提升用户体验。这里指的用户既包括使用文件系统的开发者又包括维护文件系统的运维和 SRE 同学。要做到好用好维护,我们就考虑提供在云原生设计中经常提及的可观测性。以往我们使用文件系统时认为系统是快是慢更偏向于感性的判断,很难看到非常详细的数据。
而 JuiceFS 提供了可观测性的能力,可以在客户端里打开一个详细的 accesslog,里面会输出上层应用访问文件系统时所有操作的细节,包括所有对元数据的请求,所有数据访问请求的细节以及消耗的时间。再通过一个工具将这些信息进行汇总,就可以看到上层应用运行过程中与文件系统这一层是如何交互的。当再遇到文件系统慢时,就可以很容易的分析出这个慢是什么原因造成的,是因为有太多的随机写,还是因为多线程并发之间存在阻塞。这里我们提供了 CLI 工具,在我们公有云的云服务上还提供了一些基于 GUI 的可视化工具去做观测,相信这样的观测能力对于上层应用的开发以及对于文件系统本身的运维都是很有帮助的。
在 Kubernetes 里一些有状态的应用需要一个持久卷,这个 PV 的选择一直是 Kubernetes 社区里面用户经常讨论的问题。以前大家可能会选择 CephFS,但是带来的挑战就是运维相对复杂,JuiceFS 希望给大家提供一个更易运维的选项。
因为 JuiceFS 提供了多访问协议的支持,所以在 AI 场景中很长的 Pipeline 上的各个环节都可以很方便的使用。JuiceFS 还提供了透明的缓存加速能力,可以很好的支持数据清洗、训练、推理这些环节,也支持了像 Fluid 这样的 Kubernetes 里面 AI 调度的框架。
JuiceFS 有一个很重要的能力就是海量文件的管理能力,JuiceFS 就是为管理数十亿文件的场景去做设计和优化的。在大数据场景下,因为它完全兼容 HDFS API,所以老的系统无论是什么发行版都可以很平滑的迁移进来。此外,在 Hadoop 体系外的很多MPP数据库像 ClickHouse、Elasticsearch、TDengine 和 MatrixDB 作为新的数据查询引擎,默认设计是为了追求更高性能的写入,需要配备 SSD 这样的存储磁盘。但是经过长时间的使用后,在大数据量下就能发现这些数据是有热数据也有温、冷数据的,我们希望能用更低成本的去存储温、冷数据。JuiceFS 就支持简单透明的存储温、冷数据,上层的数据库引擎基本不需要做任何修改,可以直接通过配置文件对接进来。
前面更多提到的是在互联网行业中应用较多的场景,而很多其他领域以往的行业应用都是基于 NAS 构建的,我们看到当前的趋势是这些行业都在向云环境迁移,借助云弹性的能力,利用更大规模的分布式环境去完成他们需要的诸如数据处理、仿真、计算这些场景。这里存在一个挑战就是如何把原先机房里的 NAS 迁移到云上。无论是社区还是商业的客户,JuiceFS 这边都支持了从传统的机房环境上云这样一个迁移 NAS 的过程,我们已经接触过的有基因测序、药物研究、遥感卫星,甚至像 EDA 仿真、超算这些领域,他们都已经借助 JuiceFS 实现了将机房中的 NAS 很平滑的上云。
文章内容仅供阅读,不构成投资建议,请谨慎对待。投资者据此操作,风险自担。