Ceph写入文件到分布至各个osd过程分析

一、简介
本文主要讲解ceph接收客户端数据写入,然后在系统内部数据是如何进行分布的,以及分布原理简单介绍。所有介绍都基于如下图进行讲解。

ceph

上图左侧的几个概念说明如下:
File :file就是用户需要存储或者访问的实际文件。
Ojbect :object是ceph底层分布式存储系统RADOS所看到的“对象”。File写入到ceph是由object-size(4M)决定客户端实际写入的file被分片多少个rados识别的object对象。
PG:PG的用途是对object的存储进行组织和位置映射。
OSD :即客户写入的文件file分成若干个object之后,分配到不同的pg中,然后pg被分配到不同的磁盘上(冗余决定)进行实际存储,osd实际上是对物理磁盘进行管理的一个进程,也被称之为物理磁盘。
二、原理介绍
大家可以思考下,将一个file写入到实际的磁盘上一共分为几步?
根据上面的图进行分析,我认为是需要3步:file->object、object->pg、pg->osd。下面分别进行介绍。
2.1 File -> object映射
将用户实际写入的文件file,映射为RADOS能够处理的object。本质上就是将文件file按照固定对象大小(默认是4M)进行切片,类似于条带化。每一个切分后产生的object将获得唯一的oid,即object id。结合上图(oid=ino+ono)中实际分析,ino是文件file的元数据,可以简单理解为该file的唯一id。ono则是由该file切分产生的某个object的序号。而oid就是将这个序号简单加在该file id之后得到的。举例,假设一个id为filename的file被切分成了三个object,则其object序号依次为0、1和2,而最终得到的oid就依次为filename0、filename1和filename2。
2.2 Object -> PG映射
在文件file在rados底层被分为为一个或多个object之后,就需要将每个object(oid唯一)独立地映射到一个PG(作用是对object的存储进行组织和位置映射,1个pg中包含多个对象)中去。其计算公式是:
hash(oid) & mask -> pgid
首先是使用Ceph系统指定的一个静态hash函数计算oid的哈希值,将oid映射成为一个近似均匀分布的伪随机值。然后,将这个伪随机值和mask按位相与,得到最终的PG序号(pgid)。根据RADOS的设计,给定PG的总数为m(m应该为2的整数幂),则mask的值为m-1。因此,哈希值计算和按位与操作的整体结果事实上是从所有m个PG中近似均匀地随机选择一个。
2.3 PG -> OSD映射
将PG映射到数据的实际存储磁盘OSD中。如上图所示,RADOS采用一个名为CRUSH的算法,将pgid代入其中,然后得到一组共n个OSD。这n个OSD即共同负责存储和维护一个PG中的所有object。这里的n之前也介绍过,由实际环境冗余决定,若副本数为2,则这里的n为2,也就是说pg被复制2份且分别存储到由crush计算得出的2个osd中实现冗余。
三、实际介绍
3.1 ceph逻辑层
Ceph在rados上构建了一个逻辑层,那就是池(pool),用于保存对象。个人理解这个pool的作用是对底层所有存储空间在逻辑上区分开来,不同的pool存储不同类型的数据,或者说给不同类型的客户使用。然后为方便对写入到pool的对象进行管理,Pool再一次进行了细分,即将一个pool划分为若干的PG(归置组 Placement Group)也就是说所有的PG构成了一个pool。这里 ceph不需要直接对存储池pool中的object对象进行直接管理,而是管理数量更少的pg,然后由pg对object进行管理,这块不做详细介绍。
下面摘自网络:https://www.cnblogs.com/mylovelulu/p/10508904.html
重要的是要理解
现在需要解决的问题是,对象怎么知道要保存到哪个PG上,假定这里我们的pool名叫rbd,共有256个PG,给每个PG编个号分别叫做0x0, 0x1, …0xF, 0x10, 0x11… 0xFE, 0xFF。
要解决这个问题,我们先看看我们拥有什么,1,不同的对象名。2,不同的PG编号。这里就可以引入Ceph的计算方法了 : HASH。
对于对象名分别为bar和foo的两个对象,对他们的对象名(实际上文件系统大家看到的更多的就是这样10000000000.00000008的对象名)进行计算即:
HASH(‘bar’) = 0x3E0A4162
HASH(‘foo’) = 0x7FE391A0
HASH(‘bar’) = 0x3E0A4162
对对象名进行HASH后,得到了一串十六进制输出值,也就是说通过HASH我们将一个对象名转化成了一串数字,那么上面的第一行和第三行是一样的有什么意义? 意义就是对于一个同样的对象名,计算出来的结果永远都是一样的,但是HASH算法的确将对象名计算得出了一个随机数。
有了这个输出,我们使用小学就会的方法:求余数!用随机数除以PG的总数256,得到的余数一定会落在[0x0, 0xFF]之间,也就是这256个PG中的某一个:
0x3E0A4162 % 0xFF ===> 0x62
0x7FE391A0 % 0xFF ===> 0xA0
于是乎,对象bar保存到编号为0x62的PG中,对象foo保存到编号为0xA0的PG中。对象bar永远都会保存到PG 0x62中! 对象foo永远都会保存到PG 0xA0中!
现在又来了一亿个对象,他们也想知道自己会保存到哪个PG中,Ceph说:“自己算”。于是这一亿个对象,各自对自己对象名进行HASH,得到输出后除以PG总数得到的余数就是要保存的PG。求余的好处就是对象数量规模越大,每个PG分布的对象数量就越平均。所以每个对象自有名字开始,他们要保存到的PG就已经确定了。Ceph是不区分对象的真实大小内容以及任何形式的格式,只认对象名。毕竟当对象数达到百万级时,对象的分布从宏观上来看还是平均的。
这里给出更Ceph一点的说明,实际上在Ceph中,存在着多个pool,每个pool里面存在着若干的PG,如果两个pool里面的PG编号相同,Ceph怎么区分呢? 于是乎,Ceph对每个pool进行了编号,比如刚刚的rbd池,给予编号0,再建一个pool就给予编号1,那么在Ceph里,PG的实际编号是由pool_id+.+PG_id组成的,也就是说,刚刚的bar对象会保存在0.62这个PG里,foo这个对象会保存在0.A0这个PG里。其他池里的PG名称可能为1.12f, 2.aa1,10.aa1等。
3.2 ceph物理层
理解了刚刚的逻辑层,我们再看一下Ceph里的物理层,rados在下一层,也就是实际屋里服务器上的各个磁盘,通常,Ceph将一个磁盘看作一个OSD(实际上,OSD是管理一个磁盘的程序),于是物理层由若干的OSD组成,我们的最终目标是将对象保存到磁盘上,在逻辑层里,对象是保存到PG里面的,那么现在的任务就是打通PG和OSD之间的隧道。PG相当于一堆余数相同的对象的组合,PG把这一部分对象打了个包,现在我们需要把很多的包平均的安放在各个OSD上,这就是CRUSH算法所要做的事情:CRUSH计算PG->OSD的映射关系CRUSH(PG_ID) ===> OSD

首先来看我们要做什么:
把已有的PG_ID映射到OSD上,有了映射关系就可以把一个PG保存到一个磁盘上。
如果我们想保存三个副本,可以把一个PG映射到三个不同的OSD上,这三个OSD上保存着一模一样的PG内容。
再来看我们有了什么:
互不相同的PG_ID。
互不相同的OSD_ID。
每个OSD最大的不同的就是它们的容量,我们将每个OSD的容量又称为OSD的权重(weight),规定4T权重为4,800G为0.8,也就是以T为单位的值。
现在问题转化为:如何将PG_ID映射到有各自权重的OSD上?这里我直接使用CRUSH里面采取的Straw算法,翻译过来就是抽签,说白了就是挑个最长的签,这里的签指的是OSD的权重。下面既大概选择过程:

给出一个PG_ID,作为CRUSH_HASH的输入。
CRUSH_HASH(PG_ID, OSD_ID, r) 得出一个随机数draw (重点是随机数,不是HASH)。
对于所有的OSD用他们的权重乘以每个OSD_ID对应的随机数(上述步骤中替换osd_id号),得到乘积。
选出乘积最大的OSD。
这个PG就会保存到这个OSD上。

第一行,我们姑且把r当做一个常数,第一行实际上将PG_ID, OSD_ID和r一起当做CRUSH_HASH的输入,求出一个十六进制随机数输出,这和HASH(oid)完全类似,只是多了两个输入。所以需要强调的是,对于相同的三个输入,计算得出的draw的值是一定相同的。

这个draw到底有啥用?其实,CRUSH希望得到一个随机数,也就是这里的draw,然后拿这个随机数去乘以OSD的权重,这样把随机数和OSD的权重结合在一起,就得到了每个OSD的实际签长(乘积数),而且每个签都不一样长(极大概率),就很容易从中挑一个最长的。

说白了,CRUSH希望随机挑一个OSD出来,但是还要满足权重越大的OSD被挑中的概率越大,为了达到随机的目的,它在挑之前让每个OSD都拿着自己的权重乘以一个随机数,再取乘积最大的那个。若是挑选1亿次,从宏观来看,同样是乘以一个随机数,在样本容量足够大之后,这个随机数对挑中的结果不再有影响,起决定性影响的是OSD的权重,也就是说,OSD的权重越大,宏观来看被挑中的概率越大。
这里再说明下CRUSH造出来的随机数draw,前文可知,对于常量输入,一定会得到一样的输出,所以这并不是真正的随机,所以说,CRUSH是一个伪随机算法。那crush是如何解决一个PG映射到多个OSD的问题,还记得那个常量r吗?我们把r+1,再求一遍随机数,再去乘以每个OSD的权重,再去选出乘积最大的OSD,如果和之前的OSD编号不一样,那么就选中它,如果和之前的OSD编号一样的话,那么再把r+2,再次选一次,直到选出我们需要的三个不一样编号的OSD(3个副本)为止!