怎么分析Ceph的工作原理及流程

44次阅读
没有评论

今天就跟大家聊聊有关怎么分析 Ceph 的工作原理及流程,可能很多人都不太了解,为了让大家更加了解,丸趣 TV 小编给大家总结了以下内容,希望大家根据这篇文章可以有所收获。

  下面将首先介绍 RADOS 中最为核心的、基于计算的对象寻址机制,然后说明对象存取的工作流程,之后介绍 RADOS 集群维护的工作过程,最后结合 Ceph 的结构和原理对其技术优势加以回顾和剖析。

5.1  寻址流程

 Ceph 系统中的寻址流程如下图所示 [1]。

怎么分析 Ceph 的工作原理及流程
  上图左侧的几个概念说明如下:

 File —— 此处的 file 就是用户需要存储或者访问的文件。对于一个基于 Ceph 开发的对象存储应用而言,这个 file 也就对应于应用中的“对象”,也就是用户直接操作的“对象”。

 Ojbect —— 此处的 object 是 RADOS 所看到的“对象”。Object 与上面提到的 file 的区别是,object 的最大 size 由 RADOS 限定(通常为 2MB 或 4MB),以便实现底层存储的组织管理。因此,当上层应用向 RADOS 存入 size 很大的 file 时,需要将 file 切分成统一大小的一系列 object(最后一个的大小可以不同)进行存储。为避免混淆,在本文中将尽量避免使用中文的“对象”这一名词,而直接使用 file 或 object 进行说明。

 PG(Placement Group)—— 顾名思义,PG 的用途是对 object 的存储进行组织和位置映射。具体而言,一个 PG 负责组织若干个 object(可以为数千个甚至更多),但一个 object 只能被映射到一个 PG 中,即,PG 和 object 之间是“一对多”映射关系。同时,一个 PG 会被映射到 n 个 OSD 上,而每个 OSD 上都会承载大量的 PG,即,PG 和 OSD 之间是“多对多”映射关系。在实践当中,n 至少为 2,如果用于生产环境,则至少为 3。一个 OSD 上的 PG 则可达到数百个。事实上,PG 数量的设置牵扯到数据分布的均匀性问题。关于这一点,下文还将有所展开。

 OSD —— 即 object storage device,前文已经详细介绍,此处不再展开。唯一需要说明的是,OSD 的数量事实上也关系到系统的数据分布均匀性,因此其数量不应太少。在实践当中,至少也应该是数十上百个的量级才有助于 Ceph 系统的设计发挥其应有的优势。

 Failure domain —— 这个概念在论文中并没有进行定义,好在对分布式存储系统有一定概念的读者应该能够了解其大意。

  基于上述定义,便可以对寻址流程进行解释了。具体而言,Ceph 中的寻址至少要经历以下三次映射:

 (1)File – object 映射

  这次映射的目的是,将用户要操作的 file,映射为 RADOS 能够处理的 object。其映射十分简单,本质上就是按照 object 的最大 size 对 file 进行切分,相当于 RAID 中的条带化过程。这种切分的好处有二:一是让大小不限的 file 变成最大 size 一致、可以被 RADOS 高效管理的 object;二是让对单一 file 实施的串行处理变为对多个 object 实施的并行化处理。

  每一个切分后产生的 object 将获得唯一的 oid,即 object id。其产生方式也是线性映射,极其简单。图中,ino 是待操作 file 的元数据,可以简单理解为该 file 的唯一 id。ono 则是由该 file 切分产生的某个 object 的序号。而 oid 就是将这个序号简单连缀在该 file id 之后得到的。举例而言,如果一个 id 为 filename 的 file 被切分成了三个 object,则其 object 序号依次为 0、1 和 2,而最终得到的 oid 就依次为 filename0、filename1 和 filename2。

  这里隐含的问题是,ino 的唯一性必须得到保证,否则后续映射无法正确进行。

 (2)Object – PG 映射

  在 file 被映射为一个或多个 object 之后,就需要将每个 object 独立地映射到一个 PG 中去。这个映射过程也很简单,如图中所示,其计算公式是:

 hash(oid) mask – pgid

  由此可见,其计算由两步组成。首先是使用 Ceph 系统指定的一个静态哈希函数计算 oid 的哈希值,将 oid 映射成为一个近似均匀分布的伪随机值。然后,将这个伪随机值和 mask 按位相与,得到最终的 PG 序号(pgid)。根据 RADOS 的设计,给定 PG 的总数为 m(m 应该为 2 的整数幂),则 mask 的值为 m-1。因此,哈希值计算和按位与操作的整体结果事实上是从所有 m 个 PG 中近似均匀地随机选择一个。基于这一机制,当有大量 object 和大量 PG 时,RADOS 能够保证 object 和 PG 之间的近似均匀映射。又因为 object 是由 file 切分而来,大部分 object 的 size 相同,因而,这一映射最终保证了,各个 PG 中存储的 object 的总数据量近似均匀。

  从介绍不难看出,这里反复强调了“大量”。只有当 object 和 PG 的数量较多时,这种伪随机关系的近似均匀性才能成立,Ceph 的数据存储均匀性才有保证。为保证“大量”的成立,一方面,object 的最大 size 应该被合理配置,以使得同样数量的 file 能够被切分成更多的 object;另一方面,Ceph 也推荐 PG 总数应该为 OSD 总数的数百倍,以保证有足够数量的 PG 可供映射。

 (3)PG – OSD 映射

  第三次映射就是将作为 object 的逻辑组织单元的 PG 映射到数据的实际存储单元 OSD。如图所示,RADOS 采用一个名为 CRUSH 的算法,将 pgid 代入其中,然后得到一组共 n 个 OSD。这 n 个 OSD 即共同负责存储和维护一个 PG 中的所有 object。前已述及,n 的数值可以根据实际应用中对于可靠性的需求而配置,在生产环境下通常为 3。具体到每个 OSD,则由其上运行的 OSD deamon 负责执行映射到本地的 object 在本地文件系统中的存储、访问、元数据维护等操作。

  和“object – PG”映射中采用的哈希算法不同,这个 CRUSH 算法的结果不是绝对不变的,而是受到其他因素的影响。其影响因素主要有二:

  一是当前系统状态,也就是在《“Ceph 浅析”系列之四——逻辑结构》中曾经提及的 cluster map。当系统中的 OSD 状态、数量发生变化时,cluster map 可能发生变化,而这种变化将会影响到 PG 与 OSD 之间的映射。

  二是存储策略配置。这里的策略主要与安全相关。利用策略配置,系统管理员可以指定承载同一个 PG 的 3 个 OSD 分别位于数据中心的不同服务器乃至机架上,从而进一步改善存储的可靠性。

  因此,只有在系统状态(cluster map)和存储策略都不发生变化的时候,PG 和 OSD 之间的映射关系才是固定不变的。在实际使用当中,策略一经配置通常不会改变。而系统状态的改变或者是由于设备损坏,或者是因为存储集群规模扩大。好在 Ceph 本身提供了对于这种变化的自动化支持,因而,即便 PG 与 OSD 之间的映射关系发生了变化,也并不会对应用造成困扰。事实上,Ceph 正是需要有目的的利用这种动态映射关系。正是利用了 CRUSH 的动态特性,Ceph 可以将一个 PG 根据需要动态迁移到不同的 OSD 组合上,从而自动化地实现高可靠性、数据分布 re-blancing 等特性。

  之所以在此次映射中使用 CRUSH 算法,而不是其他哈希算法,原因之一正是 CRUSH 具有上述可配置特性,可以根据管理员的配置参数决定 OSD 的物理位置映射策略;另一方面是因为 CRUSH 具有特殊的“稳定性”,也即,当系统中加入新的 OSD,导致系统规模增大时,大部分 PG 与 OSD 之间的映射关系不会发生改变,只有少部分 PG 的映射关系会发生变化并引发数据迁移。这种可配置性和稳定性都不是普通哈希算法所能提供的。因此,CRUSH 算法的设计也是 Ceph 的核心内容之一,具体介绍可以参考 [2]。

  至此为止,Ceph 通过三次映射,完成了从 file 到 object、PG 和 OSD 整个映射过程。通观整个过程,可以看到,这里没有任何的全局性查表操作需求。至于唯一的全局性数据结构 cluster map,在后文中将加以介绍。可以在这里指明的是,cluster map 的维护和操作都是轻量级的,不会对系统的可扩展性、性能等因素造成不良影响。

  一个可能出现的困惑是:为什么需要同时设计第二次和第三次映射?难道不重复么?关于这一点,Sage 在其论文中解说不多,而笔者个人的分析如下:

  我们可以反过来想像一下,如果没有 PG 这一层映射,又会怎么样呢?在这种情况下,一定需要采用某种算法,将 object 直接映射到一组 OSD 上。如果这种算法是某种固定映射的哈希算法,则意味着一个 object 将被固定映射在一组 OSD 上,当其中一个或多个 OSD 损坏时,object 无法被自动迁移至其他 OSD 上(因为映射函数不允许),当系统为了扩容新增了 OSD 时,object 也无法被 re-balance 到新的 OSD 上(同样因为映射函数不允许)。这些限制都违背了 Ceph 系统高可靠性、高自动化的设计初衷。

  如果采用一个动态算法(例如仍然采用 CRUSH 算法)来完成这一映射,似乎是可以避免静态映射导致的问题。但是,其结果将是各个 OSD 所处理的本地元数据量爆增,由此带来的计算复杂度和维护工作量也是难以承受的。

  例如,在 Ceph 的现有机制中,一个 OSD 平时需要和与其共同承载同一个 PG 的其他 OSD 交换信息,以确定各自是否工作正常,是否需要进行维护操作。由于一个 OSD 上大约承载数百个 PG,每个 PG 内通常有 3 个 OSD,因此,一段时间内,一个 OSD 大约需要进行数百至数千次 OSD 信息交换。

  然而,如果没有 PG 的存在,则一个 OSD 需要和与其共同承载同一个 object 的其他 OSD 交换信息。由于每个 OSD 上承载的 object 很可能高达数百万个,因此,同样长度的一段时间内,一个 OSD 大约需要进行的 OSD 间信息交换将暴涨至数百万乃至数千万次。而这种状态维护成本显然过高。

  综上所述,笔者认为,引入 PG 的好处至少有二:一方面实现了 object 和 OSD 之间的动态映射,从而为 Ceph 的可靠性、自动化等特性的实现留下了空间;另一方面也有效简化了数据的存储组织,大大降低了系统的维护管理开销。理解这一点,对于彻底理解 Ceph 的对象寻址机制,是十分重要的。

5.2  数据操作流程

  此处将首先以 file 写入过程为例,对数据操作流程进行说明。

  为简化说明,便于理解,此处进行若干假定。首先,假定待写入的 file 较小,无需切分,仅被映射为一个 object。其次,假定系统中一个 PG 被映射到 3 个 OSD 上。

  基于上述假定,则 file 写入流程可以被下图表示 [3]:

怎么分析 Ceph 的工作原理及流程

  如图所示,当某个 client 需要向 Ceph 集群写入一个 file 时,首先需要在本地完成 5.1 节中所叙述的寻址流程,将 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 可以根据需要删除本地数据。

  分析上述流程可以看出,在正常情况下,client 可以独立完成 OSD 寻址操作,而不必依赖于其他系统模块。因此,大量的 client 可以同时和大量的 OSD 进行并行操作。同时,如果一个 file 被切分成多个 object,这多个 object 也可被并行发送至多个 OSD。

  从 OSD 的角度来看,由于同一个 OSD 在不同的 PG 中的角色不同,因此,其工作压力也可以被尽可能均匀地分担,从而避免单个 OSD 变成性能瓶颈。

  如果需要读取数据,client 只需完成同样的寻址过程,并直接和 Primary OSD 联系。目前的 Ceph 设计中,被读取的数据仅由 Primary OSD 提供。但目前也有分散读取压力以提高性能的讨论。

5.3  集群维护

  前面的介绍中已经提到,由若干个 monitor 共同负责整个 Ceph 集群中所有 OSD 状态的发现与记录,并且共同形成 cluster map 的 master 版本,然后扩散至全体 OSD 以及 client。OSD 使用 cluster map 进行数据的维护,而 client 使用 cluster map 进行数据的寻址。

  在集群中,各个 monitor 的功能总体上是一样的,其相互间的关系可以被简单理解为主从备份关系。因此,在下面的讨论中不对各个 monitor 加以区分。

  略显出乎意料的是,monitor 并不主动轮询各个 OSD 的当前状态。正相反,OSD 需要向 monitor 上报状态信息。常见的上报有两种情况:一是新的 OSD 被加入集群,二是某个 OSD 发现自身或者其他 OSD 发生异常。在收到这些上报信息后,monitor 将更新 cluster map 信息并加以扩散。其细节将在下文中加以介绍。

 Cluster map 的实际内容包括:

 (1)Epoch,即版本号。Cluster map 的 epoch 是一个单调递增序列。Epoch 越大,则 cluster map 版本越新。因此,持有不同版本 cluster map 的 OSD 或 client 可以简单地通过比较 epoch 决定应该遵从谁手中的版本。而 monitor 手中必定有 epoch 最大、版本最新的 cluster map。当任意两方在通信时发现彼此 epoch 值不同时,将默认先将 cluster map 同步至高版本一方的状态,再进行后续操作。

 (2)各个 OSD 的网络地址。

 (3)各个 OSD 的状态。OSD 状态的描述分为两个维度:up 或者 down(表明 OSD 是否正常工作),in 或者 out(表明 OSD 是否在至少一个 PG 中)。因此,对于任意一个 OSD,共有四种可能的状态:

 —— Up 且 in:说明该 OSD 正常运行,且已经承载至少一个 PG 的数据。这是一个 OSD 的标准工作状态;

 —— Up 且 out:说明该 OSD 正常运行,但并未承载任何 PG,其中也没有数据。一个新的 OSD 刚刚被加入 Ceph 集群后,便会处于这一状态。而一个出现故障的 OSD 被修复后,重新加入 Ceph 集群时,也是处于这一状态;

 —— Down 且 in:说明该 OSD 发生异常,但仍然承载着至少一个 PG,其中仍然存储着数据。这种状态下的 OSD 刚刚被发现存在异常,可能仍能恢复正常,也可能会彻底无法工作;

 —— Down 且 out:说明该 OSD 已经彻底发生故障,且已经不再承载任何 PG。

 (4)CRUSH 算法配置参数。表明了 Ceph 集群的物理层级关系(cluster hierarchy),位置映射规则(placement rules)。

  根据 cluster map 的定义可以看出,其版本变化通常只会由(3)和(4)两项信息的变化触发。而这两者相比,(3)发生变化的概率更高一些。这可以通过下面对 OSD 工作状态变化过程的介绍加以反映。

  一个新的 OSD 上线后,首先根据配置信息与 monitor 通信。Monitor 将其加入 cluster map,并设置为 up 且 out 状态,再将最新版本的 cluster map 发给这个新 OSD。

  收到 monitor 发来的 cluster map 之后,这个新 OSD 计算出自己所承载的 PG(为简化讨论,此处我们假定这个新的 OSD 开始只承载一个 PG),以及和自己承载同一个 PG 的其他 OSD。然后,新 OSD 将与这些 OSD 取得联系。如果这个 PG 目前处于降级状态(即承载该 PG 的 OSD 个数少于正常值,如正常应该是 3 个,此时只有 2 个或 1 个。这种情况通常是 OSD 故障所致),则其他 OSD 将把这个 PG 内的所有对象和元数据复制给新 OSD。数据复制完成后,新 OSD 被置为 up 且 in 状态。而 cluster map 内容也将据此更新。这事实上是一个自动化的 failure recovery 过程。当然,即便没有新的 OSD 加入,降级的 PG 也将计算出其他 OSD 实现 failure recovery。

  如果该 PG 目前一切正常,则这个新 OSD 将替换掉现有 OSD 中的一个(PG 内将重新选出 Primary OSD),并承担其数据。在数据复制完成后,新 OSD 被置为 up 且 in 状态,而被替换的 OSD 将退出该 PG(但状态通常仍然为 up 且 in,因为还要承载其他 PG)。而 cluster map 内容也将据此更新。这事实上是一个自动化的数据 re-balancing 过程。

  如果一个 OSD 发现和自己共同承载一个 PG 的另一个 OSD 无法联通,则会将这一情况上报 monitor。此外,如果一个 OSD deamon 发现自身工作状态异常,也将把异常情况主动上报给 monitor。在上述情况下,monitor 将把出现问题的 OSD 的状态设为 down 且 in。如果超过某一预订时间期限,该 OSD 仍然无法恢复正常,则其状态将被设置为 down 且 out。反之,如果该 OSD 能够恢复正常,则其状态会恢复为 up 且 in。在上述这些状态变化发生之后,monitor 都将更新 cluster map 并进行扩散。这事实上是自动化的 failure detection 过程。

  由之前介绍可以看出,对于一个 Ceph 集群而言,即便由数千个甚至更多 OSD 组成,cluster map 的数据结构大小也并不惊人。同时,cluster map 的状态更新并不会频繁发生。即便如此,Ceph 依然对 cluster map 信息的扩散机制进行了优化,以便减轻相关计算和通信压力。

  首先,cluster map 信息是以增量形式扩散的。如果任意一次通信的双方发现其 epoch 不一致,则版本更新的一方将把二者所拥有的 cluster map 的差异发送给另外一方。

  其次,cluster map 信息是以异步且 lazy 的形式扩散的。也即,monitor 并不会在每一次 cluster map 版本更新后都将新版本广播至全体 OSD,而是在有 OSD 向自己上报信息时,将更新回复给对方。类似的,各个 OSD 也是在和其他 OSD 通信时,将更新发送给版本低于自己的对方。

  基于上述机制,Ceph 避免了由于 cluster map 版本更新而引起的广播风暴。这虽然是一种异步且 lazy 的机制,但根据 Sage 论文中的结论,对于一个由 n 个 OSD 组成的 Ceph 集群,任何一次版本更新能够在 O(log(n)) 时间复杂度内扩散到集群中的任何一个 OSD 上。

  一个可能被问到的问题是:既然这是一种异步和 lazy 的扩散机制,则在版本扩散过程中,系统必定出现各个 OSD 看到的 cluster map 不一致的情况,这是否会导致问题?答案是:不会。事实上,如果一个 client 和它要访问的 PG 内部的各个 OSD 看到的 cluster map 状态一致,则访问操作就可以正确进行。而如果这个 client 或者 PG 中的某个 OSD 和其他几方的 cluster map 不一致,则根据 Ceph 的机制设计,这几方将首先同步 cluster map 至最新状态,并进行必要的数据 re-balancing 操作,然后即可继续正常访问。

  通过上述介绍,我们可以简要了解 Ceph 究竟是如果基于 cluster map 机制,并由 monitor、OSD 和 client 共同配合完成集群状态的维护与数据访问的。特别的,基于这个机制,事实上可以自然而然的完成自动化的数据备份、数据 re-balancing、故障探测和故障恢复,并不需要复杂的特殊设计。这一点确实让人印象深刻。

看完上述内容,你们对怎么分析 Ceph 的工作原理及流程有进一步的了解吗?如果还想了解更多知识或者相关内容,请关注丸趣 TV 行业资讯频道,感谢大家的支持。