CephFS通过一个专门的MDS服务来管理文件系统元数据信息,MDS没有使用本地存储空间,而是将元数据存储在RADOS底座里面,元数据和数据使用不同的存储池来存储,做到元数据和数据分离。
Client和MDS之间进行元数据和Caps交互,Client直接将文件数据存储到RADOS底座,MDS也将日志和元数据存储到RADOS底座。
1 CephFS元数据设计
CephFS元数据包括Inode、Dentry和Dir,在MDS代码实现里分别对应CInode、CDentry和CDir。
Inode记录文件或目录的元数据,如文件大小、创建时间、所属存储池等信息,每个文件或目录都有一个Inode。
Dentry记录文件或目录名,与Inode相关联,实现如硬链接等功能。
Dir树状结构,用于链接目录下的Dentry。当单个Dir下Dentry数超过阈值,或者Dir下部分Dentry的负载超过阈值时,Dir会进行分片,产生多个Dir。
2 CephFS元数据、数据存储
CephFS存储池包括元数据池和数据池,Inode、Dentry、Dir等元数据存储在元数据池中,文件数据存储在数据池内。具体而言元数据散落在元数据池的各个RADOS对象的KV存储内(OMAP/Xattr),数据则按照切分规则散布在数据池的各个RADOS对象的数据部分。
2.1 元数据和数据存储模型
元数据存储模型:
目录以Inode号和Dir编号组成Dir分片对象名存储在元数据池,每个Dir分片的Dentry以OMAP形式存储在Dir分片对象的OMAP中。OMAP的每个key由目录下文件名/子目录名加上_head后缀组成,OMAP的每个key对应的value就是对应Dentry的Inode信息。
数据存储模型:
文件数据按切分规则(默认按4MB切分)切分成N块,Inode号和分块号组成分块对象名,然后各个分块对象按照CRUSH算法分布到数据池的各个OSD上。
2.2 使用Ceph工具查看存储模型
接下来我们在CephFS里实际创建目录并写入文件来验证CephFS的存储模型。验证分析使用的CephFS存储池情况如下:
$ ceph fs ls
name: a, metadata pool: cephfs.a.meta, data pools: [cephfs.a.data ]
其中cephfs.a.meta是元数据池,cephfs.a.data是数据池。
准备工作,首先创建/testdir目录,并写入2个文件:
$ mkdir testdir
$ dd if=/dev/urandom of=testdir/testfileA bs=1M count=9
$ dd if=/dev/urandom of=testdir/testfileB bs=1M count=3
2.2.1 查看元数据存储模型
(1)查看testdir目录的Inode号,然后去元数据池找对应的Dir对象
$ ll -i
total 1
1099511629781 drwxrwxr-x 2 db db 0 May 25 16:52 testdir
testdir的Inode号是1099511629781,将它转化成16进制形式:
$ printf "%x\n" 1099511629781
100000007d5
(2)刷新CephFS journal日志,强制将修改立即应用到元数据对象上:
$ ceph daemon mds.a flush journal
{
"message": "",
"return_code": 0
}
(3)然后在元数据池里就可以看到testdir目录对应的RADOS对象了:
$ rados -p cephfs.a.meta ls | grep 100000007d5
100000007d5.00000000
100000007d5是testdir 16进制形式的Inode号,00000000是16进制形式的分片ID,由于testdir目录中只有2个文件没有达到目录分片的条件所以目前只有一个目录分片对象。
(4)查看testdir目录下的Dentry
$ rados -p cephfs.a.meta listomapkeys 100000007d5.00000000
testfileA_head
testfileB_head
可以看到目录对象100000007d5.00000000的OMAP key就是由我们之前创建的testfileA和testfileB加上_head后缀组成的。
(5)查看Dentry对应的Inode信息
Dentry编码格式可以查看src/mds/CDir.cc中的CDir::_encode_dentry函数。
获取testfileA_head的OMAP value:
rados -p cephfs.a.meta getomapval 100000007d5.00000000 testfileA_head ~/out/testfileA-InodeStoreBare
移除头部的8字节snapid_t和1字节'L'/'I'标记:
$ dd if=testfileA-InodeStoreBare of=testfileA-InodeStoreBare.skip bs=9 skip=1
使用ceph-dencoder来导出InodeStoreBare信息:
可以看到testilfeA的Inode号1099511629783以及文件大小9437184,都和通过文件系统标准命令ls -li看到的一样。
$ ls -li
total 12288
1099511629783 -rw-rw-r-- 1 db db 9437184 May 25 19:06 testfileA
1099511629784 -rw-rw-r-- 1 db db 3145728 May 25 19:06 testfileB
2.2.2查看数据存储模型
(1)将ls -li命令看到的testfileA和testfileB的十进制Inode号转化成16进制
$ printf "%x\n" 1099511629783
100000007d7
$ printf "%x\n" 1099511629784
100000007d8
(2)在数据池中查看以16进制Inode号和分块号组成分块对象名
$ rados -p cephfs.a.data ls | sort
100000007d7.00000000
100000007d7.00000001
100000007d7.00000002
100000007d8.00000000
可以看到9MB的testfileA有3个数据对象,3MB的testfileB有1个数据对象。
查看每个RADOS对象大小,可以看到9MB的testfileA由2个4MB的对象文件和1个1MB的对象文件组成;3MB的testfileB由1个3MB的对象文件组成。
$ rados -p cephfs.a.data stat 100000007d7.00000000
cephfs.a.data/100000007d7.00000000 mtime 2021-05-25 19:06:36.000000, size 4194304
$ rados -p cephfs.a.data stat 100000007d7.00000001
cephfs.a.data/100000007d7.00000001 mtime 2021-05-25 19:06:20.000000, size 4194304
$ rados -p cephfs.a.data stat 100000007d7.00000002
cephfs.a.data/100000007d7.00000002 mtime 2021-05-25 19:06:20.000000, size 1048576
$ rados -p cephfs.a.data stat 100000007d8.00000000
cephfs.a.data/100000007d8.00000000 mtime 2021-05-25 19:06:36.000000, size 3145728