1. Crush算法与作用
CRUSH算法,全称Controlled Replication Under Scalable Hashing (可扩大哈希下的受控复制),它是一个可控的、可扩大的、分布式的正本数据搁置算法, 通过CRUSH算法来计算数据存储地位来确定如何存储和检索数据。
保障数据分布的均衡性
让数据可能平均的分不到各个节点下面,同时让数据拜访的读写操作在各个节点和磁盘上放弃负载平衡。
集群的灵便伸缩性
能够不便疾速的减少或删除节点,对生效的节点自动检测解决,可能主动实现数据的平衡,并且尽可能少的迁徙改变数据。
反对更大规模的集群
可能做到数据分布算法保护较小的元数据与计算量, 随着集群规模的一直减少,放弃较小的数据分布式算法开销,不能呈线性增长。
2. Crush算法阐明
PG到OSD的映射的过程算法称为CRUSH 算法,它是一个伪随机的过程,能够从所有的OSD中,随机性抉择一个OSD汇合,然而同一个PG每次随机抉择的后果是不变的,本质上映射的OSD汇合是固定的。
CRUSH使Ceph客户机可能间接与OSDs通信,而不是通过集中的服务器或代理。通过算法确定的数据存储和检索办法,从而防止了单点故障、性能瓶颈和对其可伸缩性的物理限度。
Crush Map将零碎的所有硬件资源形容成一个树状构造,而后再基于这个构造依照肯定的容错规定生成一个逻辑上的树形构造,树的末级叶子节点device也就是OSD,其余节点称为bucket节点,依据物理构造形象的虚构节点,蕴含数据中心形象、机房形象、机架形象、主机形象。
3. Crush算法原理
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的签长, 权重越大的,数值越大。
- 遍历选取最高的权重:high_draw
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。
4. IO流程图
步骤:
- client连贯monitor获取集群map信息。
- 同时新主osd1因为没有pg数据会被动上报monitor告知让osd2长期接替为主。
- 长期主osd2会把数据全量同步给新主osd1。
- client IO读写间接连贯长期主osd2进行读写。
- osd2收到读写io,同时写入另外两正本节点。
- 期待osd2以及另外两正本写入胜利。
- osd2三份数据都写入胜利返回给client, 此时client io读写结束。
- 如果osd1数据同步结束,长期主osd2会交出主角色。
- osd1成为主节点,osd2变成正本。
5. Ceph 通信机制
网络通信框架三种不同的实现形式:
Simple线程模式
- 特点:每一个网络链接,都会创立两个线程,一个用于接管,一个用于发送。
- 毛病:大量的链接会产生大量的线程,会耗费CPU资源,影响性能。
Async事件的I/O多路复用模式
- 特点:这种是目前网络通信中宽泛采纳的形式。新版默认曾经应用Asnyc异步形式了。
XIO形式应用了开源的网络通信库accelio来实现
- 特点:这种形式须要依赖第三方的库accelio稳定性,目前处于试验阶段。
音讯的内容次要分为三局部:
- header //音讯头类型音讯的信封
user data //须要发送的理论数据
- payload //操作保留元数据
- middle //预留字段
- data //读写数据
- footer //音讯的完结标记
步骤:
- Accepter监听peer的申请, 调用 SimpleMessenger::add_accept_pipe() 创立新的 Pipe, 给 SimpleMessenger::pipes 来解决该申请。
- Pipe用于音讯的读取和发送。该类次要有两个组件,Pipe::Reader,Pipe::Writer用来解决音讯读取和发送。
- Messenger作为音讯的发布者, 各个 Dispatcher 子类作为音讯的订阅者, Messenger 收到音讯之后, 通过 Pipe 读取音讯,而后转给 Dispatcher 解决。
- Dispatcher是订阅者的基类,具体的订阅后端继承该类,初始化的时候通过 Messenger::add_dispatcher_tail/head 注册到 Messenger::dispatchers. 收到音讯后,告诉该类解决。
- DispatchQueue该类用来缓存收到的音讯, 而后唤醒 DispatchQueue::dispatch_thread 线程找到后端的 Dispatch 解决音讯。
6. Ceph RBD 块存储 IO流程图
osd写入过程:
- 采纳的是librbd的模式,应用librbd创立一个块设施,向这个块设施中写入数据。
- 在客户端本地通过调用librados接口,而后通过pool,rbd,object、pg进行层层映射,在PG这一层中,能够晓得数据是保留在哪三个OSD上,这三个OSD别离为主从的关系。
- 客户端与primary OSD建设SOCKET 通信,将要写入的数据传给primary OSD,由primary OSD再将数据发送给其余replica OSD数据节点。
7. Ceph 心跳和故障检测机制
问题:
故障检测时间和心跳报文带来的负载, 如何衡量升高压力?
- 心跳频率太高则过多的心跳报文会影响零碎性能。
- 心跳频率过低则会缩短发现故障节点的工夫,从而影响零碎的可用性。
故障检测策略应该可能做到:
及时性:节点产生异样如宕机或网络中断时,集群能够在可承受的工夫范畴内感知。
适当的压力:包含对节点的压力,和对网络的压力。
容忍网络抖动:网络偶然提早。
扩散机制:节点存活状态扭转导致的元信息变动须要通过某种机制扩散到整个集群。
OSD节点会监听public、cluster、front和back四个端口
- public端口:监听来自Monitor和Client的连贯。
- cluster端口:监听来自OSD Peer的连贯。
- front端口:客户端连贯集群应用的网卡, 这里长期给集群外部之间进行心跳。
- back端口:在集群外部应用的网卡。集群外部之间进行心跳。
- hbclient:发送ping心跳的messenger(送信者)。
Ceph OSD之间互相心跳检测
- 同一个PG内OSD相互心跳,他们相互发送PING/PONG信息。
- 每隔6s检测一次(理论会在这个根底上加一个随机工夫来防止峰值)。
- 20s没有检测到心跳回复,退出failure队列。
本文由mirson创作分享,如需进一步交换,请加QQ群:19310171或拜访www.softart.cn