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...


miazzy
1 声望1 粉丝

菩提本无树,明镜亦非台,本来无一物,何处惹尘埃


« 上一篇
Git中常用术语