注意

本文档适用于 Ceph 的开发版本。

监视器选举

原始算法

在历史上,监视器领导者选举非常简单:排名最低的监视器获胜!

这是通过一个低状态的“Elector”(选举器)模块实现的(尽管现在它已被拆分为一个处理消息传递的 Elector 和一个做出投票选择的 ElectionLogic)。它跟踪选举纪元(epoch)以及不多其他信息。奇数纪元是选举;偶数纪元有领导者并让监视器执行其持续工作。当超时发生或监视器请求新的选举时,我们增加纪元并向所有已知的监视器发送 Propose(提议)消息。通常,如果我们收到一条旧消息,我们会将其丢弃或触发新的选举(如果我们认为发送方是新启动的并需要加入仲裁)。如果我们收到一条来自较新纪元的消息,我们会将我们的纪元增加到匹配,然后要么 Defer(服从)于提议者,要么如果我们期望赢得他们,则再次增加纪元并自己 Propose。当我们在当前纪元内收到 Propose 时,我们要么 Defer 于发送方,要么忽略他们(如果他们的排名高于我们,或者高于我们已经 Defer 的排名,我们就忽略他们)。(请注意,如果我们排名最高,则有可能在同一选举纪元内依次 Defer 于所有其他监视器!)

这在正常情况下可以解决,因为所有监视器都同意优先级投票顺序,并且只有当监视器没有参与或看到与已知提议者有潜在冲突时,纪元才会增加。

问题

原始算法在各种网络分裂条件下根本不起作用。这种情况在实践中不常发生,但随着社区和商业供应商将 Ceph 推广到需要使用“stretch clusters”(跨地域集群)的领域,这个问题变得很重要。

新算法

我们仍然默认使用原始的(“经典”)选举算法,但支持用户通过 CLI 切换到新的算法。这些算法在 ElectionLogic 类中作为不同的函数和 switch 语句实现。

第一种算法非常简单:“disallow”(禁止)允许您将监视器添加到不允许作为领导者的列表中。第二种算法,“connectivity”(连通性),结合了连接分数评级,并选举得分最高的监视器。

算法:disallow

如果一个监视器在 disallowed 列表中,它总是服从另一个监视器,无论排名如何。否则,它与经典算法相同。由于更改 disallowed 列表需要 paxos 更新,因此一起进行选举的监视器应始终具有相同的集合。这意味着选举顺序在整个监视器集合中是恒定且静态的,并且选举可以轻松解决(假设网络是连通的)。

这个算法实际上只是作为更高级连通性模式的演示和垫脚石而存在,但它可能在非对称网络和集群中具有实用价值。

算法:connectivity

该算法将每个连接(双向,在下一节中讨论)的得分作为输入,并尝试选举总分最高的监视器。我们保留与经典算法相同的基本消息传递流程,其中选举通过对 Propose 消息的反应来驱动。但这有几个挑战,因为与排名不同,分数不是静态的(并且可能在选举期间发生变化!)。为了保证一个选举纪元不会产生多个领导者,我们必须维护两个关键的不变量:* 监视器必须在选举纪元期间保持静态分数 * 任何服从都必须是可传递的——如果 A 服从于 B,然后服从于 C,那么 B 最好也服从于 C!

我们非常明确地处理这些问题:在开始选举(或增加纪元)时,分支一个 stable_peer_tracker 拷贝的 peer_tracker 评分对象,并且如果我们当前的领导者选择不会服从于某个监视器,我们就拒绝服从它。(所有 Propose 消息都包含领导者正在使用的分数的副本,以便对等点可以评估它们。)

当然,这些修改很容易造成阻塞。为了保证向前推进,我们做了进一步的调整:* 如果我们想服从于一个新的对等点,但已经服从于一个分数不允许这样做的对等点,我们就增加选举纪元并重新开始 start() 选举。* 所有选举消息都包含发送方所知道的分数。

这保证了只要网络合理稳定(即使断开连接):只要所有分数“视图”都导致相同的服从顺序,选举就会正常完成。通过在整个监视器集合中广泛共享分数,监视器迅速收敛到全局最新状态。

与经典和 disallowed 处理程序相比,此算法还有一个重要的功能:它可以忽略仲裁外的对等点。通常,每当监视器 B 收到来自仲裁外对等点 C 的 Propose 时,B 本身会触发新的选举,以便给 C 一个加入的机会。但是因为得分最高的监视器 A 可能与 C 网络分裂,所以这是不希望的。因此,在连通性选举算法中,只有当 B 的分数表明集群会选择 A 以外的领导者时,B 才会“转发”Propose 消息。

连接评分

我们在 ConnectionTracker 类中实现评分,该类由 Elector 驱动,并作为资源提供给 ElectionLogic。Elector 负责发送 MMonPing 消息,并通过调用 report_[live|dead]_connection(带有相关的对等点和调用计数的单位时间)将结果报告给 ConnectionTracker。(这些时间单位在监视器中是秒,但 ConnectionTracker 是不可知的,我们的单元测试计算简单的时间步长。)

我们配置了一个“半衰期”,每次报告都会更新对等点的当前状态(alive 或 dead)及其总分。新分数是 current_score * (1 - units_alive / (2 * half_life)) + (units_alive / (2 * half_life))。(对于 dead 报告,我们当然是减去新的 delta,而不是加上它)。

我们可以进一步编码和解码 ConnectionTracker 进行有线传输,并接收完整的 ConnectionTracker(包含所有已知分数)或 ConnectionReport(代表单个对等点的分数)的 receive_peer_report()s 来吸收来自对等点的分数。这些分数当然都经过版本控制,因此我们不会有意外回到过去时间的危险。我们可以查询单个连接分数(如果连接断开,则为 0)或特定监视器的总分,这是来自所有其他监视器到该监视器的连接分数。

默认情况下,我们认为 ping 在 2 秒后失败(mon_elector_ping_timeout),并且每秒 ping 活动连接(mon_elector_ping_divisor)。半衰期为 12 小时(mon_con_tracker_score_halflife)。

由 Ceph 基金会为您呈现

Ceph 文档是由非营利性 Ceph 基金会 资助和托管的社区资源。如果您希望支持这项工作和我们的其他努力,请考虑 立即加入