JuiceFS 源码阅读-上

共 2749字,需浏览 6分钟

 ·

2021-06-25 20:21

JuiceFS 源码阅读-上

最近研究文件系统,把近期比较火的JuiceFS代码翻出来看了一下,研究为啥其性能要比CephFS要好。

背景知识

参考资源:https://mp.weixin.qq.com/s/HvbMxNiVudjNPRgYC8nXyg

FUSE 是一个用来实现用户态文件系统的框架,这套 FUSE 框架包含 3 个组件:

  • 内核模块 fuse.ko :用来接收 vfs 传递下来的 IO 请求,并且把这个 IO 封装之后通过管道发送到用户态;

  • 用户态 lib 库 libfuse :解析内核态转发出来的协议包,拆解成常规的 IO 请求;

  • mount 工具 fusermount ;

背景:一个用户态文件系统,挂载点为 /tmp/fuse ,用户二进制程序文件为 ./hello ;当执行 ls -l /tmp/fuse 命令的时候,流程如下:
IO 请求先进内核,经 vfs 传递给内核 FUSE 文件系统模块;
内核 FUSE 模块把请求发给到用户态,由 ./hello 程序接收并且处理。处理完成之后,响应原路返回;

7c2b236ec5eafe40c351cb4bddb13ca3.webp


JuiceFS架构简介

JuiceFS 由三个部分组成:

  1. JuiceFS 客户端:协调对象存储和元数据存储引擎,以及 POSIX、Hadoop、Kubernetes、S3 Gateway 等文件系统接口的实现;

  2. 数据存储:存储数据本身,支持本地磁盘、对象存储;

  3. 元数据引擎:存储数据对应的元数据,支持 Redis、MySQL、SQLite 等多种引擎;

65f8758b395336c8863f6e3702f7f706.webp

JuiceFS 依靠 Redis 来存储文件的元数据。Redis 是基于内存的高性能的键值数据存储,非常适合存储元数据。与此同时,所有数据将通过 JuiceFS 客户端存储到对象存储中。

a9836478ab2da0d16cbc512e5c2bbdfa.webp

任何存入 JuiceFS 的文件都会被拆分成固定大小的 "Chunk",默认的容量上限是 64 MiB。每个 Chunk 由一个或多个 "Slice" 组成,Slice 的长度不固定,取决于文件写入的方式。每个 Slice 又会被进一步拆分成固定大小的 "Block",默认为 4 MiB。最后,这些 Block 会被存储到对象存储。与此同时,JuiceFS 会将每个文件以及它的 Chunks、Slices、Blocks 等元数据信息存储在元数据引擎中。

a69d23f4c364422bc33a233ac8a9919c.webp

使用 JuiceFS,文件最终会被拆分成 Chunks、Slices 和 Blocks 存储在对象存储。因此,你会发现在对象存储平台的文件浏览器中找不到存入 JuiceFS 的源文件,存储桶中只有一个 chunks 目录和一堆数字编号的目录和文件。不要惊慌,这正是 JuiceFS 高性能运作的秘诀!

补充一下源码中,每个blocks的命名规则定义,也就是最终存储在对象存储系统中的对象key名称。


func (c *rChunk) key(indx int) string {
    if c.store.conf.Partitions > 1 {
        return fmt.Sprintf("chunks/%02X/%v/%v_%v_%v", c.id%256, c.id/1000/1000, c.id, indx, c.blockSize(indx))
    }
    return fmt.Sprintf("chunks/%v/%v/%v_%v_%v", c.id/1000/1000, c.id/1000, c.id, indx, c.blockSize(indx))
}
从命名规则里面也能看出,数据是支持按partition进行分区存储的,也就是说最终存储数据的bucket可以是多个,这样有助于提高并发能力,特别是AWS S3每个bucket是有TPS性能上限的。

JuiceFS文件系统golang抽象接口组成

文件系统定义核心数据结构

type FileSystem struct {
    conf   *vfs.Config
    reader vfs.DataReader
    writer vfs.DataWriter
    m      meta.Meta
    logBuffer chan string
}

下图为个人理解所画的抽象接口结构图

d4439f5c7f9eb9c0202dc5ce1bf41073.webp
  • 整个JuiceFS文件系统实现主要拆分为VFS抽象实现和相关的config配置管理两大部分。

  • 任意文件File操作都涉及到数据和元数据两部分内容,因此代码中包含数据处理相关的DataReader和DataWriter两个抽象接口,用来处理数据的读取和写入两类请求。而元数据抽象出Meta一个数据库相关的接口,基于这个接口目前官方实现了dbMeta也就是兼容SQL相关的元数据实现,以及redisMeta实现(基于redis)。从性能表现来看,redis比MySQL性能要好3~5倍左右。具体可以参考这个

  • 所有的数据读写操作都要和本地缓存进行交互(Chunk->Slice->block(page)三个层级进行管理),缓存目前主要实现了基于本地文件系统diskStore和基于内存缓存cacheStore(堆空间)两种类型。数据写入和读取最终都是由对应的缓存模块同步到远程的ObjectSotrage。

  • config主要负责对本地缓存、元数据引擎连接信息等相关的配置。


JuiceFS支持的数据列表:

https://github.com/juicedata/juicefs/blob/main/docs/zh_cn/databases_for_metadata.md

JuiceFS元数据redis与MySQL性能对比测试:

https://github.com/juicedata/juicefs/blob/main/docs/en/metadata_engines_benchmark.md

下图是源码中对ChunkStore的抽象接口定义,通过NewReader和NewWriter生成对应的Reader读取抽象和Writer抽象,实现数据的读写相关原子接口。

94050e1ad8444832adead0375c1a13d9.webp


08fe3ea35ccd067306ea5776d5c12953.webpd5794fac4a18e3f916eb423148124fd0.webp



任何厂家的对象存储产品,只要实现下面的接口抽象,即可实现与JuiceFS的对接。

74ece1a319a71f229d1bd3919b07a538.webp

数据读取抽象接口

下图是数据读取抽象接口的继承组合关系

60759be395563232c2a38e79fea90bec.webp

最终的数据读取关联到rChunk这个struct的相关method方法。

f93d651bca2c0c80c4ea1dd9633ececb.webp

数据写入抽象接口

下图是数据写入抽象接口的继承组合关系

fc8d90ec977cae22dc7653928040d126.webp

最终的数据读取关联到wChunk这个struct的相关method方法。

3eaa9ac20015027b2b6c438adc8b3c2f.webp


小节

至此,基本理清了JuiceFS的基本构架和核心数据结构,下节开始对其读写具体接口流程进行剖析。


打个小广告,团队持续招聘存储开发和运维同学,感兴趣的可以看看之前公众号网易游戏招聘相关内容。



浏览 121
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报