博客 > 巨杉Tech | 文件系统 Filesystem 原理和实践

巨杉Tech | 文件系统 Filesystem 原理和实践

 2020-05-07  SequoiaDB,原理解析

SequoiaFS文件系统利用SequoiaDB的LOB对象来存储文件,从而能够使得SequoiaFS文件系统拥有SequoiaDB分布式数据库的相关特性,如分布式存储,高可用和高弹性,还以及易备份和可恢复、迁移等特性,并且还有类似NFS网络文件系统特性,如一地存储,多处可访问。

一、Fuse简介

FUSE 为用户态空间下的文件系统框架方案,使用户可以在用户态下而非深入内核层就可以实现文件系统。FUSE 主要由三个部分组成:FUSE 内核模块、FUSE 库以及 mount 工具。FUSE 内核模块为系统内核为注册 FUSE 文件系统而实现的 kernel 接入层,类似于一个驱动层模块。FUSE 库为用户态下实现文件系统的相关库文件,mount 工具为挂载用户文件系统工具。   

FUSE 文件系统主要由内核模块和 lib 库组成,内核模块在 Linux 下注册一个/dev/fuse的驱动设备,应用程序对于用户空间文件系统的操作请求由VFS转发写入/dev/fuse设备中,用户空间下的文件系统后台进程(daemon进程)会循环读取/dev/fuse设备中的请求并进行响应,然后将响应结果写回到/dev/fuse设备中并由VFS回复给请求应用进程,从而请求应用程序对该用户文件系统下的所有操作都是由文件系统进程来进行控制和实现,下面是FUSE的整体框架示意图。

图片3.png


二、 SequoiaFS 设计原理

2.1 SequoiaFS架构

SequoiaFS将SequoiaDB中的某个集合映射到文件系统某个挂载点目录下,从而实现利用SequoiaDB作为挂载点目录的文件存储数据库,而且又支持对该挂载点目录的一系列文件系统API操作,从而实现利用文件系统API对SequoiaDB集合数据的操作。


图片1.png


SequoiaFS是FUSE用户空间下的文件系统,在用户空间下通过SequoiaDB驱动层调用SequoiaDB数据库进行数据文件存储管理操作,同时作为FUSE文件系统,调用FUSE相关lib接口完成文件系统调用相关接口,实现Application用户软件在调用标准文件系统接口操作时对SequoiaDB的一系列操作,从而实现了分布式NFS文件系统。


图片2.png

 

2.2 集合-文件映射关系

SequoiaFS将指定的SequoiaDB目标集合映射挂载到某个目标路径mountpoint。在该路径下的所有文件和目录的操作都实际上是对SequoiaDB数据库的操作,映射示意图如下。

image.png

SequoiaDB中的Lob对象集合映射到SequoiaFS文件系统的mountpoint映射点,而目录元数据集合mdcl记录目录文件信息,文件元数据集合mfcl记录文件信息,而Lob对象集合是文件的lob形式存储集合载体。从而在SequoiaFS文件系统下,所有的文件和目录都只是虚拟的文件和目录,即文件和目录都只实际存储在后台SequoiaDB中,而SequoiaFS下所有的文件系统操作和调用都会实际转换到后台SequoiaDB对lob文件或者元数据集合的操作。实现利用文件系统API操作访问SequoiaDB数据文件。

从原理上,目录元数据和文件元数据是可以做成一个元数据集合的,但由于目录和文件在数目数量级上和访问上会有不同的差距,因此分开成两个元数据集合会有助于后续的扩展和元数据热点分离。并且目录元数据和文件元数据在记录结构上稍有差别。

2.3 目录-文件归属

在SequoiaFS文件系统中,目录和文件都是以元数据集合和lob对象的形式存储于SequoiaDB中的,两元数据在SequoiaDB集合之间是并列的,而文件与目录在SequoiaFS层次上又必须是从属的关系,因此文件元数据需要记录所属目录的id,即作为文件的Pid,以表明文件从属于某个目录,文件与文件之间不具有从属关系,所以文件元数据记录不需要记录id。

对于目录的id,为了保证id的唯一性,创建一个SequoiaFS.sequenceid集合以记录id值,每次创建目录都原子性递增,只增不减以保证唯一性,其结构为。

名称

类型

说明

备注

Name

String

表明名称,为Sequenceid


Value

INT64

表示ID的取值


 对于目录遍历,有相对路径模式和绝对路径模式,但是在记录目录元数据的时候,目录则以记录相对路径(即目录父目录Pid和本身id,文件记录父目录pid)的方式进行。

根目录的id为1,根目录下的文件或目录则以1为Pid。元数据集合中文件记录以Pid和name作为唯一索引项,其子文件和子目录也类似处理。

对于遍历查找文件,如果要查找/a/b/c/下的d文件,则需要从根目录往下遍历,直找到d文件的pid和name,根据pid和name则可以找到d文件的元数据信息,如下图所示。

image.png

方案不采用记录绝对路径方式的原因是,此种记录方式存在大量冗余信息,在mv目录涉及rename操作时,要对子文件或目录做大量改写操作;同时一致性保证需要做得比较复杂,并且中间层目录访问权限无法控制。

基于上述原因选择记录相对路径的方式,但是记录相对路径的效率比较差,因此考虑对目录元数据做缓存机制,将目录缓存下来,以保证文件访问时的效率。

2.4 目录缓存

在SequoiaFS下,访问目录和文件是频繁的热点操作,无论是ls查看还是文件读写,都需要大量的访问目录以验证文件是否存在及是否有权限操作等。所以为了减少与SequoiaDB的交互次数,将目录元数据文件进行缓存,将会大大提升SequoiaFS的性能。

由于目录和文件操作具有相关的耦合性,即当前操作的目录和文件在未来一段时间内也将大概率的进行再次操作,所以利用一个线程安全的LRU缓存作为目录的缓存区,将有效提高目录文件的操作性能。

在不同的节点机器上,可以同时mount同一个目标集合,这就意味着,在不同节点上很有可能对目标集合进行进行大量的不同操作,这就使得各个节点上的缓存会失效,这也是只对目录做缓存不对文件做缓存的原因之一,因为对目录的更改一般比文件更改少。一旦缓存失效,需要重新与SequoiaDB进行交互获取最新信息。

2.5 文件句柄和并发操作

在FUSE内核模块层,有多个请求队列,background,pending,processing,interrupts和forgets,SequoiaFS可以通过选项-o async_read或-o sync_read来指定SequoiaFS在读操作时进行异步IO模式还是同步IO模式。在异步IO模式下,用户的IO请求首先会发送到background队列中(用户可以根据选项-o max_background=N来设置background队列的大小)。同时,当background队列request请求达到最大值,容易导致IO被阻塞。异步读模式下,background队列中请求不断被发送 pending队列进行处理,待SequoiaFS完成read并返回数据时,request被触发读事件,表示本次io完成。由此可见,回到SequoiaFS层,read在异步io模式下是并发操作的。

image.png 

同样在SequoiaFS文件系统侧,上层应用通过通用的文件系统API进行对文件操作,例如读写文件需要open->read->write->close,open和read等接口通过文件句柄进行操作,而SequoiaFS在FUSE层调用了大量的SequoiaDB的接口以实现对数据库的操作,其中包括大量的对目录元数据集合、文件元数据集合和Lob文件的操作,所以同一个句柄需要记录对应的句柄以实现在同一个句柄之间调用。

而在SequoiaFS层,如果未指定-s以禁止多线程模式,那么SequoiaFS接收/dev/fuse设备消息线程和处理线程就会有多线程并发操作。如果在open一个文件并将在其fd上做并发读写操作,就会产生lob文件的并发读写,而SequoiaDB的连接是不允许多线程操作的,所以在每一个文件句柄加排他lock锁以保护。

三、 SequoiaFS操作实践

3.1 挂载目录

1. 在 DB 节点上创建目标集合

首次启动时,需要在远程DB节点上创建映射的目标集合collection。后面挂载目录之后,mountpoint目录下的所有文件的实际内容会以lob的形式存放在该集合下。而所有文件的属性信息会分别存放在目录元数据集合和文件元数据集合中。

$sdb
Welcome to SequoiaDB shell!
help() for help, Ctrl+c or quit to exit
> var db = new Sdb("localhost", 11810) 
Takes 0.124118s.
> db.createCS("foo")
localhost:11800.foo
Takes 0.352408s.
> db.foo.createCL("bar")
localhost:11800.foo.bar
Takes 2.466226s.
>

2. 在FS节点上创建挂载目录及配置文件

挂载目录mountpoint为FS节点上的目录,用以挂载映射远程DB节点的目标集合,所以需要在FS节点上创建该目录。

启动SequoiaFS时可以指定从配置文件中读取配置参数,建议首次启动前创建配置文件并进行参数设置,配置文件及日志路径建议参考配置文件规则进行设置,以防止出现多次映射时互相覆盖的情况。

$mkdir -p /opt/sequoiadb/mountpoint
$mkdir -p /opt/sequoiafs/conf/foo_bar/001/
$mkdir -p /opt/sequoiafs/log/foo_bar/001/

该例中按照默认参数值进行启动,所以不对参数进行配置,只是创建一个空配置文件,实际使用时按需写入相关配置值。

$touch /opt/sequoiafs/conf/foo_bar/001/SequoiaFS.conf

3. 挂载目录

挂载目录时,除了目标集合collection外,还需要指定一系列参数,具体参数选项详情请查看选项。

通过-i或者--hosts进行指定远程DB节点(协调节点),一旦挂载之后,mountpoint目录下的所有文件的属性信息会存放在远程DB节点上的目录元数据集合及文件元数据集合中,而文件内容会以lob的形式存放在目标集合下。目录元数据集合和文件元数据集合可以分别通过-d(或--metadircollection)和-f(或--metafilecollection)在进行指定,也可以直接通过指定--autocreate默认生成,该例指定默认生成。

$sequoiafs
 /
opt/sequoiadb
/mountpoint -i localhost:11810 -l foo.bar --autocreate -c /
opt/sequoiafs
/conf/foo_bar/001/ --diagpath  /
opt/sequoiafs
/log/foo_bar/001/ -o big_writes -o max_write=131072 -o max_read=131072

4. 本地FS节点通过mount可以看到挂载信息

mount
/dev/sda1 on / type ext4 (rw,errors=remount-ro)
proc on /proc type proc (rw,noexec,nosuid,nodev)
sysfs on /sys type sysfs (rw,noexec,nosuid,nodev)
none on /sys/fs/FUSE/connections type FUSEctl (rw)
none on /sys/kernel/debug type debugfs (rw)
none on /sys/kernel/security type securityfs (rw)
udev on /dev type devtmpfs (rw,mode=0755)
devpts on /dev/pts type devpts (rw,noexec,nosuid,gid=5,mode=0620)
tmpfs on /run type tmpfs (rw,noexec,nosuid,size=10%,mode=0755)
none on /run/lock type tmpfs (rw,noexec,nosuid,nodev,size=5242880)
none on /run/shm type tmpfs (rw,nosuid,nodev)
SequoiaFS on /opt/sequoiadb/mountpoint type FUSE.SequoiaFS (rw,nosuid,nodev,user=sdbadmin)

5. 在DB节点可以查看相关信息

var db = new Sdb("localhost", 11810) 
Takes 0.001705s.
db.list(4)
{
  "Name": "SequoiaFS.maphistory"
}
{
  "Name": "SequoiaFS.sequenceid"
}
{
  "Name": "SequoiaFS.bar_dir148139183721030"
}
{
  "Name": "SequoiaFS.bar_file148139183721030"
}
{
  "Name": "foo.bar"
}

对于每次mount,可以通过以上5张表查看相关信息,后续会介绍各表的作用,SequoiaFS.maphistory为映射挂载历史信息表,记录历史挂载的关键数据信息。

 6. 在FS节点挂载目录下创建文件和目录

cd /opt/sequoiadb/mountpoint/
touch testfile
echo 'hello, this is a testfile!' >> testfile
cat testfile 
hello, this is a testfile!
mkdir testdir
ls
testdir  testfile

上面我们在FS挂载目录下创建了文件testfile并写入'hello, this is a testfile!',并创建了子目录testdir。在DB节点查看目录元数据集合,可以查到testdir目录元数据信息记录。

db.SequoiaFS.bar_dir148139183721030.find()
{
  "_id": {
    "$oid": "5affae7115d4f9e718e723d0"
  },
  "Name": "testdir",
  "Mode": 16877,
  "Uid": 2109,
  "Gid": 2000,
  "Pid": 1,
  "Id": 621,
  "NLink": 0,
  "Size": 4096,
  "CreateTime": 1526705777945,
  "ModifyTime": 1526705777945,
  "AccessTime": 1526705777945,
  "SymLink": ""
}
Return 1 row(s).
Takes 0.019212s.


3.2 卸载目录

卸载目录可以通过fusermount程序指定-u来进行,也可以直接kill掉SequoiaFS进程。

1. fusermount 卸载

$
fusermount
 -u /
opt/sequoiadb
/mountpoint

2. kill 进程

$ps -ef | grep sequoiafs
$kill 程序PID

如果使用kill -9进行强杀进程,进程结束后会导致原 mountpoint 目录无法被 linux 文件系统正常访问的情况,需要使用fusermount -u <DIR> 来进行 unmount 即可。

四、总结

总体来说,SequoiaDB 的LOB大对象存储通过与FUSE的结合,实现了对Posix文件系统的支持与兼容。用户在除了使用SequoiaDB的LOB标准API接口进行文件的存储外,还可以通过SequoiaFS文件挂载的方式进行文件数据的存储和查看。

准备开始体验 SequoiaDB 巨杉数据库?