关于java:Ceph分布式存储实践应用之深入Ceph实现原理

226次阅读

共计 4621 个字符,预计需要花费 12 分钟才能阅读完成。

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 算法原理

  1. Ceph 的存储构造

    Ceph 为了保留对象,会先构建一个池(pool),把 pool 能够比喻成一个仓库,一个新对象的保留就相似于把一个包裹放到仓库外面。

    为了更好的晋升效率,Pool 能够划分为若干的 PG(Placement Group)归置组,这相似于仓库外面有不同的储物架,所有的储物架组成了一个残缺的仓库,所有的 PG 也就构建成了一个 pool。

  2. 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 外面。

  3. 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(对象存储设备地位)

  4. 为什么须要采纳 Crush 算法

    如果把 CRUSH(PG_ID)改成 HASH(PG_ID)% OSD_NUM 是否实用?是会存在一些问题。

    1)如果挂掉一个 OSD,所有的 OSD_NUM 余数就会发生变化,之前的数据就可能须要从新打乱整顿,一个优良的存储架构该当在呈现故障时,可能将数据迁徙老本降到最低,CRUSH 则能够做到。

    2)如果减少一个 OSD, OSD_NUM 数量增大,同样会导致数据从新打乱整顿,然而通过 CRUSH 能够保障数据向新增机器平均的扩散,且不须要从新打乱整顿。

    3)如果保留多个正本,就须要可能获取多个 OSD 后果的输入,然而 HASH 形式只能获取一个,然而通过 CEPH 的 CRUSH 算法能够做到获取多个后果。

  1. Crush 算法如何实现

    每个 OSD 有不同的容量,比方是 4T 还是 800G 的容量,能够依据每个 OSD 的容量定义它的权重,以 T 为单位,比方 4T 权重设为 4,800G 则设为 0.8。

    那么如何将 PG 映射到不同权重的 OSD 下面?这里能够间接采纳 CRUSH 外面的 Straw 抽签算法,这外面的抽签是指挑取一个最长的签,而这个签值得就是 OSD 的权重。如果每次都存储在容量最大的 OSD 上,很容易将该节点塞满,这就须要采取相似随机权重的算法来做实现。

     ![file](/img/bVcQ9tP)
    

    次要步骤:

    • 计算 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 编号。

     ![file](/img/bVZkcX)
    

    假如常数 r =0,依据算法 (CRUSH_HASH & 0xFFFF) * weight 计算最大的一个 OSD,后果为 osd.1 的 0x39A00,也就是选出的第一个 OSD,而后再让 r =1,生成新的 CRUSH_HASH 随机值,获得第二个 OSD,顺次失去第三个 OSD。

4. IO 流程图

步骤:

  1. client 连贯 monitor 获取集群 map 信息。
  2. 同时新主 osd1 因为没有 pg 数据会被动上报 monitor 告知让 osd2 长期接替为主。
  3. 长期主 osd2 会把数据全量同步给新主 osd1。
  4. client IO 读写间接连贯长期主 osd2 进行读写。
  5. osd2 收到读写 io,同时写入另外两正本节点。
  6. 期待 osd2 以及另外两正本写入胜利。
  7. osd2 三份数据都写入胜利返回给 client, 此时 client io 读写结束。
  8. 如果 osd1 数据同步结束,长期主 osd2 会交出主角色。
  9. 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 写入过程:

  1. 采纳的是 librbd 的模式,应用 librbd 创立一个块设施,向这个块设施中写入数据。
  2. 在客户端本地通过调用 librados 接口,而后通过 pool,rbd,object、pg 进行层层映射, 在 PG 这一层中,能够晓得数据是保留在哪三个 OSD 上,这三个 OSD 别离为主从的关系。
  3. 客户端与 primary OSD 建设 SOCKET 通信,将要写入的数据传给 primary OSD,由 primary OSD 再将数据发送给其余 replica OSD 数据节点。

7. Ceph 心跳和故障检测机制

问题:

故障检测时间和心跳报文带来的负载, 如何衡量升高压力?

  1. 心跳频率太高则过多的心跳报文会影响零碎性能。
  2. 心跳频率过低则会缩短发现故障节点的工夫,从而影响零碎的可用性。

故障检测策略应该可能做到:

及时性:节点产生异样如宕机或网络中断时,集群能够在可承受的工夫范畴内感知。

适当的压力:包含对节点的压力,和对网络的压力。

容忍网络抖动:网络偶然提早。

扩散机制:节点存活状态扭转导致的元信息变动须要通过某种机制扩散到整个集群。

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

正文完
 0