监视器与Paxos,与Joao的对话
这是一个对话,Joao Luis 是 Ceph 核心开发者,他对监视器有深入的了解,他回答了 Loïc Dachary 的问题,后者正在探索 Ceph 代码库的这个领域。许多答案都包含在 Joao 的文章 Ceph 的新监视器变更 (2013年3月) 中,但角度不同。
Loic: 我的问题将从一个新接触 Ceph 监视器代码库的开发者的角度出发。我想编写单元测试。
Joao: : 我曾经提议为监视器中的 paxos 机制编写一些单元测试。但它并不像人们期望的那么简单。
Loic: 我对一件事感到困惑,如果你能先解释一下,这将有帮助。我的理解是监视器可以就给定的 MonMap 达成一致。这是正确的吗?
Joao: : 我们需要确保集群中的所有监视器都具有相同的内容,无论这些内容是什么。为了使集群正常工作,需要保持一些映射一致。不仅是 MonMap,还有 OSDmap、MDSMap、PGMap 以及一个密钥环,这些都应该散布在监视器中,以便客户端和其他守护程序进行身份验证。
Loic: 我们是试图将所有这些信息存储为一个单一的单元吗?还是针对每个映射进行 paxos 和选举?
Joao: : 选举(尽管它们依赖于修改后的 paxos 实现)基本上与其余的 paxos 机制完全不同。从监视器的角度来看,选举可以随时发生,并且仅对监视器建立基于排名的领导者有用。有一个领导者,其余的是追随者。让我们先将选举放在一边。在 cuttlefish 之前,我们曾经为每个 paxos 服务使用一个 paxos 实现,该服务负责监视器组件,该组件负责一个映射。OSDMonitor 是一个负责 OSDMap 的 paxos 服务。但是,我们可以使用单个 paxos 实现来处理多个映射,因为服务应该与 paxos 以及它们提出更改的算法无关。我们确保使用单个 paxos 实例来在集群中提出更改。
Loic: 如果我理解正确,paxos 实现的目标是监视器就一个值达成一致。那么,在这种情况下,该值是监视器中的所有信息吗?所以目标是,例如,当 MonMap 和 OSDMap 中的某些内容发生变化时,paxos 将被使用,以便这些数据块(包括这些修改)传播到所有监视器。从 paxos 的角度来看,这将是一个包含所有数据的单个值,这似乎很大。我有什么遗漏吗?
Joao: : 你假设 paxos 会与所有监视器共享所有信息,对吗?
Loic: 是的。
Joao: : 监视器拥有不需要共享的信息。paxos 用于共享任何数据块,无论其语义如何。它不关心共享什么。它只关心有一个分配了版本的数据块,它被提议给追随者。追随者将接受或忽略该版本,一旦提交,所有监视器都将在其存储中拥有该数据块。
Loic: 所以共享的是将更改数据的事务,但 paxos 却不知道它。它只是一个事务。
Joao: : 完全正确。paxos 然后将在不知道它正在应用什么的情况下将事务应用于存储。
Loic: 并且有可能这个事务是关于修改 MonMap,但它也可能是关于修改 OSDMap…… paxos 只是帮助确保它被共享。
Joao: : 完全正确。我们的 paxos 并不是一个纯粹的 paxos 实现。
Loic: 确实,在 src/mon/paxos.h 中声明它是基于 Paxos 算法,但在几个关键方面有所不同:“仅生成一个新值,简化了恢复逻辑”。这意味着什么?
Joao: : 我们的实现中的恢复逻辑试图减轻同时恢复多个版本的负担。我们提出一个版本,让追随者接受它,然后移动到下一个版本。在 ceph 中,我们只提供一个值一次。
Loic: 提出一个值意味着什么?一个值可能是 MonMap 中的一个更改吗?
Joao: : paxos 并不真正关心哪个服务提出什么,但在当前的代码库中,只有一个服务会一次提出一个。一个 OSDMap 的提议,一个 MonMap 的提议:它们没有被聚合。我们只提议一次一种组件的更改。
Loic: 另一个区别是:2- 节点跟踪“已提交”的值,并慷慨地(和信任地)共享它们。
Joao: : 在咨询 ‘git blame’ 之后,看起来实际上是我写了那段文字,但我不记得自己做过这件事了——这种情况发生的频率比我愿意承认的要高。 ![]()
它指的是在恢复期间,监视器共享它拥有的任何已提交的值,而其他监视器可能需要加入仲裁者等,并且我们会信任,当他们说他们接受了这个值时,这意味着他们实际上已经将其写入磁盘。并且,当他们声称一个值已提交时,它实际上已提交到存储中。
Loic: 最后一个要点是租赁机制:3- 内置了一个“租赁”机制,允许节点确定何时可以“读取”其最后已提交值的副本。
Joao: : 你有一个领导者,提出一个给定的 paxos 版本。其他监视器提交该版本。从那时起,它就可以从任何监视器被任何客户端读取。但是,该版本有一个生存时间。给定监视器上的读取能力有一个生存时间。假设你有三个监视器,并且客户端连接到所有这些监视器。如果其中一个监视器与其余监视器失去联系。它注定会退出仲裁者(并且无法接收新版本)。生存时间分配给给定的 paxos 版本:last_committed 版本。没有多个租赁,因为这正是它所需要的。该租赁必须由领导者刷新:如果它过期,监视器将假定它与集群的其余部分(包括领导者)失去连接。
Loic: 它会停止为客户端请求提供服务吗?
Joao: : 是的,并启动新的选举。
Loic: 优点是,当客户端连接到与仲裁者失去联系的监视器时,它将继续获取数据,但不会持续太久。
Joao: : 我相信我们依赖于租赁超时来触发选举。它也可能意味着领导者已经死亡,这也需要新的选举。现在你可以想象为什么监视器中的延迟和时钟同步如此关键。如果你有时钟偏差,这意味着你将过早或过晚过期。无论如何,它都会造成混乱和随机性,并且监视器将不断呼叫选举。
Loic: 当你说版本时,它与 epoch 有关吗?你在 ceph -s 中看到的那个?
Joao: : 映射有 epoch,值有版本。它们适用于不同的上下文。我们有一个 paxos 机制,它将提出值(一堆捆绑在事务中的修改)。它们由 paxos 服务提出(在代码中,类名以 Monitor 结尾:OSDMonitor、MonMapMonitor……):它们负责管理集群中的给定映射或信息集。当你提出时:它有一个 paxos 版本,paxos 会跟踪它的版本。但这并不意味着它与服务 epoch 之间存在一一对应关系。
Loic: 但是当服务中的某些内容发生变化时(例如 MonMapMonitor),它会转换为一个事务,该事务转换为一个 paxos 值和版本 x。当此事务提交到其他监视器时,它将更改 MonMap,因此更改 epoch。你是在说版本和 paxos 版本之间没有关系,尽管每次处理事务时都会更改 epoch?
Joao: : 假设你有两个监视器。客户端发送一个 MonMap 的增量更改,并将其转发给领导者。它基于此增量修改创建一个事务,将其编码到 bufferlist 中,并以版本 10 的形式将其提议给其他监视器。其他监视器提交该版本,并且两者都具有 paxos 版本 10。提交后,它们将解码事务并将其应用。在此事务中(在两个监视器上完全相同),你将拥有对 MonMap 的更改,包括映射的新 epoch(例如 2)。它与 paxos 版本不相关。
Loic: 谈到事务,Paxos.h 的开头有一条注释:Paxos 将把值写入磁盘,并将其与版本关联,但会更进一步:该值将被解码,并且该事务上的操作将在将编码的 bufferlist 值写入磁盘的同一事务中应用。这意味着什么?
Joao: : 我们需要在监视器上拥有 paxos 版本,以便其他监视器能够恢复。当我们提出一个新值(一个编码到 bufferlist 中的事务)时,我们必须确保它被写入磁盘,这样我们就不会丢失它。领导者将一个事务发送到其他监视器,该事务包括:paxos 版本(例如 10)、值以及更新 last_committed 版本的操作(如果 paxos 版本是 10,则为 10)。提交该版本后,监视器将基本上通过解码该值并应用它来创建一个新事务。
Loic: 我假设这个事务不是应用于与用于存储传入 paxos 版本的事务相同的存储?
Joao: : 我们只为整个监控器使用一个存储。我们基本上通过对键进行一些创造性的命名,将Paxos和各个服务划分到不同的命名空间中。当接受该值时,我们会将其写入Paxos版本键。只有在提交后,我们才会解码该值,并创建一个大型事务,该事务将同时更新Paxos的last_committed版本,并在服务侧执行事务。我们将它们捆绑在一起,因为我们在接受后将其写入磁盘,但只有在提交后才会实际更新指向该版本的last_committed指针。我相信这次通话中唯一缺少的东西是一块白板。
Loic: 是的
改变细节层级,如果你能解释Paxos在Ceph中的工作原理,将会很有用。
Joao: : 好的。基本上我们有一个领导者:他是唯一会向集群提出任何建议的人。集群中的其他监控器只会点头并将该值写入磁盘。
Loic: 为什么领导者会提出任何建议?
Joao: : 这是对监控器上某个服务的更改。OSDMap更新、MonMap更新总是发生在领导者身上。
Loic: 这意味着每当我通过命令行进行更改时,我必须与领导者通信吗?
Joao: : 它将与它连接的任何监控器通信。如果这是一个写入操作,它将被转发到领导者。领导者将通过相同的路径回复。
Loic: 所以监控器只充当代理,但最终只有领导者会进行更改。
Joao: : 是的,并且监控器将提供读取服务。这不是Paxos施加的约束。回到正题:服务提交一个由Paxos提出的更改(Paxos::begin),该值将被发送到法定数量中的所有其他监控器。收到此提议后,监控器将检查它是否以前见过该提议。如果他们没有,他们会将该值写入磁盘并向领导者发送消息,表明他们接受了该值。它还没有提交,只是写入了磁盘。如果领导者失败,可以从法定数量中的任何成员检索该值的副本。在收到大多数监控器的接受信息后,领导者将在提交自己的值后发出提交命令。然后,其他监控器可以安全地声明这个Paxos版本是最新提交的版本。如果大多数监控器没有接受该值,或者领导者失去连接,则提议可能会过期……这并不重要。该提议将被丢弃,客户端必须重新提交该更改,因为它从未被接受,就好像它从未发生过一样。如果其他监控器接受了该版本但尚未提交,即使领导者失败,在下一轮选举中,该版本将作为未提交的值被提出。
Loic: 它已经被接受了,因此…
Joao: : 它将被重新提出,因为可以安全地假设它已被大多数人接受”,我应该这样表述:“可以安全地假设它在监控器是法定数量的一部分时被接受”(法定数量反过来由大多数监控器组成)——事实上,如果我们没有从领导者那里收到提交的指示,我们就无法确定该值是否已被大多数人接受;我们只知道该值已被提出,并且如果它没有被提交,我们必须在下次形成法定数量时尝试重新提出它。我相信这部分内容在接下来的几次交互中得到了涵盖,但因此,该评论有点错误。![]()
Loic: 如果新的法定数量有一个没有接受该值的监控器会发生什么?
Joao: : 如果你假设在这个法定数量中至少有一个监控器拥有该值,它将是法定数量的第一个提议。在选举之后会发出一个恢复阶段,它允许领导者要求其他监控器上存在的任何未提交的值。因此,这些监控器将共享它们的未提交值,领导者将重新将它们提出给集群。以便它们可以被重新接受并最终提交。这样说有道理吗?
Loic: 有道理,并且由于只有在存在大多数的情况下才能接受一个值,无论选举结果如何,法定数量至少有一个监控器包含这个接受的值。
Joao: : 如果它
没有,这意味着有人篡改了集群。更改法定数量之间的监控器集合是击败该算法的唯一方法。例如,你有三个监控器,一个已经关闭,但另外两个正在进行,其中一个在提交新版本之前失败了。然后,为了不丢失集群,有人重新启动了第三个监控器,注入了一个新的MonMap……这并不是什么大问题,因为客户端最终会超时并向新的领导者重新发送更改。
Loic: 选举是如何发生的?
Joao: : 它们发生的机制与提议非常相似。监控器将触发选举(租约过期,监控器刚刚启动……)。它取决于等级:它们决定谁会被选为领导者。它基于IP地址:“IP地址越高,等级越高”,应该读作“IP:PORT组合越低,等级越高”。这意味着192.168.1.1:6789将比192.168.1.2:6789具有更高的等级,后者又将比192.168.1.2:6790具有更高的等级(第一个等级为0,作为领导者,其余等级为1和2,分别是工蜂:较低的数值等级意味着更高的等级)。
