概览
JuiceFS 是一款面向云原生设计的高性能共享文件系统,在 Apache 2.0 开源协议下发布。JuiceFS 提供完备的 POSIX 兼容性,可将几乎所有对象存储接入本地作为海量本地磁盘使用,亦可同时在跨平台、跨地区的不同主机上挂载读写。JuiceFS 也提供如 HDFS 兼容的 API、S3 兼容的 API 等多种类型接口,适用于不同的数据使用场景。
亚马逊云科技开发者社区为开发者们提供全球的开发技术资源。这里有技术文档、开发案例、技术专栏、培训视频、活动与竞赛等。帮助中国开发者对接世界最前沿技术,观点,和项目,并将中国优秀开发者或技术推荐给全球云社区。如果你还没有关注/收藏,看到这里请一定不要匆匆划过,点这里让它成为你的技术宝库!
JuiceFS 采用「元数据」与「数据」分离存储的架构设计,文件系统的元数据可以持久化在 Redis、MySQL、PostgreSQL、TiKV、etcd、BadgerDB、SQLite 等多种数据库中,以满足不同使用场景对于存储规模、性能的需求;数据可以持久化在对象存储(如 Amazon S3)中,目前 JuiceFS 已经支持了超过 30 种对象存储,即使是不在现有支持列表中的对象存储,只要它提供 S3 兼容的 API 都可以直接接入 JuiceFS。采用元数据与数据分离这种架构设计的一个好处是可以分别扩展元数据和数据的存储资源,以支撑海量数据存储,同时也能保证整体的性能。
本篇文章以 Redis 为例,介绍它作为 JuiceFS 的元数据引擎的优缺点,以及在实践中遇到的问题与挑战,并进一步介绍如何使用 Redis Cluster 以及 Amazon MemoryDB for Redis 来解决这些问题。
使用 Redis 作为 JuiceFS 的元数据引擎
Redis 是基于内存的键值存储系统,在 BSD 协议下开源,可用于数据库、缓存和消息代理等多种使用场景,在 Amazon 上可以使用 Amazon ElastiCache for Redis 快速创建一个托管的 Redis 服务。JuiceFS 支持使用 4.0 及以上版本的 Redis 作为元数据引擎,初次使用 JuiceFS 需要先创建一个新的文件系统,命令格式如下(这里假设 JuiceFS 已经安装完毕,请参考文档了解具体安装步骤):
bash
juicefs format [command options] META-URL NAME
对于 Redis 来说,META-URL 的格式为:
ruby
redis[s]://[<username>:<password>@]<host>[:<port>]/<db>
例如使用一个本地部署且没有设置密码的 Redis,META-URL 的格式应该为 redis://localhost:6379/1,有关 META-URL 格式中具体每一部分的解释请参考文档。juicefs format 命令中同样需要提供与对象存储有关的信息,这里以 Amazon S3 为例:
arduino
juicefs format \
--storage s3 \
--bucket https://<bucket>.s3.<region>.amazonaws.com \
--access-key <Access Key ID> \
--secret-key <Secret Access Key> \
redis://localhost:6379/1 \
myjfs
如果执行命令的 EC2 上已经绑定了 IAM Role 并具有读写指定 S3 bucket 的权限,那么以上命令中的 --access-key 和 --secret-key 选项可以省略。
当创建好 JuiceFS 文件系统以后就可以将它挂载到机器上,这一步可以通过 juicefs mount 命令来完成,例如:
bash
juicefs mount redis://localhost:6379/1 /mnt/jfs
挂载命令同样需要提供 Redis 的访问地址,因为需要从 Redis 中获取上一步创建文件系统时提供的对象存储访问信息,同时要指定一个本地目录作为挂载点,这里将 JuiceFS 文件系统挂载到了 /mnt/jfs 目录。通过 df 命令可以查看这个挂载点的信息:
bash
$ df -h /mnt/jfs
Filesystem Size Used Avail Capacity iused ifree %iused Mounted on
JuiceFS:myjfs 1.0Pi 0Bi 1.0Pi 0% 0 10485760 0% /mnt/jfs
因为 Amazon S3 的存储容量可以认为几乎是无限的,所以通过 df 命令看到的挂载点容量是 1.0Pi,这个值只是一个虚数,并不是实际的容量上限。当然你也可以为 JuiceFS 文件系统设置容量配额,以限制最大使用空间,具体请参考文档。
使用 Redis 作为元数据引擎的优势与限制
由于 Redis 完全使用内存来存储数据,因此在文件系统元数据请求的响应上是非常快的。同时在某些关键的元数据操作上(如 lookup),JuiceFS 还充分利用 Redis 提供的 Lua 脚本特性,提升这些关键操作的性能。在与 MySQL、TiKV、etcd 的元数据引擎性能评测中,Redis 的平均元数据请求性能相比其它几个数据库快了 2-4 倍。因此 Redis 非常适合那些对于元数据请求性能有着极致要求的使用场景。
不过同样是由于使用内存来进行数据存储这一点,造成单个 Redis 服务的存储容量具有一定的上限。根据 JuiceFS 的经验,文件系统中 1 个 inode 的元数据大约占用 300 字节内存,因此如果要存储 1 亿个 inode 需要大约 32GiB 内存。在实践中不推荐使用超过 64GiB 内存的实例来部署 Redis,一方面越大的内存意味着故障恢复的时间也会越久,另一方面因为 Redis 是单线程模型,没法利用多核,所以大内存机器的 CPU 资源也会存在很大的浪费。
Redis 单机架构的问题与挑战
当仅部署一个 Redis 实例时,这个实例会成为整个文件系统的单点,一旦元数据引擎服务不可用,将会造成所有操作无法正常进行。因此在生产环境中,通常建议部署多个副本实例以及 Redis Sentinel 来保证服务的可用性。多个副本可以确保同时有多个实例存活,并且这些实例之间的数据几乎一致(为什么说是「几乎一致」后面会讲到),而 Redis Sentinel 可以定期检测 Redis master 的健康状况,如果 Redis master 意外宕机,Redis Sentinel 会立即进行故障切换(切换时间通常在秒级),将某个副本实例晋升为 master 实例,保证 Redis 服务的可用性。
默认情况下,Redis master 与副本之间通过异步复制的方式来同步数据,这种方式可以在尽量不牺牲 Redis 性能的情况下将数据同步到副本实例。不过当 Redis master 出现故障时,可能因为某些数据还没来得及同步到副本实例而造成数据丢失。虽然 Redis 也提供同步复制的能力,但是这并不能将 Redis 变成一个 CP 系统,只是一定程度上降低了数据丢失的概率。
因此如何在保证服务可用性的情况下同时确保数据的可靠性,是使用 Redis 的一个大的挑战,特别是当这些数据是文件系统的元数据时。
使用 Redis Cluster
如果你熟悉 Redis 可能会问为什么不使用 Redis Cluster 作为元数据引擎?这背后的原因得从 JuiceFS 文件系统的元数据设计讲起。JuiceFS 的元数据有多种类型,如文件、目录、属性、文件锁等,这里每一种类型的元数据在 Redis 中都对应不同的 key。当执行某个元数据操作时可能会同时涉及多种类型的元数据,也就意味着需要同时修改多个 Redis 中的 key。为了保证文件系统元数据操作的原子性,JuiceFS 使用了 Redis 的事务特性来同时修改多个 key,确保元数据的一致性。
但是事务特性在 Redis Cluster 中有一定限制,Redis Cluster 划分了很多 hash slot,所有数据会根据哈希算法分配到不同的 hash slot 中。Redis Cluster 要求一个事务中所有操作的 key 必须在同一个 hash slot 中,也就是说它并不提供跨 hash slot 的事务支持。因此如果将 Redis Cluster 作为 JuiceFS 的元数据引擎,将无法保证元数据操作的原子性,这对于文件系统来说是不可以接受的。
幸运的是,通过在 key 中显式指定 hash tag 可以使得 Redis Cluster 将具有相同 hash tag 的 key 分配到相同的 hash slot 中。JuiceFS 正是使用了这个特性确保一个文件系统的所有 key 都存储到同一个 hash slot 中,从而保证元数据操作的原子性,这个功能目前已经在 JuiceFS v1.0.0 Beta3 中正式发布。Redis Cluster 在 JuiceFS 中的使用方式与单机 Redis 没有区别,可以直接沿用以前的命令。相比单机 Redis,使用 Redis Cluster 可以将不同 JuiceFS 文件系统的元数据存储到一个集群中,而不用单独维护多个 Redis 实例,大大降低了运维成本。
虽然解决了 Redis Cluster 的事务限制问题,但是上一小节提到的数据可靠性问题依然存在,Redis Cluster 仍旧无法提供强一致性保证,本质上是由于 Redis Cluster 沿用了单机架构中的异步数据复制机制。因此使用 Redis Cluster 还是存在数据丢失的可能性。
使用 Amazon MemoryDB for Redis
2021 年 8 月,Amazon 发布了 Amazon MemoryDB for Redis(以下简称 MemoryDB)服务,这是继 Amazon ElastiCache for Redis 之后又一个兼容 Redis 协议的全托管存储服务。正如这个服务的名字所描述的,MemoryDB 可以作为一个主数据库,而不仅仅是一个缓存层。它具有微秒级的读取性能以及毫秒级的写入性能,最重要的是,MemoryDB 提供强一致性保证,这是通过将写操作持久化到一个分布式事务日志系统来实现的,以确保成功写入的数据不会丢失。同时 MemoryDB 保留了 Amazon ElastiCache for Redis 的诸多有用特性,如自动故障切换、自动数据备份(基于事务日志实现)等。
于是回到前面讨论 Redis 单机架构时提到的挑战:如何在保证服务可用性的情况下同时确保数据的可靠性,MemoryDB 非常好地解决了这个问题(在牺牲了一点写性能的前提下),因此 MemoryDB 是作为 JuiceFS 元数据引擎的绝佳选择。
总结
元数据管理一直是文件系统领域的核心话题,JuiceFS 开创性地将文件系统的元数据引擎「插件化」,让用户可以根据实际的使用场景自由选择适合的数据库作为 JuiceFS 的元数据引擎。本文以 Redis 为例,简要介绍了将 Redis 作为 JuiceFS 元数据引擎的基本使用方法,并深入探讨了 Redis 架构的挑战、演进以及目前在 Amazon 上的解决方案。
如果你对 JuiceFS 感兴趣,欢迎访问 JuiceFS 的文档主页了解更多信息,你也可以访问 JuiceFS 的 GitHub 共同参与社区建设。
本篇作者
高昌健
十年互联网行业从业经历,曾在知乎、即刻、小红书多个团队担任架构师职位,专注于分布式系统、大数据、AI 领域的技术研究。现在 Juicedata 担任技术专家,参与建设 JuiceFS 开源社区