已经有三个月(好像不止3个月)没怎么看书学习了,要重新开始好好做人TAT。要多看书才能进步~
我一直以为元数据就是原始对象呢。今天才明白。。。
元数据是描述数据的数据,主要描述数据属性的信息,如:存储位置,历史数据,资源查找,文件记录等功能。是用于描述一个文件的特征的系数数据,比如:访问权限,文件拥有者以及文件数据库的分布信息。在集群文件系统中,分布信息包括文件在磁盘上的位置以及磁盘在集群中的位置。用户需要操作一个文件就必须先得到它的元数据,才能定位到文件的位置并且得到文件的内容、相关属性。
在linux中使用stat命令,可以显示文件的元数据。
[root@ceph-node1 ~]# stat 1.txt
File: ‘1.txt’
Size: 0 Blocks: 0 IO Block: 4096 regular empty file
Device: 802h/2050d Inode: 33889728 Links: 1
Access: (0644/-rw-r--r--) Uid: ( 0/ root) Gid: ( 0/ root)
Context: unconfined_u:object_r:admin_home_t:s0
Access: 2018-08-05 16:38:22.137566272 +0800
Modify: 2018-08-05 16:38:22.137566272 +0800
Change: 2018-08-05 16:38:22.137566272 +0800
Birth: -
File:文件名
Size:文件大小(单位:B)
Blocks:文件所占扇区个数,为8的倍数(通常的Linux扇区大小为512B,连续八个扇区组成一个block)
IO Block:每个数据块的大小(单位:B)
regular file:普通文件(此处显示文件的类型)
Inode:文件的Inode号
Links:硬链接次数
Access:权限
Uid:属主id/属主名
Gid:属组id/属组名
Access:最近访问时间
Modify:数据改动时间
Change:元数据改动时间
ceph核心组件有:OSD,Monitor,MDS
OSD:赋予底层物理设备(一般来说指得是磁盘)一些CPU,内存资源等,使之成为一个抽象对象存储设备(即OSD),它能够独立的完成一些低级别的文件系统操作(如:空间分配,磁盘IO调度等),以实现客户端IO操作(如:读、写)与系统调用(如:打开文件,关闭文件)之间的解耦。
Monitor:Monitor是集群的大脑,负责维护和分发集群的关键元数据。同时也是客户端与RADOS集群建立连接的桥梁。它是基于Paxos兼职议会算法构建的,具有分布式强一致性的小型集群,主要负责维护和传播集群表的权威副本。采用负荷分担的方式工作,因此任何时刻,任意类型的客户端或者OSD都可以通过和集群中任意一个Monitor进行交互,以索取或者请求更新集群表。基于Paxos的分布式一致性算法可以保证所有Monitor的行为自始至终都是正确和自治的。
MDS(我一直都不清楚它的作用。。。)全程是Ceph MetaData Server,主要保存文件系统服务的元数据,但对象存储和块存储设备是不需要使用该服务的。
RADOS是ceph的核心,基于RADOS及其派生的librados标准库可以开发任意类型的存储应用,典型如ceph的三大核心应用——RBD,RGW和cephFS。如图所示。
一个RADOS集群由大量OSD和少量的Monitor组成。
RADOS使用一张紧凑的集群表对集群拓扑结构和数据映射规则进行描述。任何时刻,任何持有该表的合法客户端都可以独立地与位于任意位置的OSD直接通信。
当集群拓扑结构发生变化时,RAODS确保这些变化能够及时地被Monitor捕获,并通过集群表高效传递至所有受影响的OSD和客户端,以保证对外提供不中断的业务和服务。
数据复制、故障检测和数据恢复都是由每个OSD自动进行,因此不存在明显的调度和处理瓶颈。
RADOS另辟蹊径地以基于可扩展哈希的受控副本分布算法——CRUSH作为衔接客户端和OSD的桥梁。说白了CRUSH是利用计算代替查表。CRUSH包含了获取集群当前数据分布形态所需的全部信息,所以OSD之间通过交互即可智能地完成诸如故障、扩容等引起的数据重新分布。
RADOS并不是直接将数据写入OSD的本地存储设备,而是引入了一个中间结构,称为PG,执行两次映射。如图:
第一次:File -> object
将客户端数据按照固定的大小进行切割,编号
第二次: object -> PG映射
第三次:PG -> OSD 映射
以file写入过程为例,对数据操作流程进行说明。
为简化说明,便于理解,此处进行若干假定。首先,假定待写入的file较小,无需切分,仅被映射为一个object。其次,假定系统中一个PG被映射到3个OSD上。
基于上述假定,则file写入流程可以被下图表示:
如图所示,当某个client需要向Ceph集群写入一个file时,首先需要在本地完成上面叙述的寻址流程,将file变为一个object,然后找出存储该object的一组三个OSD。这三个OSD具有各自不同的序号,序号最靠前的那个OSD就是这一组中的Primary OSD,而后两个则依次是Secondary OSD和Tertiary OSD。
找出三个OSD后,client将直接和Primary OSD通信,发起写入操作(步骤1)。Primary OSD收到请求后,分别向Secondary OSD和Tertiary OSD发起写入操作(步骤2、3)。当Secondary OSD和Tertiary OSD各自完成写入操作后,将分别向Primary OSD发送确认信息(步骤4、5)。当Primary OSD确信其他两个OSD的写入完成后,则自己也完成数据写入,并向client确认object写入操作完成(步骤6)。
之所以采用这样的写入流程,本质上是为了保证写入过程中的可靠性,尽可能避免造成数据丢失。同时,由于client只需要向Primary OSD发送数据,因此,在Internet使用场景下的外网带宽和整体访问延迟又得到了一定程度的优化。
这种可靠性机制必然导致较长的延迟,特别是,如果等到所有的OSD都将数据写入磁盘后再向client发送确认信号,则整体延迟可能难以忍受。因此,Ceph可以分两次向client进行确认。当各个OSD都将数据写入内存缓冲区后,就先向client发送一次确认,此时client即可以向下执行。待各个OSD都将数据写入磁盘后,会向client发送一个最终确认信号,此时client可以根据需要删除本地数据。
与文件类似,ceph也使用字符串对对象进行标识,使用命名空间对每个对象归属的作用域进行限制和隔离。因为对象必须通过PG间接地 归属于某个存储池,所以还必须记忆所归属的存储池 标识。
ceph具有数据备份功能和数据回滚功能。
ceph引以为傲的自动数据恢复和平衡功能,用于守护数据正确性与一致性的Scrub机制,都依赖于“可以通过某种手段不重复地遍历PG中所有对象”。一般典型的应用中,为了保证对象的唯一性一般会使用比较长的文件名(例如:rbd_data.75b86e238e1f29.00000000000050),所以若采用字符串比较来实现对象排序效率是十分低下的。
RADOS采用的是32位(根据内存消耗和比较效率选出)的哈希,使用命名空间加上对象名作为哈希输入。
至此,我们得到对象标识完整的特征值:对象名,键(作用与对象名类型,目前暂未使用),命名空间,对象归属存储池标识,哈希,快照标识,分片标识,版本号
基于对象的特征值可以定义对象排序所依赖的比较算法如下:
Ceph通过C/S模式实现外部应用程序与RADOS集群之间的交互。应用程序通过客户端访问集群时,首先由客户端负责生成一个字符串形式的对象名,然后基于对象名(命名空间)生成32位的哈希值。针对此哈希值,通过简单的数学运算,例如对存储池的PG数(pg_num)取模,可以得到一个PG在存储池内部的编号,加上对象本身已经记录的存储池编号,即可得到负责承载该对象的PG。
为什么要引入stable_mod呢? --主要是为PG分裂做铺垫,当PG分裂时,老PG上的对象需要向新PG上迁移,我们需要尽可能的减少对象的移动,以避免长时间的业务停顿。此时就需要保证“同一个PG内的对象的哈希值有尽可能的多,相同的低位”。
假定标识为1的存储池中的某个对象,哈希值为0x4979FA12并且该存储池的pg_num为256。
0x4979FA12 mod 256 = 18
因此该对象由存储池1中的18号PG负责承载,并且完整的PGID为1.18。以下对象也会映射到1.18PG上。
0x4979FB12 mod 256 = 18
0x4979FC12 mod 256 = 18
0x4979FD12 mod 256 = 18
在上面这个例子中,我们仅仅使用了32位哈希的后8位(二进制),如果我们将pg_num写成2n的形式(256=28),我们很容易验证**此时(pg_num是2的整数幂时)归属同一PG的对象的低n位都是相同的。**我们将2n-1称为pg_num的掩码,其中n为掩码的位数。
如果pg_num不是2的整数次幂,此时其最高位n,此时普通的取模无法保证“归属同一PG的对象的低n位都是相同的”这一特性,因此我们对普通取模进行改进,以保证该特性。一种方案是通过掩码代替取模操作,例如pg_num的最高位为n,其掩码为2n-1。映射时执行hash & (2n-1)即可。但是这种方式会产生空穴,即将一些对象映射到了不存在的PG上面。如下图,此时pg_num=12,n=4,执行hash & (2n-1)会产生0-15共计16中结果。实际12-15并不存在。
因此我们退而其次,**如果pg_num不是2的整数次幂,我们只要保证同一PG上面的对象的低n-1位时相同的即可。**改进后的映射方法称为stable_mod:
if ((hash & (2^n - 1)) < pg_num)
return (hash & (2^n -1));
else
return (hash & (2^(n-1) -1))
利用stable_mod映射结果如下:
无论pg_num是否为2的整数次幂,采用stable_mod的方法都可以产生一个相对有规律的对象到PG的映射结果,这是PG分裂的重要理论基础。
PG增加之后带来两个问题:1. 对象需要重新映射,老PG上的对象向新PG上迁移。
2. 由上面学习我们知道,RADOS会使用CRUSH规则根据pid为PG分配OSD,若pid发生变化,那么集群会大规模的迁移。
我们以pg_num=24 -> pg_num=26为例,老PG中的对象的哈希低4位一定是相同的。扩容之后n=6,所以我们只需要关注对象哈希值的低6位。我们关注其中一个PG,假定此PGID=Y.X,其中X=0bX3X2X1X0,那么此PG内的对象可以分为4类:
0 | 0 | X3 | X2 | X1 | X0 |
0 | 1 | X3 | X2 | X1 | X0 |
1 | 0 | X3 | X2 | X1 | X0 |
1 | 1 | X3 | X2 | X1 | X0 |
依次针对这4种类型的对象PG(Y.X)使用新的pg_num(26)执行stable_mod(只写了6位)
可见,由于存储池的pg_num发生了变化,仅有第一种的对象(X5X4=00)使用stable_mod仍然映射到老PG上,其他的三种类型并无实际的PG对应,我们创建3个新的PG来转移来自老PG中的对象。
针对所有的老PG重复上述过程,最终可以将存储池的PG数量调整为原来的4倍(24 -> 26)。又因为在调整PG数量的过程中,我们总是基于老PG(称为祖先PG)产生新的孩子PG,并且新的孩子PG中的最初的对象全部来源于laoPG,所以这个过程被形象的称为PG的分裂。
在FileStore的实现中,由于PG对应一个文件目录,其下的对象全部使用文件保存,并对目录下的文件进行分层管理,使用对象哈希值逆序之后作文目录分层的依据。(目录结构详情见《ceph之RADOS设计原理与实现》P15)
此时可以不用移动对象,而是直接修改文件夹的归属,即可完成对象在新老PG之间的迁移。 – 解决了问题1。
引入PG分裂之后,如果仍然使用PGID作为CRUSH输入,据此计算新增孩子PG在OSD之间的映射结果,由于此时每个PG的PGID都不相同,必然触发大量新增孩子PG在OSD之间的迁移。一个好的办法是让同一个祖先诞生的所有孩子PG与其保持相同的副本分布,这样分裂之后集群的PG分布仍然是均衡的。为此,每个存储池除了记录当前的PG数量之外,为了应对PG分裂,还需要额外记录分裂之前祖先PG的数量,我们称它为PGP数量(pgp_num)。
最终,利用CRUSH执行PG映射时,我们不是直接使用PGID,而是先PGID对pgp_num执行stable_mod,再与存储池标识一起哈希之后作为CRUSH的特征输入,即可保证每个孩子PG与祖先产生相同的CRUSH计算结果(因为他们两个的输入完全相同),进而保证两者产生相同的副本分布。
Luminous将默认的本地对象存储引擎切换为BlueStore,此时同一个OSD下所有对象都共享一个扁平寻址空间,所以PG分裂时,甚至不存在上述对象在新老文件夹之间转移的过程,因而更加高效。