ZooKeeper Internals — ZooKeeper内部工作方式

33次阅读

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

ZooKeeper Internals 介绍原子广播保证,属性和定义领导者激活活动消息摘要比较法定人数记录开发者指南记录在正确的级别使用标准的 slf4j 成语
介绍本文档包含有关 ZooKeeper 内部工作方式的信息。到目前为止,它讨论了以下主题:
原子广播记录
原子广播 ZooKeeper 的核心是一个原子消息系统,可以使所有服务器保持同步。
保证,属性和定义 ZooKeeper 使用的消息传递系统提供的特定保证如下:
可靠的交付:如果消息 m 由一台服务器提供,它将最终由所有服务器提供。
总订单:如果一条服务器在消息 b 之前传递消息,则所有服务器将在 b 之前传送消息。如果 a 和 b 是传递的消息,则 a 将在 b 之前传递或 b 将在传递之前传递。
因果顺序:如果在 b 的发送者发送消息 a 之后发送消息 b,则必须在 b 之前订购。如果发送方在发送 b 后发送 c,则必须在 b 之后订购 c。
ZooKeeper 消息传递系统还需要高效,可靠,易于实施和维护。我们大量使用消息传递,因此我们需要系统能够每秒处理数千个请求。虽然我们可以要求至少 k + 1 个正确的服务器来发送新消息,但我们必须能够从相关故障中恢复,例如断电。当我们实施该系统时,我们几乎没有时间和很少的工程资源,因此我们需要一个工程师可以访问的协议,并且易于实现。我们发现我们的协议满足了所有这些目标。
我们的协议假设我们可以在服务器之间构建点对点 FIFO 通道。虽然类似的服务通常假设消息传递可能丢失或重新排序消息,但我们假设 FIFO 通道非常实用,因为我们使用 TCP 进行通信。具体来说,我们依赖 TCP 的以下属性:
有序传递:数据按照发送的顺序传送,只有在传送完 m 之前发送的所有邮件之后才传送消息 m。(这样做的必然结果是,如果消息 m 丢失,m 之后的所有消息都将丢失。)
关闭后没有消息:一旦 FIFO 通道关闭,将不会收到任何消息。
FLP 证明,如果可能出现故障,则无法在异步分布式系统中实现共识。为确保我们在出现故障时达成共识,我们会使用超时。但是,我们依靠活力时间而不是正确性。因此,如果超时停止工作(例如时钟故障),则消息传递系统可能会挂起,但不会违反其保证。
在描述 ZooKeeper 消息传递协议时,我们将讨论数据包,提议和消息:
数据包:通过 FIFO 通道发送的字节序列
提案:协议单位。通过与法定数量的 ZooKeeper 服务器交换数据包来商定提案。大多数提案都包含消息,但 NEW_LEADER 提案是与消息不对应的提案示例。
消息:要以原子方式广播到所有 ZooKeeper 服务器的字节序列。在提交之前提交并同意的消息。
如上所述,ZooKeeper 保证了消息的总顺序,并且它还保证了提议的总顺序。ZooKeeper 使用 ZooKeeper 事务 id(zxid)公开总排序。所有提案在提议时都会加盖 zxid,并准确反映总排序。提案将发送到所有 ZooKeeper 服务器,并在法定人数确认提案时提交。如果提案包含消息,则在提交提案时将传递消息。确认意味着服务器已将提议记录到持久存储。我们的法定人数要求任何一对仲裁必须至少有一个共同的服务器。我们通过要求所有法定人数的大小(n / 2 + 1)来确保这一点)其中 n 是组成 ZooKeeper 服务的服务器数量。
zxid 有两个部分:纪元和计数器。在我们的实现中,zxid 是一个 64 位数字。我们使用高阶 32 位用于纪元,低阶 32 位用于计数器。因为它有两个部分代表 zxid 既是数字又是一对整数,(epoch,count)。时代数字代表了领导层的变化。每当新领导人上台时,它将拥有自己的纪元号码。我们有一个简单的算法来为一个提议分配一个唯一的 zxid:领导者只需递增 zxid 以获得每个提案的唯一 zxid。领导激活将确保只有一个领导者使用给定的纪元,因此我们的简单算法保证每个提案都具有唯一的 ID。
ZooKeeper 消息传递包含两个阶段:
领导者激活:在此阶段,领导者建立正确的系统状态,并准备开始提出建议。
主动消息传递:在此阶段,领导者接受建议和协调消息传递的消息。
ZooKeeper 是一个整体协议。我们不关注个别提案,而是关注整个提案流。我们严格的订购使我们能够有效地完成这项工作并大大简化我们的协议。领导激活体现了这种整体概念。领导者只有在达到法定数量的粉丝时才会变得活跃(领导者也算作跟随者。你总是可以为自己投票)与领导者同步,他们拥有相同的状态。该州包括领导者认为已经提交的所有提案以及跟随领导者的提议,NEW_LEADER 提案。(希望你是在想自己,领导者认为已提交的提案包括所有真正提交的提案吗?答案是肯定的。下面,我们说明原因。)
领导者激活领导者激活包括领导者选举。我们目前在 ZooKeeper 中有两个领导者选举算法:LeaderElection 和 FastLeaderElection(AuthFastLeaderElection 是 FastLeaderElection 的变体,它使用 UDP 并允许服务器执行简单形式的身份验证以避免 IP 欺骗)。只要符合以下条件,ZooKeeper 消息传递并不关心选择领导者的确切方法:
领导者已经看到了所有粉丝中最高的 zxid。法定数量的服务器已承诺跟随领导者。在这两个要求中,只有第一个,跟随者需要保持正确操作的最高 zxid。第二个要求,即法定数量的追随者,只需要很高的概率。我们将重新检查第二个要求,因此如果在领导者选举期间或之后发生失败并且法定人数丢失,我们将通过放弃领导者激活和进行另一次选举来恢复。
领导者选举后,单个服务器将被指定为领导者,并开始等待粉丝连接。其余服务器将尝试连接到领导者。领导者将通过发送他们遗失的任何提案与追随者同步,或者如果追随者缺少太多提案,它将向关注者发送状态的完整快照。
有一个角落案例,其中有一个跟随者有提议,U,领导人看不到。提案按顺序排列,因此 U 的提案的 zxids 高于领导者看到的 zxids。追随者必须在领导人选举后到达,否则追随者将被选为领导者,因为它已经看到更高的 zxid。由于提交的提案必须由法定数量的服务器看到,并且选出领导者的法定数量的服务器没有看到 U,因此您的提议尚未提交,因此可以将其丢弃。当追随者连接到领导者时,领导者将告诉追随者丢弃 U.
一个新的领导者建立一个 zxid​​来开始使用新的提议,通过获得它所见过的最高 zxid 的时代 e,并设置下一个 zxid​​用于(e + 1,0),领导者与跟随者同步后,它将提出一个 NEW_LEADER 提案。提交 NEW_LEADER 提案后,领导者将激活并开始接收和发布提案。
这听起来很复杂,但这里是领导者激活过程中的基本操作规则:
跟随者将在与领导者同步后确认 NEW_LEADER 提案。跟随者只会使用来自单个服务器的给定 zxid 确认 NEW_LEADER 提议。当法定​​数量的粉丝确认后,新的领导者将提交 NEW_LEADER 提案。当 NEW_LEADER 提议为 COMMIT 时,关注者将提交从领导者收到的任何州。在 NEW_LEADER 提案获得 COMMITED 之前,新领导者不会接受新提案。如果领导者选举错误地终止,我们就没有问题,因为由于领导者没有法定人数,因此不会提交 NEW_LEADER 提案。当发生这种情况时,领导者和任何剩下的追随者将超时并返回领导者选举。
活动消息领导者激活完成所有繁重的工作。一旦领导者加冕,他就可以开始提出提案。只要他仍然是领导者,就不可能出现其他领导者,因为没有其他领导者能够获得法定数量的粉丝。如果新的领导者确实出现,那就意味着领导者失去了法定人数,新的领导者将清理领导激活期间留下的任何混乱。
ZooKeeper 消息传递的操作类似于传统的两阶段提交。
所有通信通道都是 FIFO,所以一切都按顺序完成。具体而言,遵守以下操作约束:
领导者使用相同的订单向所有粉丝发送提案。此外,此顺序遵循收到请求的顺序。因为我们使用 FIFO 通道,这意味着关注者也会按顺序接收提案。关注者按照收到的顺序处理消息。这意味着将按顺序确认消息,并且由于 FIFO 通道,领导者将按顺序接收来自关注者的 ACK。这也意味着如果消息 $ m $ 已写入非易失性存储器,则在 $ m $ 之前建议的所有消息都已写入非易失性存储器。一旦有法定数量的粉丝确认消息,领导者将向所有粉丝发出 COMMIT。由于消息按顺序被确认,因此将由跟随者按顺序接收的领导者发送 COMMIT。COMMIT 按顺序处理。在提交提案时,关注者会发送提案消息。
摘要你去吧 它为什么有效?具体而言,为什么新领导人认为的一系列提案总是包含任何实际提交的提案?首先,所有提议都有一个唯一的 zxid,因此与其他协议不同,我们永远不必担心为同一个 zxid​​提出两个不同的值; 粉丝(领导者也是粉丝)按顺序查看和记录提案; 提案按顺序提交; 由于粉丝一次只跟随一位领导者,所以每次只有一位活跃的领导者; 一位新领导人已经看到了上一个时代的所有承诺提案,因为它已经从法定数量的服务器中看到了最高的 zxid; 新领导人看到的上一个时代的任何未提出的提议都将由该领导者在活跃之前承诺。
比较这不只是 Multi-Paxos 吗?不,Multi-Paxos 需要一些方法来确保只有一个协调员。我们不指望这种保证。相反,我们使用领导者激活来恢复领导层变革,或者让老领导人相信他们仍然活跃。
这不仅仅是 Paxos 吗?您的活动消息传递阶段看起来就像 Paxos 的第 2 阶段?实际上,对我们来说,主动消息传递看起来就像是 2 阶段提交,而不需要处理中止。主动消息传递与它们具有交叉提议排序要求的意义不同。如果我们不对所有数据包保持严格的 FIFO 排序,它就会崩溃。此外,我们的领导者激活阶段与他们两个都不同。特别是,我们对 epochs 的使用允许我们跳过未提交的提议块,而不用担心给定 zxid 的重复提议。
法定人数原子广播和领导者选举使用法定人数的概念来保​​证系统的一致视图。默认情况下,ZooKeeper 使用多数仲裁,这意味着在其中一个协议中发生的每个投票都需要多数投票。一个例子是承认领导者提案:领导者只有在收到法定数量的服务器的确认后才能提交。
如果我们从使用多数性中提取我们真正需要的属性,我们只需要保证用于通过投票验证操作的进程组(例如,确认领导者提议)在至少一个服务器中成对交叉。使用多数人保证这样的财产。但是,还有其他方法可以构建与多数群体不同的法定人数。例如,我们可以为服务器的投票分配权重,并说一些服务器的投票更重要。为了获得法定人数,我们得到足够的票数,以便所有投票的权重总和大于所有权重总和的一半。
使用权重并且在广域部署(共址)中有用的不同结构是分层结构。通过这种构造,我们将服务器分成不相交的组并为进程分配权重。为了形成法定人数,我们必须从大多数 G 组获得足够的服务器,这样对于 G 中的每个组 g,来自 g 的投票总和大于 g 中权重总和的一半。有趣的是,这种结构可以实现更小的法定人数。例如,如果我们有 9 个服务器,我们将它们分成 3 组,并为每个服务器分配 1 的权重,然后我们就可以形成大小为 4 的仲裁。注意,两个进程的子集各占大多数来自大多数组中的每个组的服务器必然具有非空交叉点。
使用 ZooKeeper,我们为用户提供配置服务器以使用多数仲裁,权重或组层次结构的功能。
记录 Zookeeper 使用 slf4j 作为日志记录的抽象层。现在选择版本 1.2 中的 log4j 作为最终的日志记录实现。为了更好地嵌入支持,计划将来决定为最终用户选择最终的日志记录实现。因此,始终使用 slf4j api 在代码中编写日志语句,但配置 log4j 以了解如何在运行时进行日志记录。请注意,slf4j 没有 FATAL 级别,FATAL 级别的旧消息已移至 ERROR 级别。有关为 ZooKeeper 配置 log4j 的信息,请参阅 ZooKeeper 管理员指南的“日志记录”部分。
开发者指南在代码中创建日志语句时,请遵循 slf4j 手册。在创建日志语句时,请阅读有关性能的常见问题解答。补丁审阅者将查找以下内容:
记录在正确的级别
slf4j 中有多个级别的日志记录。
选择正确的一个很重要。按从高到低的顺序:
ERROR 级别指定可能仍允许应用程序继续运行的错误事件。WARN 级别表示潜在的有害情况。INFO 级别指定信息性消息,以粗粒度级别突出显示应用程序的进度。DEBUG Level 指定对调试应用程序最有用的细粒度信息事件。TRACE Level 指定比 DEBUG 更细粒度的信息事件。ZooKeeper 通常在生产中运行,以便将 INFO 级别严重性和更高(更严重)的日志消息输出到日志。
使用标准的 slf4j 成语
静态消息记录
LOG.debug(“process completed successfully!”); 但是,当需要创建参数化消息时,请使用格式化锚点。
LOG.debug(“got {} messages in {} minutes”,new Object[]{count,time}); 命名
记录器应以其使用的类命名。
public class Foo {
private static final Logger LOG = LoggerFactory.getLogger(Foo.class);
….
public Foo() {
LOG.info(“constructing Foo”);
异常处理
try {
// code
} catch (XYZException e) {
// do this
LOG.error(“Something bad happened”, e);
// don’t do this (generally)
// LOG.error(e);
// why? because “don’t do” case hides the stack trace

// continue process here as you need… recover or (re)throw
}
转载来源:https://github.com/apache/zoo…

正文完
 0