CRUSH算法,全称Controlled Replication Under Scalable Hashing (可扩展哈希下的受控复制),它是一个可控的、可扩展的、分布式的副本数据放置算法, 经过CRUSH算法来计算数据存储位置来肯定如何存储和检索数据。算法
保障数据分布的均衡性后端
让数据可以均匀的分不到各个节点上面,同时让数据访问的读写操做在各个节点和磁盘上保持负载均衡。缓存
集群的灵活伸缩性服务器
能够方便快速的增长或删除节点,对失效的节点自动检测处理,可以自动实现数据的均衡,而且尽量少的迁移改动数据。网络
支持更大规模的集群架构
可以作到数据分布算法维护较小的元数据与计算量, 随着集群规模的不断增长,保持较小的数据分布式算法开销,不能呈线性增加。负载均衡
PG到OSD的映射的过程算法称为CRUSH 算法,它是一个伪随机的过程,能够从全部的OSD中,随机性选择一个OSD集合,可是同一个PG每次随机选择的结果是不变的,实质上映射的OSD集合是固定的。框架
CRUSH使Ceph客户机可以直接与OSDs通讯,而不是经过集中的服务器或代理。经过算法肯定的数据存储和检索方法,从而避免了单点故障、性能瓶颈和对其可伸缩性的物理限制。异步
Crush Map将系统的全部硬件资源描述成一个树状结构,而后再基于这个结构按照必定的容错规则生成一个逻辑上的树形结构,树的末级叶子节点device也就是OSD,其余节点称为bucket节点,根据物理结构抽象的虚拟节点,包含数据中心抽象、机房抽象、机架抽象、主机抽象。分布式
Ceph的存储结构
Ceph为了保存对象,会先构建一个池(pool),把pool能够比喻成一个仓库,一个新对象的保存就相似于把一个包裹放到仓库里面。
为了更好的提高效率,Pool能够划分为若干的PG(Placement Group)归置组,这相似于仓库里面有不一样的储物架,全部的储物架组成了一个完整的仓库,全部的PG也就构建成了一个pool。
PG的分配存储
对象是如何保存至哪一个PG上?假设Pool名称为rbd,共有256个PG,每一个PG编个号分别叫作0x0, 0x1, 0x2,... 0xFF。 具体该如何分配?这里能够采用Hash方式计算。
假设有两个对象名, 分别为bar和foo的,根据对象名作Hash计算:
HASH(‘bar’) = 0x3E0A4162
HASH(‘foo’) = 0x7FE391A0
经过Hash获得一串随机的十六进制的值, 对于一样的对象名,计算出的结果可以永远保持一致,但咱们预分配的是256个PG,这就须要再进行取模处理, 所得的结果会落在【0x0,0xFF】区间:
0x3E0A4162 % 0xFF ===> 0x62
0x7FE391A0 % 0xFF ===> 0xA0
实际在Ceph中, 存在不少个Pool,每一个Pool里面存在若干个PG,若是两个Pool里面的PG编号相同,该如何标识区分?Ceph会对每一个pool再进行编号,好比上面名称为rbd的Pool,给定ID编号为0, 再新增一个Pool,ID编号设定为1,那么一个PG的实际编号是由pool_id + . + pg_id组成。由此推论, 刚才的bar对象会保存在编号为0的pool,pg编号为62里面。
OSD的分配存储
Ceph的物理层,对应的是服务器上的磁盘,Ceph将一个磁盘或分区做为OSD,在逻辑层面,对象是保存至PG内,如今须要打通PG与OSD之间的联系, Ceph当中会存在较多的PG数量,如何将PG平均分布各个OSD上面,这就是Crush算法主要作的事情: 计算PG -> OSD的映射关系。
上述所知, 主要两个计算步骤:
POOL_ID(对象池) + HASH(‘对象名称’) % pg_num(归置组)==> PG_ID (完整的归置组编号)
CRUSH(PG_ID)==> OSD (对象存储设备位置)
为何须要采用Crush算法
若是把CRUSH(PG_ID)改为 HASH(PG_ID)% OSD_NUM 可否适用? 是会存在一些问题。
1)若是挂掉一个OSD,全部的OSD_NUM 余数就会发生变化,以前的数据就可能须要从新打乱整理, 一个优秀的存储架构应当在出现故障时, 可以将数据迁移成本降到最低, CRUSH则能够作到。
2)若是增长一个OSD, OSD_NUM数量增大, 一样会致使数据从新打乱整理,可是经过CRUSH能够保障数据向新增机器均匀的扩散, 且不须要从新打乱整理。
3)若是保存多个副本,就须要可以获取多个OSD结果的输出, 可是HASH方式只能获取一个, 可是经过CEPH的CRUSH算法能够作到获取多个结果。
Crush算法如何实现
每一个OSD有不一样的容量,好比是4T仍是800G的容量,能够根据每一个OSD的容量定义它的权重,以T为单位, 好比4T权重设为4,800G则设为0.8。
那么如何将PG映射到不一样权重的OSD上面?这里能够直接采用CRUSH里面的Straw抽签算法,这里面的抽签是指挑取一个最长的签,而这个签值得就是OSD的权重。若是每次都存储在容量最大的OSD上,很容易将该节点塞满, 这就须要采起相似随机权重的算法来作实现。

主要步骤:
计算HASH: CRUSH_HASH( PG_ID, OSD_ID, r ) ==> draw
把r当作一个常数,将PG_ID, OSD_ID一块儿做为输入,获得一个HASH值。
增长OSD权重: ( draw &0xffff ) * osd_weight ==> osd_straw
将计算出的HASH值与OSD的权重放置一块儿,这样就可以获得每一个OSD的签长, 权重越大的,数值越大。
Crush目的是随机跳出一个OSD,而且要知足权重越大的OSD,挑中的几率越大,为了保障随机性,将每一个OSD的权重都乘以一个随机数也就是HASH值,再去结果最大的那个。若是样本容量足够大, 随机数对选中的结果影响逐渐变小, 起决定性的是OSD的权重,OSD的权重越大, 被挑选的几率也就越大,这样可以作到数据的有效分布。
Crush所计算出的随机数,是经过HASH得出来,这样能够保障相同的输入, 会得出一样的输出结果。 因此Crush并非真正的随机算法, 而是一个伪随机算法。
这里只是计算得出了一个OSD,在Ceph集群中是会存在多个副本,如何解决一个PG映射到多个OSD的问题?
将以前的常量r加1, 再去计算一遍,若是和以前的OSD编号不同, 那么就选取它;若是同样的话,那么再把r+2,再从新计算,直到选出三个不同的OSD编号。

假设常数r=0,根据算法(CRUSH_HASH & 0xFFFF) * weight 计算最大的一个OSD,结果为osd.1的0x39A00,也就是选出的第一个OSD,而后再让r=1, 生成新的CRUSH_HASH随机值,取得第二个OSD,依次获得第三个OSD。
步骤:
网络通讯框架三种不一样的实现方式:
Simple线程模式
Async事件的I/O多路复用模式
XIO方式使用了开源的网络通讯库accelio来实现
消息的内容主要分为三部分:
user data //须要发送的实际数据
步骤:
osd写入过程:
问题:
故障检测时间和心跳报文带来的负载, 如何权衡下降压力?
故障检测策略应该可以作到:
及时性:节点发生异常如宕机或网络中断时,集群能够在可接受的时间范围内感知。
适当的压力:包括对节点的压力,和对网络的压力。
容忍网络抖动:网络偶尔延迟。
扩散机制:节点存活状态改变致使的元信息变化须要经过某种机制扩散到整个集群。
OSD节点会监听public、cluster、front和back四个端口
Ceph OSD之间相互心跳检测
本文由mirson创做分享,如需进一步交流,请加QQ群:19310171或访问www.softart.cn