前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >分布式文件系统:JuiceFS 技术架构

分布式文件系统:JuiceFS 技术架构

作者头像
Freedom123
发布2024-03-29 16:06:58
910
发布2024-03-29 16:06:58
举报
文章被收录于专栏:DevOpsDevOps

一、整体架构

JuiceFS 文件系统由三个部分组成:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-90ZtG0tw-1687771442157)(https://juicefs.com/docs/zh/assets/images/juicefs-arch-new-ab6339cb1408945cc9b70dc091c523c5.png)]

JuiceFS 客户端(Client):所有文件读写,乃至于碎片合并、回收站文件过期删除等后台任务,均在客户端中发生。可想而知,客户端需要同时与对象存储和元数据引擎打交道。客户端支持众多接入方式:

  • 通过 FUSE,JuiceFS 文件系统能够以 POSIX 兼容的方式挂载到服务器,将海量云端存储直接当做本地存储来使用。
  • 通过 Hadoop Java SDK,JuiceFS 文件系统能够直接替代 HDFS,为 Hadoop 提供低成本的海量存储。
  • 通过 Kubernetes CSI 驱动,JuiceFS 文件系统能够直接为 Kubernetes 提供海量存储。
  • 通过 S3 网关,使用 S3 作为存储层的应用可直接接入,同时可使用 AWS CLI、s3cmd、MinIO client 等工具访问 JuiceFS 文件系统。
  • 通过 WebDAV 服务,以 HTTP 协议,以类似 RESTful API 的方式接入 JuiceFS 并直接操作其中的文件。

数据存储(Data Storage):文件将会切分上传保存在对象存储服务,既可以使用公有云的对象存储,也可以接入私有部署的自建对象存储。JuiceFS 支持几乎所有的公有云对象存储,同时也支持 OpenStack Swift、Ceph、MinIO 等私有化的对象存储。

元数据引擎(Metadata Engine):用于存储文件元数据(metadata),包含以下内容:

  • 常规文件系统的元数据:文件名、文件大小、权限信息、创建修改时间、目录结构、文件属性、符号链接、文件锁等。
  • JuiceFS 独有的元数据:文件的 chunk 及 slice 映射关系、客户端 session 等。

JuiceFS 采用多引擎设计,目前已支持 Redis、TiKV、MySQL/MariaDB、PostgreSQL、SQLite 等作为元数据服务引擎,也将陆续实现更多元数据存储引擎。欢迎提交 Issue 反馈你的需求。

二、存储文件

与传统文件系统只能使用本地磁盘存储数据和对应的元数据的模式不同,JuiceFS 会将数据格式化以后存储在对象存储(云存储),同时会将文件的元数据存储在专门的元数据服务中,这样的架构让 JuiceFS 成为一个强一致性的高性能分布式文件系统。

任何存入 JuiceFS 的文件都会被拆分成一个或多个**「Chunk」(最大 64 MiB)。而每个 Chunk 又由一个或多个「Slice」组成。Chunk 的存在是为了对文件做切分,优化大文件性能,而 Slice 则是为了进一步优化各类文件写操作,二者同为文件系统内部的逻辑概念。Slice 的长度不固定,取决于文件写入的方式。每个 Slice 又会被进一步拆分成「Block」**(默认大小上限为 4 MiB),成为最终上传至对象存储的最小存储单元。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-U2UAt015-1687771442159)(https://juicefs.com/docs/zh/assets/images/juicefs-storage-format-new-fb61716d2baaf23335573504aa5f3bc7.png)]

因此,你会发现在对象存储平台的文件浏览器中找不到存入 JuiceFS 的源文件,存储桶中只有一个 chunks 目录和一堆数字编号的目录和文件,不必惊慌,这正是经过 JuiceFS 拆分存储的数据块。与此同时,文件与 Chunks、Slices、Blocks 的对应关系等元数据信息存储在元数据引擎中。正是这样的分离设计,让 JuiceFS 文件系统得以高性能运作。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-utFvu5De-1687771442161)(https://juicefs.com/docs/zh/assets/images/how-juicefs-stores-files-new-a5529b6935c59e4c6f36ac38dd1652f7.png)]

JuiceFS 的存储设计,还有着以下技术特点:

  • 对于任意大小的文件,JuiceFS 都不进行合并存储,这也是为了性能考虑,避免读放大。
  • 提供强一致性保证,但也可以根据场景需要与缓存功能一起调优,比如通过设置出更激进的元数据缓存,牺牲一部分一致性,换取更好的性能。详见「元数据缓存」
  • 支持并默认开启「回收站」功能,删除文件后保留一段时间才彻底清理,最大程度避免误删文件导致事故。

三、写入流程

JuiceFS 对大文件会做多级拆分(JuiceFS 如何存储文件),以提高读写效率。在处理写请求时,JuiceFS 先将数据写入 Client 的内存缓冲区,并在其中按 Chunk/Slice 的形式进行管理。Chunk 是根据文件内 offset 按 64 MiB 大小拆分的连续逻辑单元,不同 Chunk 之间完全隔离。每个 Chunk 内会根据应用写请求的实际情况进一步拆分成 Slice;当新的写请求与已有的 Slice 连续或有重叠时,会直接在该 Slice 上进行更新,否则就创建新的 Slice。Slice 是启动数据持久化的逻辑单元,其在 flush 时会先将数据按照默认 4 MiB 大小拆分成一个或多个连续的 Block,并作为最小单元上传到对象存储;然后再更新一次元数据,写入新的 Slice 信息。

显然,在应用顺序写情况下,只需要一个不停增长的 Slice,最后仅 flush 一次即可;此时能最大化发挥出对象存储的写入性能。以一次简单的 JuiceFS 基准测试为例,使用 1 MiB IO 顺序写 1 GiB 文件,在不考虑压缩和加密的前提下,数据在各个组件中的形式如下图所示:

juicefs stats 命令记录的指标图,可以直观地看到实时性能数据:

图中第 1 阶段:

  • 对象存储写入的平均 IO 大小为 object.put / object.put_c = 4 MiB,等于 Block 的默认大小
  • 元数据事务数与对象存储写入数比例大概为 meta.txn : object.put_c ~= 1 : 16,对应 Slice flush 需要的 1 次元数据修改和 16 次对象存储上传,同时也说明了每次 flush 写入的数据量为 4 MiB * 16 = 64 MiB,即 Chunk 的默认大小
  • FUSE 层的平均请求大小为约 fuse.write / fuse.ops ~= 128 KiB,与其默认的请求大小限制一致

小文件的写入通常是在文件关闭时被上传到对象存储,对应 IO 大小一般就是文件大小。指标图的第 3 阶段是创建 128 KiB 小文件,可以发现:

  • 对象存储 PUT 的大小就是 128 KiB
  • 元数据事务数大致是 PUT 计数的两倍,对应每个文件的一次 Create 和一次 Write

对于这种不足一个 Block Size 的对象,JuiceFS 在上传的同时还会尝试写入到本地缓存,来提升后续可能的读请求速度。因此从图中第 3 阶段也可以看到,创建小文件时,本地缓存(blockcache)与对象存储有着同等的写入带宽,而在读取时(第 4 阶段)大部分均在缓存命中,这使得小文件的读取速度看起来特别快。

由于写请求写入客户端内存缓冲区即可返回,因此通常来说 JuiceFS 的 Write 时延非常低(几十微秒级别),真正上传到对象存储的动作由内部自动触发,比如单个 Slice 过大,Slice 数量过多,或者仅仅是在缓冲区停留时间过长等,或应用主动触发,比如关闭文件、调用 fsync 等。

缓冲区中的数据只有在被持久化后才能释放,因此当写入并发较大时,如果缓冲区大小不足(默认 300MiB,通过 --buffer-size 调节),或者对象存储性能不佳,读写缓冲区将持续被占用而导致写阻塞。缓冲区大小可以在指标图的 usage.buf 一列中看到。当使用量超过阈值时,JuiceFS Client 会主动为 Write 添加约 10ms 等待时间以减缓写入速度;若已用量超过阈值两倍,则会导致写入暂停直至缓冲区得到释放。因此,在观察到 Write 时延上升以及 Buffer 长时间超过阈值时,通常需要尝试设置更大的 --buffer-size。另外,增大上传并发度(--max-uploads,默认 20)也能提升写入到对象存储的带宽,从而加快缓冲区的释放。

1.随机写

JuiceFS 支持随机写,包括通过 mmap 等进行的随机写。

要知道,Block 是一个不可变对象,这也是因为大部分对象存储服务并不支持修改对象,只能重新上传覆盖。因此发生覆盖写、大文件随机写时,并不会将 Block 重新下载、修改、重新上传(这样会带来严重的读写放大!),而是在新分配或者已有 Slice 中进行写入,以新 Block 的形式上传至对象存储,然后修改对应文件的元数据,在 Chunk 的 Slice 列表中追加新 Slice。后续读取文件时,其实在读取通过合并 Slice 得到的视图。

因此相较于顺序写来说,大文件随机写的情况更复杂:每个 Chunk 内可能存在多个不连续的 Slice,使得一方面数据对象难以达到 4 MiB 大小,另一方面元数据需要多次更新。因此,JuiceFS 在大文件随机写有明显的性能下降。当一个 Chunk 内已写入的 Slice 过多时,会触发碎片清理(Compaction)来尝试合并与清理这些 Slice,来提升读性能。碎片清理以后台任务形式发生,除了系统自动运行,还能通过 juicefs gc 命令手动触发。

2.客户端写缓存

客户端写缓存,也称为「回写模式」。

如果对数据一致性和可靠性没有极致要求,可以在挂载时添加 --writeback 以进一步提写性能。客户端缓存开启后,Slice flush 仅需写到本地缓存目录即可返回,数据由后台线程异步上传到对象存储。换个角度理解,此时本地目录就是对象存储的缓存层。

更详细的介绍请见「客户端写缓存」

四、读取流程

JuiceFS 支持顺序读和随机读(包括基于 mmap 的随机读),在处理读请求时会通过对象存储的 GetObject 接口完整读取 Block 对应的对象,也有可能仅仅读取对象中一定范围的数据(比如通过 S3 APIRange 参数限定读取范围)。与此同时异步地进行预读(通过 --prefetch 参数控制预读并发度),预读会将整个对象存储块下载到本地缓存目录,以备后用(如指标图中的第 2 阶段,blockcache 有很高的写入带宽)。显然,在顺序读时,这些提前获取的数据都会被后续的请求访问到,缓存命中率非常高,因此也能充分发挥出对象存储的读取性能。数据流如下图所示:

但是对于大文件随机读场景,预读的用途可能不大,反而容易因为读放大和本地缓存的频繁写入与驱逐使得系统资源的实际利用率降低,此时可以考虑用 --prefetch=0 禁用预读。考虑到此类场景下,一般的缓存策略很难有足够高的收益,可考虑尽可能提升缓存的整体容量,达到能几乎完全缓存所需数据的效果;或者直接禁用缓存(--cache-size=0),并尽可能提高对象存储的读取性能。

小文件的读取则比较简单,通常就是在一次请求里读取完整个文件。由于小文件写入时会直接被缓存起来,因此类似 juicefs bench 这种写入后不久就读取的访问模式,基本都会在本地缓存目录命中,性能非常可观。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2023-06-26,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、整体架构
  • 二、存储文件
  • 三、写入流程
    • 1.随机写
      • 2.客户端写缓存
      • 四、读取流程
      相关产品与服务
      对象存储
      对象存储(Cloud Object Storage,COS)是由腾讯云推出的无目录层次结构、无数据格式限制,可容纳海量数据且支持 HTTP/HTTPS 协议访问的分布式存储服务。腾讯云 COS 的存储桶空间无容量上限,无需分区管理,适用于 CDN 数据分发、数据万象处理或大数据计算与分析的数据湖等多种场景。
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档