1. Seafile 介绍
Seafile 是国内开源的网盘产品,目前有社区版和专业版。因为目前也在做网盘类产品的研发,所以对 Seafile 的部分源码进行了研究。
2.Seafile Git 模型
Seafile 采用了类似 Git 的模型来存储文件,主要有下面几个概念。
Repo: 资料库,可以理解为一个存储盘。每创建一个资料库,都会在 Repo 表插入一条记录。
Branch: 类似 Git 里的分支,每个 Repo 都默认有一条 master 分支。
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 下的文件内容中。
3.2 Seafile 存储内容分析
(1)commit 记录
repo 下的每次变动,都会生成一次新的 commit,并写到 commits 文件夹下。
我们打开 commits 目录,以 repo(08455862-8513-4dcf-93ca-93db751881ba)为例,可以看见该 repo 下有四个文件,文件路径即为 commit id。
该 repo 当前最新的 commit id为 3b9518cbb12e55477f89c64a887a3c60e42e5093,我们根据 id 可以定位到文件 3b/9518cbb12e55477f89c64a887a3c60e42e5093 ,直接打开可以查看到其中的内容,是个 json 文本,内容如下:
{ "commit_id":"3b9518cbb12e55477f89c64a887a3c60e42e5093", "root_id":"3cd9159af2cb49e0864e8e05f58919d32dd98119", "repo_id":"08455862-8513-4dcf-93ca-93db751881ba", "creator_name":"me@example.com", "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 下的所有提交记录按顺序串起来。
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,表示文件夹。
A 文件夹的 id 为 5b787cc5a7a64836fe3a00707db71005dd4652f7,根据这个 id ,同样可以在 fs 目录下找到 A 目录的元数据描述文件 5b/787cc5a7a64836fe3a00707db71005dd4652f7,查看其中的内容为:
{ "dirents":[ { "id":"b14c54950f33f99bf1face498445e886d536d07d", "mode":33188, "modifier":"me@example.com", "mtime":1608985761, "name":"[MS-PAC].pdf", "size":1908956 }, { "id":"44ede347ece2990da2cda35a5dbcb765d4e51cae", "mode":33188, "modifier":"me@example.com", "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。
我们再来看看 seafile 资料库,可以看见和上面的分析一致。
(3)blocks 记录
blocks 目录下记录了文件的数据块,比如上面的 [MS-PAC].pdf,它的 block_ids 为[“d32f5687a0c2591e591abf40dbea06fbc0d2d422”],也就是只有一个 id 为 d32f5687a0c2591e591abf40dbea06fbc0d2d422 的数据块,我们在 blocks 目录下定位到 d3/2f5687a0c2591e591abf40dbea06fbc0d2d422 文件,它的大小刚好是 1.9 MB,也就是 [MS-PAC].pdf 的原始文件。
4.总结
通过上面对 Seafile 的存储结构分析,可以看出 Seafile 是通过 commits 、fs 和 blocks,将整个文件系统串起来了,并且每次修改,都会生成一个新的 commit,并将当前 commit 的 repo 快照,存储到了commits 、fs 和 blocks下。这样的好处是可以根据 commit id,快速回滚整个 repo 到某一个版本,而且 Seafile 基于 git 的模型设计,也天然的支持了多端同步的特性。但是凡事都有利有弊,这样设计带来的影响就是,在每次修改单个文件时,Seafile 都会将整颗文件树的信息存储下来,造成写放大,如果树很大的情况下,就不得不考虑写放大的性能损耗了。