浅析 Redis 作为 JuiceFS 元数据引擎的优劣势

高昌健 2022.07.22

作者简介: 高昌健,Juicedata 技术专家,参与建设 JuiceFS 开源社区的主力队员。十年互联网行业从业经历,曾在知乎、即刻、小红书多个团队担任架构师职位,专注于分布式系统、大数据、AI 领域的技术研究。

概览

Redis 是诞生于 2009 年的开源内存 KV 数据库,根据 DB-Engines 网站的排名统计,Redis 是目前世界上最流行的 KV 存储。由于 Redis 具有极高的性能、足够友好的用户体验、活跃的开发者社区以及成熟的生态,JuiceFS 开源之初 初便将 Redis 作为首个支持的元数据引擎,Redis 也是目前 JuiceFS 社区中使用非常广泛的元数据引擎之一。

作为一个学习门槛很低的数据库,Redis 非常适合在快速上手时使用。但是随着数据规模的增长,以及将 JuiceFS 部署到生产环境以后对于服务可用性、数据可靠性的要求,传统 Redis 架构的一些弊端也会逐渐显现。本文将针对不同 Redis 架构的优劣势进行分析,力图帮助读者更加全面地了解 Redis 作为 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 的数据分布可能会不均衡,如果存在单个特别大的文件系统,只能通过 scale up 的方式来扩容。

虽然解决了 Redis Cluster 的事务限制问题,但是上一小节提到的数据可靠性问题依然存在,Redis Cluster 仍旧无法提供 强一致性保证 ,本质上是由于 Redis Cluster 沿用了单机架构中的异步数据复制机制。因此使用 Redis Cluster 还是存在数据丢失的可能性。

再次进阶! 使用具有强一致性保证的 Redis 作为元数据引擎

不管是 Redis 还是 Redis Cluster 由于缺乏对于数据的强一致性保证,因此总是存在数据丢失的风险。Redis 公司(前 Redis Labs)其实也在尝试解决这个问题,于是有了 RedisRaft 开源项目,试图补齐 Redis 的这个短板(当然可能会牺牲掉一部分性能)。但是 RedisRaft 项目迟迟没有 GA,目前仍处于开发阶段。

尝试解决这个问题的不仅仅是开源社区,各个公有云服务商也在积极推动。2021 年 8 月,AWS 发布了 Amazon MemoryDB for Redis(以下简称 MemoryDB)服务,这是继 Amazon ElastiCache for Redis 之后又一个兼容 Redis 协议的全托管存储服务。正如这个服务的名字所描述的,MemoryDB 可以作为一个主数据库,而不仅仅是一个缓存层。它具有微秒级的读取性能以及毫秒级的写入性能,最重要的是,MemoryDB 提供强一致性保证,这是通过将写操作持久化到一个分布式事务日志系统来实现的,以确保成功写入的数据不会丢失。同时 MemoryDB 保留了 Amazon ElastiCache for Redis 的诸多有用特性,如自动故障切换、自动数据备份(基于事务日志实现)等。

如果你使用的是其它公有云,通常也会有着类似 MemoryDB 的兼容 Redis 协议的云服务,例如阿里云的云数据库 Redis 企业版(持久内存型)、华为云的云数据库 GaussDB(for Redis)。最重要的是这些云服务能够提供数据的强一致性保证,极大地提高了 JuiceFS 元数据的数据可靠性。