Ceph 的新 Monitor 变更

joao

早在 2012 年 5 月,在离开里斯本后在几架飞机上度过无数小时之后,我抵达洛杉矶,与 Inktank 的大多数人会面。 在逗留期间,我有机会与团队中的每个人见面,参加公司的启动派对,并开始对 Ceph Monitor 的一些关键方面进行重大且当之无愧的重构。 这些更改已合并到 Ceph 的 v0.58 版本中。

在深入了解这些更改的细节之前,让我先介绍一下 Monitor 的工作原理。

Monitor 架构

如你可能已经知道,Monitor 是任何 Ceph 集群中的关键组成部分:如果没有至少一个 Monitor,集群将无法执行任何有用的操作。 我指的是什么都不会发生。 永远。

将 Monitor 视为集群中跟踪其他组件的位置和发生情况的中央组件。 通过单个 Monitor,客户端可以获得剩余 Monitor 的位置、对象存储守护进程 (OSD) 或元数据服务器 (MDS) 的位置,或者确定数据所在的位置;OSD 和 MDS 将向 Monitor 报告。

Monitor 跟踪集群运行所必需的大量信息,其中大部分信息最终由系统中的其他组件提供。 其中一些信息以地图的形式存储——OSDMap 和 PGMap,仅举例说明——每个地图可能有多个版本。 例如,OSDMap 包含 OSD 的位置、CRUSH 映射和许多统计信息;PGMap 跟踪 PG 及其在任何给定时刻的位置,不同的版本提供不同的集群历史见解。 因此,人们可能会考虑在同一个集群中拥有多个 Monitor,不仅是为了保证 Monitor 数据存储发生可怕故障时信息的冗余,而且是为了保证 Monitor 发生故障时的可用性(例如,Monitor 服务器或机架上的电源或网络故障)。

但是,保持多个 Monitor 意味着信息必须由它们全部平等地共享。 任何潜在的不一致性,无论是丢失还是损坏的版本,都可能导致不正确的集群行为甚至数据丢失。 为了在 Monitor 集群中强制执行一致性要求,Ceph 采用 Paxos (http://en.wikipedia.org/wiki/Paxos_(computer_science)),一种分布式共识算法。 每次修改地图时,都会创建一个新版本,并通过 Monitor 的法定数量运行。 当大多数 Monitor 确认更改时,并且只有那时,新版本才会被视为已提交。 在文档和邮件列表存档中,人们可以找到许多保持多个 Monitor(并且数量为奇数)的原因,但我认为 Mike Lowe 在一封邮件中描述得最好

(http://lists.ceph.com/pipermail/ceph-users-ceph.com/2013-February/000224.html)

“想象一下你是一个黑帮老大,Monitor 是你的会计师。 虽然你可能知道你藏匿非法所得的所有账户号码,但只有你的会计师知道这些账户号码属于哪家银行。 如果你或别人干掉了你唯一的会计师,你的钱就没了。 哦,你的会计师可能会对你撒谎,所以最好有奇数个会计师,让多数人统治。”

架构重构

Monitor 有三个主要的架构重构,我将在接下来的章节中详细解释

  • 将数据存储从遗留的文件系统转移到键/值存储;
  • 引入单个 Paxos 实例,而不是每个 Monitor 服务一个实例;以及,
  • 执行全集群同步以赶上集群状态

使用键/值存储而不是遗留的文件系统

直到 v0.58,Monitor 的数据存储由一组文件和目录组成。 这种方法受益于使用“ls”、“cat”等工具检查数据的简单性。 然而,这种简单性也会促使一些创造性的解决问题方法,我们时不时会遇到有人因为篡改 monmap 版本而导致 Monitor 崩溃的情况。 让我明确一点:将 Monitor 屏蔽起来不是为了避免这种方法——一旦用户理解了不应该这样做,他们就不会再重复这种行为——但屏蔽确实提供了其他更重要的好处。

基于文件的 Monitor 存储还有一些无法通过简单地告知用户来避免的缺点。 例如,无法原子地更改一组文件。 这个问题在文件系统中并不常见,并且已经开发了几种技术来规避它,但它们很麻烦,并且在某些情况下它们实际上无法正常工作。 举例说明 Monitor 如何将其数据存储应用到版本 V,即存储中最新的已提交版本;在 foo/V+1 下创建一个包含新版本内容的文件;将 V+1 写入最新的已提交版本文件。 现在假设在将新版本的内容写入磁盘时,存储空间不足。 有可能只有部分内容最终到达磁盘,我们可能会得到一个损坏的版本。 你会说:“但是我们没有将该版本标记为最后一个已提交版本,所以没问题,对吧?” 当然,在某种程度上是正确的。 真正的情况是,在恢复期间,Monitor 可能会检查存储中是否有未提交的版本,如果有,则尝试通过 Paxos 运行它,在这种情况下,该版本可能会损坏。 因此,有人会说这是一个错误,是存储的错误,当然这是正确的:可以通过在写入磁盘之前存储版本的 crc 来避免它,并且我们可以在使用它之前检查 crc 是否与读取的版本匹配。

当然,我们可以继续使用基于文件的存储,根据需要添加功能,如果不是因为我们即将对 Monitor 进行重大重构,我们可能也会这样做。 因此,与其专注于扩展现有的基于文件的存储,我们决定是时候迁移到具有我们正在寻找的所有属性的键/值存储了,并且由于我们已经在 Ceph 中使用过这样的存储,我们直接使用了 leveldb (http://code.google.com/p/leveldb/)。

事实上,遗留的基于文件存储在数据放置方面很像一个键/值存储。 它主要由包含数据的文件组成,文件名充当键,数据充当值。 因此,迁移到键/值存储并没有带来太大的困难,并且它让我们期待在 Monitor 的其余架构重构中使用:事务,能够在一个原子批处理中执行多个修改操作。

Paxos 与 Monitor 服务

Figure 1

图 1

我们已经讨论了 Monitor 如何通过使用 Paxos 来维护集群中的地图一致性,但我们没有详细说明。 抛开令人费解的细节,事实上 Monitor 可以被视为分为 6 个服务,每个服务负责处理一种类型的信息:身份验证、日志记录、MDS、Monitor、PG 和 OSD 地图。 这些服务中的每一个都是我们所说的“Paxos 服务”,因为它们几乎表现为 Paxos 机器,每个机器都维护一个 Paxos 实例(参见图 1)。 这意味着在任何给定时刻,理论上都可以并行进行 6 次修改,前提是每次修改的类型都不同。 在现实中,它们并不是真正并行的,因为 Monitor 一次只能处理一条消息,但可以保持多个并发的 Paxos 提案。

每个服务拥有一个 Paxos 实例,保证每个服务将跟踪自己的版本,并负责维护可能因不同要求和标准而异的版本。 基本上,这种方法赋予了每个服务很大的自主权,但代价是冗余,因为当只有一个 Paxos 实例就足够时,却有多个 Paxos 实例。 在图 1 中,我们大致描述了每个服务如何执行其在 Monitor 数据存储上的读取和写入操作。 简而言之,大多数修改都将通过其 Paxos 实例进行,而读取将由服务直接执行。 我们说大多数修改是因为我们只会使用 Paxos 来处理集群中的新版本。 还有其他一些修改将直接在存储上完成,只要它们被认为不会影响全局 Paxos 状态,例如版本修剪(即删除旧的、不必要的版本)。

抛开概念架构,从实现的角度来看,每个服务在访问自己的数据时也需要付出很多努力,因为它们在某种程度上需要显式地使用基于文件的 Monitor 存储接口来访问文件系统中的分配命名空间。 几乎没有提供抽象。

这使我们达到了架构重构的最终目标:使用所有服务的单个 Paxos 实例,同时保持它们的自主性并使用干净且易于使用的接口将它们的存储访问隔离到自己的命名空间。

一个 Paxos 统治它们

Figure 2

图 2

虽然使用单个 Paxos 实例是有道理的,但它涉及对服务如何看待其世界以及 Paxos 在 Monitor 中如何使用的重大重构。

与其坚持以前使用 Paxos 仅将特定服务的版本运行到集群中的其他 Monitor 的方法,我们现在使用它来执行集群中的任何更改,从而保证所有 Monitor 始终保持同步——这意味着修剪也强制同时在整个集群中发生。 因此,使用单个 Paxos 方法,我们确保每个写入操作在应用于存储之前都将通过 Paxos 运行,尽管服务可以直接读取存储(参见图 2)。

这种方法提出了一个主要挑战:鉴于 Paxos 版本(这意味着,例如,地图纪元)直接与服务版本相关联,从 [1,n] 递增地进行,我们现在如何处理它,因为我们只有一个 Paxos 实例? 我们最终会得到地图纪元中的差距吗? 如果这是一个标题,我可以很容易地引用 Betteridge 的标题定律 (http://en.wikipedia.org/wiki/Betteridge’s_law_of_headlines);既然不是,我只能回答“不!”并解释原因。

在将 Paxos 与服务分离时,Paxos 的版本变成了全局版本,代表给定的提案版本而不是地图纪元。 服务保留了管理自身版本的责任,并且完全不知道只有一个 Paxos 实例——它们真的不在乎,它们只是将更改推送到链条上,并将其提交给集群。 Paxos 也是如此。 通过利用新的键/值存储的事务能力,我们不仅能够抽象服务与 Paxos 处理版本的方式,还能够抽象 Paxos 与服务提出的内容,这在以前是不可能的——Paxos/服务关系非常紧密,Paxos 提案采用“服务 baz 的版本 foo 内容 bar”的形式。

Figure 3

图 3

然而,借助事务的支持,我们可以让服务生成一个包含其要在命名空间上执行的操作的事务——该事务将被正确调整以反映服务的命名空间,而服务无需知晓。然后,该事务将被编码成一个字节数组(Ceph 拥有所有允许这样做的所有数据结构),并提交给 Paxos。参见图 3,我们在此描绘了这个过程。一旦服务的事务到达 Paxos,将创建一个新的事务,反映新的 Paxos 版本。在图 3 中,我们可以看到 Paxos 将创建一个新的版本 42,其中包含服务编码事务的内容——Paxos 不会关心内容到底是什么;从它的角度来看,它们是无意义的。一旦提议被大多数监控节点确认,每个监控节点将执行单个事务,该事务包含 Paxos 事务的操作和服务的事务的操作——所有这些都以单个原子批处理方式应用。

这种方法也用于几乎所有需要在集群中以一致方式应用的操作。例如,过去我们让每个服务在每个监控节点上决定何时修剪其版本,现在我们将该决定委托给监控仲裁中的 Leader。定期地,Leader 将评估哪些版本,无论是 Paxos 版本还是服务特定的版本,需要被修剪,生成一个包含 Paxos 版本上 erase() 操作的事务,以及服务特定的版本(如果有的话)。与其它修改类似,此事务通过 Paxos 提出,Paxos 将创建一个包含编码的提议事务的新版本,最终将其应用于整个集群。

您可能已经注意到,我们刚刚声明版本修剪也是一个 Paxos 提议,它将导致一个新的 Paxos 版本。好吧,这是有意为之的,并且在恢复漂移的监控节点时非常有用。

如果监控节点落后于给定的 Paxos 版本数,则认为该监控节点已漂移。如果该数字足够小,以至于其最后提交的版本在剩余集群监控节点上的可用版本范围内,那么该监控节点可以通过获取缺失的 Paxos 版本并在存储上重新应用它们来轻松恢复——其中一些版本可以简单地向存储添加新信息,或者擦除旧版本;无论如何,该监控节点将获得与剩余集群一致的状态。

但是,有时可能会发生监控节点漂移过多,不再与剩余集群共享任何 Paxos 版本的情况。此时,监控节点必须执行全存储同步。

全存储同步

在 v0.58 之前,当 Paxos 服务漂移超过给定的版本数时,将触发一种称为 slurp 的机制。简而言之,该机制包括与仲裁 Leader 建立连接并获取 Leader 拥有的每个服务的每个版本,该服务已漂移。这种方法对于每个 Paxos 对应一个服务的架构来说是足够的,但对于单个 Paxos 架构来说,效果不会很好。原因很简单,并且遵循前一节中描述的 Paxos 的行为:Paxos 版本不再代表服务版本,并且仅同步它们肯定会导致状态损坏,并且会丢失大量信息。

所以我们去掉了 slurp。相反,我们利用 leveldb 的快照和迭代器,现在执行全存储同步。这意味着一旦监控节点(以下称为 Requester)发现它已经漂移到无法挽救的程度,它将请求另一个监控节点(以下称为 Provider)执行同步。Provider 然后将拍摄其存储的快照并对其进行迭代,将找到的所有键/值捆绑到事务中并将其发送到 Requester。Requester 将应用每个收到的事务,并且一旦收到最后一个块,它将准备好加入集群。

这个新机制的好处是,与 slurp 不同,Requester 实际上不需要直接从仲裁的 Leader 同步。相反,它可以从仲裁中的任何给定监控节点同步,并且可以同时执行任何给定数量的同步,而不会使 Leader 过载。

但是,但是……升级复杂吗?可以回滚吗?

好吧……不,有点。

在 Bobtail 左右的时间,监控节点开始为服务通过其 Paxos 实例提出的每个版本记录一个全局版本。运行一段时间后,监控节点将持有从服务特定 Paxos 版本到全局版本的映射,然后将在其存储上设置一个标志,说明我们现在可以将任何 Paxos 版本映射到全局 ID。

这被滑入监控节点是为了允许我们从每个 Paxos 对应一个服务的架构升级到单个 Paxos 架构。因此,基本上,只要运行了 Bobtail 监控节点一段时间,升级到新的监控节点应该就像重新启动它并等待存储转换完成一样简单。此转换将自动触发,如果存储足够大,可能需要一些时间。所以,不,升级并不复杂,前提是您来自 Bobtail;否则,您将需要升级到 Bobtail 并从那里开始。

如果升级由于某种原因失败,如果您能通过 邮件列表和/或 IRC 告知我们,我们将非常感激。无论如何,没有必要绝望。鉴于在转换期间,我们仅对旧的基于文件的存储执行读取操作,并且我们将所有内容转换为监控节点数据目录中的 leveldb 子目录,您可以简单地通过运行旧的监控节点来恢复到原始数据存储。但是,如果您的监控节点没有失败,如果您成功升级了所有监控节点并且它们形成了一个仲裁,那么从那时起就无法回滚(除非您愿意回滚到旧状态)。此外,您应该知道此升级不允许混合监控节点集群,因此尝试仅升级集群的一部分监控节点毫无意义:它将无法工作,因为重构后的代码无法理解重构前的工作方式。

总结

在过去的十个月里,Ceph 监控节点进行了一次重大重构,从其后端数据存储从基于文件的格式移动到支持原子事务的键/值存储,到版本创建的方式,将所有服务统一到一个 Paxos 实例下,并隔离它们对数据存储的访问。这种重构使我们能够抑制先前架构的一些限制,并创建一个通过将 Paxos 与监控服务分离,它将允许我们在集群中以无缝的方式传播信息,从而简化了如何在监控器周围构建和构建新功能的方式。监控器的未来版本可能例如包含一个通用的键/值存储,以便用户可以存储和检索必要的信息,同时受益于监控器的分布式和高可用性。目前正在开发利用现有机制的工作,将 Paxos 作为集群中修改的管道。

如果您想了解更多信息,请随时查看引入整个架构重构的补丁的提交消息(单个 Paxos通过 Paxos 修剪存储同步),深入研究 源代码,或在 邮件列表或 IRC 上与我们聊天!