8

Seafile 存储模型探索

 3 years ago
source link: https://zhuanlan.zhihu.com/p/339789495
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

1. Seafile 介绍

Seafile 是国内开源的网盘产品,目前有社区版和专业版。因为目前也在做网盘类产品的研发,所以对 Seafile 的部分源码进行了研究。

2.Seafile Git 模型

Seafile 采用了类似 Git 的模型来存储文件,主要有下面几个概念。

Repo: 资料库,可以理解为一个存储盘。每创建一个资料库,都会在 Repo 表插入一条记录。

m2YfUrm.png!mobile

Branch: 类似 Git 里的分支,每个 Repo 都默认有一条 master 分支。

f2uu6zZ.jpg!mobile

Commit: 类似 Git 里的提交记录,每次上传、修改文件都会产生一条新的 commit,通过上图可见,master 分支指向了最新的一条 commit。

3. Seafile 文件存储模型

3.1 Seafile 存储结构

Seafile 没有采用 MySQL 存储 commit 和 file 相关的元数据,而是直接将这些元数据以 JSON 文本的方式,存储到了文件系统里。打开 Seafile 存储目录,可以看见有三个主文件夹:blocks、commits 和 fs。

blocks: 文件的真实数据,采用了 CDC 算法进行分块存储。

commits: 存储了 repo 的 commit 记录。每条 commit 记录对应一个文件,文件内容为 commit 数据结构的 json 字符串。

fs: 存储了 repo 中的目录层级信息及文件信息。所有的目录层级在 fs 目录下都是打平的,文件之间的层级关系表达在 fs 下的文件内容中。

6FfM7vJ.jpg!mobile

3.2 Seafile 存储内容分析

(1)commit 记录

repo 下的每次变动,都会生成一次新的 commit,并写到 commits 文件夹下。

我们打开 commits 目录,以 repo(08455862-8513-4dcf-93ca-93db751881ba)为例,可以看见该 repo 下有四个文件,文件路径即为 commit id。

Z3q2qmj.jpg!mobile

该 repo 当前最新的 commit id为 3b9518cbb12e55477f89c64a887a3c60e42e5093,我们根据 id 可以定位到文件 3b/9518cbb12e55477f89c64a887a3c60e42e5093 ,直接打开可以查看到其中的内容,是个 json 文本,内容如下:

{
    "commit_id":"3b9518cbb12e55477f89c64a887a3c60e42e5093",
    "root_id":"3cd9159af2cb49e0864e8e05f58919d32dd98119",
    "repo_id":"08455862-8513-4dcf-93ca-93db751881ba",
    "creator_name":"[email protected]",
    "creator":"0000000000000000000000000000000000000000",
    "description":"Added "[MS-PAC].pdf".",
    "ctime":1608985761,
    "parent_id":"2a8d9636f7a589da18b340b9d010d2dd563b6203",
    "second_parent_id":null,
    "repo_name":"test",
    "repo_desc":"",
    "repo_category":null,
    "no_local_history":1,
    "version":1
}

这里记录了本次 commit 的相关信息,其中部分字段含义如下:

parent_id: 这里的 parent_id 为 2a8d9636f7a589da18b340b9d010d2dd563b6203,它其实就是上一次 commit 的 id,我们同样可以根据该 commit id,定位到文件2a/8d9636f7a589da18b340b9d010d2dd563b6203,并查看其中的内容,它的 parent_id 为 8a4cb2ea24b35ed3094c410ee6af636a93b54e3a,也就是再上一次的 commit id。

这样通过 branch 的 head commit 和其中每条 commit 记录的 parent_id,我们就可以将 branch 下的所有提交记录按顺序串起来。

uqaIba.jpg!mobile

root_id: 在本次 commit 时,repo 的根目录 id,可以在 fs 目录下找到该 id 对应的 json 文本。

(2)fs 记录

fs 文件夹下存储的是每次 commit 的快照记录。拿最新的 commit 3b9518cbb12e55477f89c64a887a3c60e42e5093 来说,根据上次 3b/9518cbb12e55477f89c64a887a3c60e42e5093 中记录的 root_id:3cd9159af2cb49e0864e8e05f58919d32dd98119,我们可以在 fs 目录下定位到 3c/d9159af2cb49e0864e8e05f58919d32dd98119 文件,该文件通过 zlib 编码了,解码后的内容也是一个 json 文本:

{
    "dirents":[
        {
            "id":"5b787cc5a7a64836fe3a00707db71005dd4652f7",
            "mode":16384,
            "mtime":1608985761,
            "name":"A"
        }
    ],
    "type":3,
    "version":1
}

这里记录了根目录下的文件信息,表示根目录下只有一个文件为 A,且 mode 是 16384,转成 8 进制也就是 0040000,表示文件夹。

Y3yqmam.png!mobile

A 文件夹的 id 为 5b787cc5a7a64836fe3a00707db71005dd4652f7,根据这个 id ,同样可以在 fs 目录下找到 A 目录的元数据描述文件 5b/787cc5a7a64836fe3a00707db71005dd4652f7,查看其中的内容为:

{
    "dirents":[
        {
            "id":"b14c54950f33f99bf1face498445e886d536d07d",
            "mode":33188,
            "modifier":"[email protected]",
            "mtime":1608985761,
            "name":"[MS-PAC].pdf",
            "size":1908956
        },
        {
            "id":"44ede347ece2990da2cda35a5dbcb765d4e51cae",
            "mode":33188,
            "modifier":"[email protected]",
            "mtime":1608985735,
            "name":"38b57137ee7c64038f658a6485ac8ea7.jpeg",
            "size":150713
        }
    ],
    "type":3,
    "version":1
}

这里表示 A 目录下有两个文件,分别为 [MS-PAC].pdf 和 38b57137ee7c64038f658a6485ac8ea7.jpeg,这两个文件的 mode 都是 33188,表示他们是真实文件,而不是文件夹。

以 [MS-PAC].pdf 为例,它的大小是 1908956,id 是 b14c54950f33f99bf1face498445e886d536d07d,根据 id 在 fs 目录下再次定位到 b1/4c54950f33f99bf1face498445e886d536d07d 文件,查看其内容:

{
    "block_ids":[
        "d32f5687a0c2591e591abf40dbea06fbc0d2d422"
    ],
    "size":1908956,
    "type":1,
    "version":1
}

这里记录的是文件 [MS-PAC].pdf 所有数据块的 id,对应到 blocks 目录下的文件。可以看见 [MS-PAC].pdf 只有一个数据块,id 为 d32f5687a0c2591e591abf40dbea06fbc0d2d422。

mqYNVb.jpg!mobile

我们再来看看 seafile 资料库,可以看见和上面的分析一致。

VF3q2mm.jpg!mobile

(3)blocks 记录

blocks 目录下记录了文件的数据块,比如上面的 [MS-PAC].pdf,它的 block_ids 为["d32f5687a0c2591e591abf40dbea06fbc0d2d422"],也就是只有一个 id 为 d32f5687a0c2591e591abf40dbea06fbc0d2d422 的数据块,我们在 blocks 目录下定位到 d3/2f5687a0c2591e591abf40dbea06fbc0d2d422 文件,它的大小刚好是 1.9 MB,也就是 [MS-PAC].pdf 的原始文件。

Mf2Ar2B.png!mobile

4.总结

通过上面对 Seafile 的存储结构分析,可以看出 Seafile 是通过 commits 、fs 和 blocks,将整个文件系统串起来了,并且每次修改,都会生成一个新的 commit,并将当前 commit 的 repo 快照,存储到了commits 、fs 和 blocks下。这样的好处是可以根据 commit id,快速回滚整个 repo 到某一个版本,而且 Seafile 基于 git 的模型设计,也天然的支持了多端同步的特性。但是凡事都有利有弊,这样设计带来的影响就是,在每次修改单个文件时,Seafile 都会将整颗文件树的信息存储下来,造成写放大,如果树很大的情况下,就不得不考虑写放大的性能损耗了。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK