关于golang:JuiceFS-数据读写流程详解

1次阅读

共计 3127 个字符,预计需要花费 8 分钟才能阅读完成。

对于文件系统而言,其读写的效率对整体的零碎性能有决定性的影响,本文咱们将通过介绍 JuiceFS 的读写申请解决流程,让大家对 JuiceFS 的个性有更进一步的理解。

写入流程

JuiceFS 对大文件会做多级拆分(参见 JuiceFS 如何存储文件),以进步读写效率。在解决写申请时,JuiceFS 先将数据写入 Client 的内存缓冲区,并在其中按 Chunk/Slice 的模式进行治理。Chunk 是依据文件内 offset 按 64 MiB 大小拆分的间断逻辑单元,不同 Chunk 之间齐全隔离。每个 Chunk 内会依据利用写申请的理论状况进一步拆分成 Slices;当新的写申请与已有的 Slice 间断或有重叠时,会间接在该 Slice 上进行更新,否则就创立新的 Slice。

Slice 是启动数据长久化的逻辑单元,其在 flush 时会先将数据依照默认 4 MiB 大小拆分成一个或多个间断的 Blocks,并上传到对象存储,每个 Block 对应一个 Object;而后再更新一次元数据,写入新的 Slice 信息。显然,在利用程序写状况下,只须要 一个 不停增长的 Slice,最初仅 flush 一次即可;此时能最大化施展出对象存储的写入性能。

以一次简略的 JuiceFS 基准测试为例,其第一阶段是应用 1 MiB IO 程序写 1 GiB 文件,数据在各个组件中的模式如下图所示:

留神:图中的压缩和加密默认未开启。欲启用相干性能须要在 format 文件系统的时候增加 --compress value--encrypt-rsa-key value 选项。

这里再放一张测试过程中用 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,与其默认的申请大小限度统一

相较于程序写来说,大文件内随机写的状况要简单许多;每个 Chunk 内可能存在 多个不间断 的 Slice,使得一方面数据对象难以达到 4 MiB 大小,另一方面元数据须要屡次更新。同时,当一个 Chunk 内已写入的 Slices 过多时,会触发 Compaction 来尝试合并与清理这些 Slices,这又会进一步增大零碎的累赘。因而,JuiceFS 在此类场景下会比程序写有较显著的性能降落。

小文件的写入通常是在文件敞开时被上传到对象存储,对应 IO 大小个别就是文件大小。从下面指标图的第 3 阶段(创立 128 KiB 小文件)中也能够看到:

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

值得一提的是,对于这种有余一个 Block 的对象,JuiceFS 在上传的同时还会尝试写入到本地 Cache(由 --cache-dir 指定,能够是内存或硬盘),以期能晋升后续可能的读申请速度。从指标图中也能够看到,创立小文件时 blockcache 下有等同的写入带宽,而在读取时(第 4 阶段)大部分均在 Cache 命中,这使得小文件的读取速度看起来特地快。

因为写申请写入 Client 内存缓冲区即可返回,因而通常来说 JuiceFS 的 Write 时延非常低(几十微秒级别),真正上传到对象存储的动作由外部主动触发(单个 Slice 过大,Slice 数量过多,缓冲工夫过长等)或利用被动触发(敞开文件、调用 fsync 等)。缓冲区中的数据只有在被长久化后能力开释,因而当写入并发比拟大或者对象存储性能有余时,有可能占满缓冲区而导致写阻塞。

具体而言,缓冲区的大小由挂载参数 --buffer-size 指定,默认为 300 MiB;其实时值能够在指标图的 usage.buf 一列中看到。当使用量超过阈值时,JuiceFS Client 会被动为 Write 增加约 10ms 等待时间以减缓写入速度;若已用量超过阈值两倍,则会导致新的写入暂停直至缓冲区失去开释。因而,在察看到 Write 时延回升以及 Buffer 长时间超过阈值时,通常须要尝试设置更大的 --buffer-size。另外,通过增大 --max-uploads 参数(上传到对象存储的最大并发数,默认为 20)也有可能晋升写入到对象存储的带宽,从而放慢缓冲区的开释。

回写(Writeback)模式

当对数据的一致性和可靠性要求并不高时,还能够在挂载时增加 --writeback 以进一步晋升零碎性能。回写模式开启后,Slice flush 仅需写到本地 Staging 目录(与 Cache 共享)即可返回,数据由后盾线程异步上传到对象存储。请留神,JuiceFS 的回写模式与通常了解的先写内存不同,是须要将数据写入本地 Cache 目录的(具体的行为依据 Cache 目录所在硬件和本地文件系统而定)。换个角度了解,此时本地目录就是对象存储的缓存层。

回写模式开启后,还会默认跳过对上传对象的大小查看,激进地尽量将所有数据都保留在 Cache 目录。这在一些会产生大量两头文件的场景(如软件编译等)特地有用。

此外,JuiceFS v0.17 版本还新增了 --upload-delay 参数,用来延缓数据上传到对象存储的工夫,以更激进地形式将其缓存在本地。如果在期待的工夫内数据被利用删除,则无需再上传到对象存储,既晋升了性能也节俭了老本。同时相较于本地硬盘而言,JuiceFS 提供了后端保障,在 Cache 目录容量有余时仍然会主动将数据上传,确保在利用侧不会因而而感知到谬误。这个性能在应答 Spark shuffle 等有长期存储需要的场景时十分无效。

读取流程

JuiceFS 在解决读申请时,个别会依照 4 MiB Block 对齐的形式去对象存储读取,实现肯定的预读性能。同时,读取到的数据会写入本地 Cache 目录,以备后用(如指标图中的第 2 阶段,blockcache 有很高的写入带宽)。显然,在程序读时,这些提前获取的数据都会被后续的申请拜访到,Cache 命中率十分高,因而也能充分发挥出对象存储的读取性能。此时数据在各个组件中的流动如下图所示:

留神:读取的对象达到 JuiceFS Client 后会先解密再解压缩,与写入时相同。当然,如果未启用相干性能则对应流程会间接跳过。

做大文件内随机小 IO 读取时,JuiceFS 的这种策略则效率不高,反而会因为读放大和本地 Cache 的频繁写入与驱赶使得系统资源的理论利用率升高。可怜的是,此类场景下个别的缓存策略很难有足够高的收益。此时可思考的一个方向是尽可能晋升缓存的整体容量,以期达到能简直齐全缓存所需数据的成果;另一个方向则能够间接将缓存敞开(设置 --cache-size 0),并尽可能进步对象存储的读取性能。

小文件的读取则比较简单,通常就是在一次申请里读取残缺个文件。因为小文件写入时会间接被缓存起来,因而相似 JuiceFS bench 这种写入后不久就读取的拜访模式根本都会在本地 Cache 目录命中,性能十分可观。

总结

以上就是本文所要简略论述的 JuiceFS 读写申请解决流程相干的内容,因为大文件和小文件的个性差别,JuiceFS 通过对不同大小的文件执行不同的读写策略,从而大大的晋升了整体性能和可用性,能够更好的满足用户对不同场景的需要。

举荐浏览:如何在 Kubernetes 集群中玩转 Fluid + JuiceFS

如有帮忙的话欢送关注咱们我的项目 Juicedata/JuiceFS 哟!(0ᴗ0✿)

正文完
 0