【翻译】 ZooKeeper Wait-free coordination for Internet-scale systems

摘要

在本文中,我们描述了一种用于协调分布式应用程序的服务ZooKeeper。 作为关键基础设施的一部分,ZooKeeper旨在提供一个简单和高性能的内核,使得客户端可以构建更复杂的协调原语。 它将组消息传递、共享寄存器和分布式锁等服务整合到一个重新分配的、集中的服务中。 由ZooKeeper暴露出来的接口在共享寄存器方面具有无等待的特性,使用类似于分布式文件系统缓存失效的事件驱动机制来提供简单但是强大的协调服务。

ZooKeeper接口支持高性能的服务实现。 除了无等待属性之外,ZooKeeper 还为每个客户端提供 FIFO 执行请求的保证。将所有改变ZooKeeper 状态的请求线性化。 这些设计使得实现高性能处理流水线成为可能,满足了本地服务器读的请求。 我们显示了目标工作负载,2:1到100:1读取写入比率,ZooKeeper可以每秒处理数以十万计的事务。这个性能使得ZooKeeper可以被客户端应用程序广泛使用。

简介

大规模分布式应用需要不同形式的协调。 配置是最基本的协调形式之一。 在这种简单形式的协调中,配置只是系统进程的操作参数列表,而更复杂的系统需要具有动态配置参数。 组的成员关系和领导选举在分布式系统中也很常见 : 通常进程需要知道哪些其他进程是有效的,这些进程负责什么 锁构成一个强大的协调原语,实现对关键资源的互斥访问。

一种协调办法是为每个不同的协调需要开发服务。 例如,Amazon Simple Queue Service[3]专门用于排队。 其他服务专门用于领导选举 [25] 和配置 [27]。 可以通过使用实现更强大的原语的服务来实现较弱的原语。 例如,chubby [6] 是一个具有强同步保证的锁定服务。 然后锁可以用来实现领导选举、组的成员关系等。

在设计我们的协调服务时,我们不再在服务器端实现特定的原语,而是选择了暴露特定的 API,使应用程序开发人员能够实现自己的原语。 这样的选择导致了一个协调内核的实现,使得新的原语的实现不需要对服务核心的改变。 这种方式使得多种形式的协调能够适应应用的需求,而不是将开发者约束到固定的原语集合。

在设计ZooKeeper的 API 时,我们放弃阻塞原语,比如锁。 除其他问题外,协调服务的阻塞原语可能导致,较慢或有故障的客户端对快速客户端的性能造成负面影响。如果处理请求依赖于其他客户端的响应和失败检测,则服务本身的实现变得更加复杂。我们的系统 Zookeeper,因此实现了一个 API,它可以操作像文件系统那样分层组织的简单的无等待数据对象。 实际上,Zookeeper API 类似于任何其他文件系统,并且只查看API签名,Zookeeper似乎是没有锁打开和关闭的Chubby 然而,实现无等待数据对象的ZooKeeper与基于锁定原语(如lock)的系统显著区分开来。

虽然无等待特性对性能和容错是很重要的,但协调不够。 我们还必须提供操作的顺序保证。
特别地,我们发现,保证所有操作的 FIFO 客户端排序和可线性化的写入能够有效地实现这个服务,并且足够实现我们应用程序感兴趣的协调原语。
借助api,事实上我们可以对任何数量进程达成共识,根据 Herlihy 的层次结构,ZooKeeper 实现了一个通用对象 [14]

ZooKeeper 服务包括一个服务的集合,使用复制来实现高可用性和高性能 其高性能使得包括大量进程的应用能够使用这样的协调内核来管理协调的方方面面。 我们能够实现一个简单的流水线架构的ZooKeeper ,允许我们有成百上千的请求,仍然低延迟。 这样的流水线自然能够以 FIFO 顺序从单个客户机执行操作。 确保 FIFO 客户端顺序使客户端能够异步提交操作。 通过异步操作,客户端一次能够有多个未完成的操作。 这个功能是合乎需要的,当一个新的客户机成为领导者时,它必须对元数据进行处理并相应地更新元数据。 没有多个未完成的操作的可能性,初始化时间可以是秒的数量级而不是亚秒。

为了保证更新操作满足线性一致性,我们实现了基于领导的原子广播协议 [23],称为 ZAB [24]。 然而,ZooKeeper 应用的典型工作负荷由读取操作主导,并且期望扩大读取吞吐量。 在ZooKeeper中,服务器在本地处理读取操作,并且我们不使用 ZAB 来完全使它们有序。

在客户端缓存数据是提高读取性能的一项重要技术。 例如,在每次需要了解领导者时,对当前领导者的标识符进行缓存而不是探查ZooKeeper 的过程是有用的。 ZooKeeper 使用监视机制使客户端能够缓存数据,而无需直接管理客户端缓存。 通过这种机制,客户端可以监视给定数据对象的更新,并在更新时接收通知。 Chubby直接管理客户端缓存。 它阻塞更新来使所有客户端缓存无效并缓存被改变的新数据。 在此设计下,如果这些客户端中的任何一个是缓慢或有故障的,更新被延迟。 chubby 使用租约来防止一个错误的客户端对该系统进行无限期的阻塞。 然而,租约只约束了缓慢或有缺陷的客户的影响,而ZooKeeper 则完全避免了问题。

本文讨论了ZooKeeper的设计与实现。 使用 ZootKeeper ,我们能够实现我们的应用程序所需要的所有协调原语,即使只有写入是可线性化的。 为了验证我们的方法,我们展示了如何用ZooKeeper实现一些协调原语。

综上所述,本文主要贡献如下:
协调内核 :我们提出了一种具有宽松一致性保证的无等待协调服务用于分布式系统。 特别地,我们描述了协调内核的设计和实现,我们已经在许多关键应用中使用该协调内核来实现各种协调技术。

协调食谱 :我们展示了 Zookeeper 如何用于构建更高级的协调原语,甚至是阻塞和强一致性原语,这些原语经常在分布式应用中使用。 我们分享了我们使用ZooKeeper 的方法,并评估了它的性能。

Zookeeper服务

客户端使用 Zookeeper 客户端库通过客户端 API 向动物园管理员提交请求。 除了通过客户端 API 发布ZooKeeper服务接口以外,客户端库还管理客户端和ZooKeeper服务器之间的网络连接。

在本节中,我们首先提供了ZooKeeper服务的高级视图。 然后我们讨论客户端用来与管理员交互的 API。

术语 在本文中,我们使用客户端来表示ZooKeeper服务的用户,服务端表示提供ZooKeeper服务的进程,Znode 表示ZooKeeper数据中的内存数据节点,该数据节点被组织在称为数据树的分级命名空间中。 我们还使用术语 “更新 ”和“ 写入” 来指代任何修改数据树状态的操作。 客户端在连接到ZooKeeper时建立一个会话,并获得他们发出请求的会话句柄。

服务概述

ZooKeeper 向其客户端提供了一组数据节点(Znode)的抽象,这些节点根据层次结构名称空间组织。 这个层次结构中的 Znode 是客户端通过 Zookeeper API 操作的数据对象。 分层名称空间通常用于文件系统中。 这是组织数据对象的理想方式,因为用户习惯于这个抽象,并且它能够更好地组织应用程序元数据。 为了引用给定的 Znode,我们使用了标准的 UNIX 表示法用于文件系统路径。 例如,我们使用 / A / B / C 来表示到 Znode C 的路径,其中 C 以 B 为其双亲,B 以 A 为其双亲。 所有 Znode 都可以存储数据,并且除了短暂 Znode 之外,所有 Znode 都可以有孩子。
2.png

客户端可以创建两种类型的 Znode:

规则 : 客户机显式地创建和删除规则的 Znode;
短命 : 客户机创建这样的 Znode,并且要么将显式地删除它们,要么当创建它们的会话终止时 (有意地或由于失败),让系统自动删除它们。

另外,当创建新的 Znode 时,客户端可以设置顺序标志。 用序列标记集创建的节点具有附加到其名称的单调递增计数器的值。 如果 N 是新的 Znode 并且 P 是父 Znode,那么 N 的序列值决不会小于在 P 下创建的任何其他顺序 Znode 的名称中的值。

ZooKeeper 实现了watch机制,允许客户在不需要轮询的情况下及时收到更改通知。当一个客户端使用一个watch标志集进行读取操作时,操作将正常完成,并且服务器承诺在返回的信息发生更改时通知客户机 Watches 是与会话相关联的一次性触发器;一旦被触发或会话关闭,它们就被注销。 watches表明发生了更改,但不提供更改。 例如,如果一个客户机在“/ foo”发生两次更改之前发出getData(“/ foo”,true),那么客户机将得到一个watch事件,告诉客户机“/ foo”的数据发生了更改。 会话事件,例如连接丢失事件,也被发送到监视回调,以便客户知道watches事件可能被延迟

数据模型 ZooKeeper的数据模型本质上是一个具有简化 API 和仅完整数据读取和写入的文件系统,或是一个具有层次键的key/value表。 分层命名空间对于定位不同应用程序的命名空间子树和对这些子树设置访问权限是有用的。 我们还利用客户端的目录概念来构建更高级的原语,正如我们将在 2.4 节中看到的。

与文件系统中的文件不同,Znode 不是为通用数据存储设计的。 相反,Znode 是客户端应用程序的抽象,通常对应于用于协调目的的元数据。 在图 1 中,我们有两个子树,一个用于应用程序 1(/ app1),另一个用于应用程序 2(/ app2)。 应用程序 1 的子树实现了一个简单的组成员身份协议 : 每个客户端进程 PI 在/ APP1下创建一个 ZNode P I ,在进程运行时持续存在。

虽然 Znode 还没有设计用于一般的数据存储,但是 Zookeeper 确实允许客户端存储一些可以用于分布式计算中的元数据或配置的信息。 例如,在基于领导的应用中,知道哪个其他服务器是领导者对应用服务器是有用的。 为了实现这个目标,我们可以让当前的领导者在 Znode 空间中的已知位置写入这个信息。 Znode 还具有与时间戳和版本计数器相关联的元数据,其允许客户端跟踪对 Znode 的改变并基于 Znode 的版本执行有条件的更新。

会话 客户端连接到ZooKeeper 并启动会话。 会话有关联超时。 如果ZooKeeper在一定时间内从会话中没有收到任何东西,ZooKeeper认为客户有故障。 当客户端显式地关闭会话句柄或ZooKeeper 检测到客户端有故障时,会话 结束。 在会话中,客户观察一系列反映其运行执行情况的状态变化。 会话使客户端能够在 Zookeeper 从一个服务器透明地移动到另一个服务器,从而在ZooKeeper 服务器上持续。

Client API

下面我们将介绍ZooKeeper API的相关子集,并讨论每个请求的语义:
create(path, data, flags): 创建在路径path下的znode,data是要存放的数据,并返回新znode的名称。标志flag可以让客户端选择znode的类型:常规的、临时的,并设置顺序标志

delete(path, version): 删除给定路径和版本下的znode。

exists(path, watch):如果有路径名路径的znode存在,返回true,否则返回false。watch允许客户端在znode上设置监视器。

getData(path, watch):返回与znode相关的数据和元数据如版本信息。watch标志的工作方式与exists()相同(),但是如果znode不存在的话,zookeeper就不设置watch;

setData(path, data, version): 如果版本号是version,写入数据data[]到znode路径;

getChildren(path, watch):返回一个znode的子节点集;

sync(path): 把所有在sync之前的更新操作都进行同步,达到每个请求都在半数以上的ZooKeeper Server上生效。path参数目前没有用

所有方法都有同步版本和异步版本,可通过 API 获得。 应用程序在执行单个Zookeeper操作,并且它没有执行的并发任务时使用同步 API,,因此让Zookeeper阻塞是必要的。 但是,异步 API 可以使应用程序进行多个未完成的 Zookeeper 操作和其他并行任务。 Zookeeper 客户端保证每个操作的相应回调调用是按顺序被调用的。

注意,ZooKeeper不使用句柄来访问 Znode。 每个请求改为包括正在操作的 Znode 的完整路径。 这个选择不仅简化了 API(没有 open () 或 close () 方法),而且还消除了服务器需要维护的额外状态。

每个更新方法都采用预期的版本号,这使得可以实现条件更新。 如果 znode 的实际znode与预期版本号不匹配,则更新失败,报版本不一致的错误。 如果版本号为 -1,则不执行版本检查。

ZooKeeper guarantees

Zookeeper有两个基本的顺序保证:
线性写:所有更新Zookeeper状态的操作是串行的,先来先服务
FIFO客户端顺序:来自客户端的所有请求按客户端发送的顺序依次执行

注意,我们的线性定义不同于 Herlihy [15] 最初提出的线性,我们称之为A-linearizability (异步线性化)。 在它的 “线性”的原始定义中,一个客户端只能一次有一个未完成的动作(客户端是一个线程)。 我们允许客户有多个未完成的操作,因此我们可以选择不保证同一客户的未完成操作的特定顺序,或者不保证 FIFO 顺序。 我们选择后者作为我们的特性 重要的是,我们观察到,对于可线性化对象的所有结果也适用于A-linearizable 对象,因为满足A-linearizable 的系统也具有线性化。 因为只有更新请求是A-linearizable的,Zookeeper在每个副本上本地读取请求。 这使得系统通过添加服务器而线性可扩展。

要了解这两个保证如何交互,请考虑以下场景。 包括多个进程的系统选举leader来命令工人进程。 当新的leader负责该系统时,它必须改变大量的配置参数,并且一旦它完成就通知其他进程。 我们有两个重要要求 :

  1. 当新的leader开始进行改变时,我们不希望其他进程使用正在被改变的配置;

  2. 如果新的leader在配置被完全更新之前宕机,我们不希望进程使用这个部分新的配置。

注意,分布式锁,如 Chuby 提供的锁,将有助于第一个需求,但对第二个需求不足。 在 Zookeeper 中,新的leader可以指定一个路径作为备用 Znode;其他进程只在 Znode 存在时才使用这个配置 新的leader通过删除就绪、更新各种配置 Znode 和创建就绪znode来使配置改变。 所有这些更改都可以流水线化并异步发布以快速更新配置状态。 尽管改变操作的等待时间是 2 毫秒,但是如果请求被一个接一个地发布,则更新 5000 个不同的 Znode 的leader将必须花费 10 秒;通过异步地发出请求将花费少于一秒。 由于有顺序的保证,如果一个进程看到就绪 Znode,它也必须看到新leader所做的所有配置更改。 如果新的领导者在创建 Znode 之前宕机,其他进程知道配置尚未完成并且不使用它。

上述方案仍然存在问题 : 如果一个进程在新的leader开始进行改变之前已经看到ready存在,并且在改变正在进行时开始读取配置,则会发生什么。 该问题由通知的排序保证解决 : 如果客户端正在观看改变,则客户端将在做出改变之后看到系统的新状态之前收到通知事件。 因此,如果读取 READY ZNODE 请求的进程请求被通知 ZNODE 更改,则它将看到通知客户机在它可以读取任何新配置之前的更改。

另一个问题可能出现在客户除了Zookeeper还有自己的通信渠道。 例如,考虑在Zookeeper中具有共享配置的两个客户端 A 和 B,并且通过共享通信信道进行通信。 如果 A 更改了 ZooKeeper 中的共享配置并通过共享通信信道告知 B 更改,则 B 将在重新读取配置时期待看到更改。 如果 B 的ZooKeeper 副本稍微落后于 A,则可能看不到新的配置。 使用上述保证B可以确保在重新读取配置之前,它可以通过发出一个写来查看最新的信息。 为了更有效地处理这个场景,ZooKeeper 提供同步请求 : 当跟随一个读取时,构成一个慢读取。同步使一个服务器在处理读取之前应用所有挂起的写请求,而没有一个完整写的开销。 这种原语的思想与 ISIS [5] 的原语相似。

ZooKeeper 还具有以下两个活性和耐用性保证 : 如果大多数ZooKeeper 服务器是活跃的,并且通信服务将是可用的;并且如果ZooKeeper 服务成功地响应了改变状态的请求,那么只要失败的服务器是可恢复的,则该改变就会在任何数量的故障中持久化。

Examples of primitives

在本节中,我们展示了如何使用 Zookeeper API 来实现更强大的原语。 Zookeeper 服务对这些更强大的原语一无所知,因为它们完全是在 Zookeeper客户端 API 上实现的。 一些常见的原语,如组成员身份和配置管理,也是无等待的。 对于其他的人,如集合点,客户需要等待事件。 即使ZooKeeper 是免等待的,我们可以使用Zookeeper实现有效的阻塞原语 ZooKeeper的顺序保证允许有效的推理系统状态,watches 允许有效等待。

Configuration Management

在分布式应用中,可以使用ZooKeeper 来实现动态配置。 在其最简单的形式中,配置被存储在 Znode,ZC 中。 进程以 ZC 的完整路径名开始。 启动进程通过读取 ZC 而获得它们的配置,其中watch设置为 TRUE。 如果更新了 ZC 中的配置,则通知进程处理并读取新配置,再次将watch标志设置为真。

注意,在这个方案中,在使用watches的大多数其他方案中,使用watches来确保进程具有最新的信息。 例如,如果监视 ZC 的进程被通知ZC有改变,并且在读取新的 ZC 之前存在对 ZC 的三个或者更多改变,则该进程不会接收三个或者更多通知的事件。 这并不影响进程的行为,因为这三个事件只是简单地通知进程它已经知道的事情 : 它对于 ZC 的信息是陈旧的。

Rendezvous(汇合)

有时在分布式系统中,最终的系统配置将看起来是什么样子并不总是清楚的。 例如,客户机可能想要启动主进程和几个工作进程,但是启动进程是由调度程序完成的,因此客户机不知道在时间之前的信息,诸如地址和端口,这些用来给予worker进程连接到主设备的信息。 我们用Zookeeper来处理这个场景,Znode Zr 是由客户端创建的节点。 客户机将 ZR 的完整路径名作为master和worker进程的启动参数传递。 当主控器开始填充 ZR 时,它就会使用关于地址和端口的信息。 当worker开始工作时,他们读了 ZR,watch设置为真。 如果尚未填写 ZR,则在 ZR 更新时,worker会被通知。 如果 ZR 是短暂节点,则主进程和worker进程可以在客户端结束时监视 ZR 被删除并清除自己。

Group Membership

我们利用短暂节点来实现组成员身份。 具体而言,我们使用短暂节点基于它允许我们看到创建节点的会话状态的事实。 我们开始设计一个 Znode,Zg 来代表这个组。 当组的进程成员开始时,它在ZG 下创建临时子 Znode。 如果每个进程都有唯一名称或标识符,则该名称被用作子 Znode 的名称;否则,进程将创建带有顺序标志的 Znode,以获得唯一名称分配。 进程可以将进程信息放入进程所使用的子 Znode、地址和端口的数据中。

在 ZG 下创建子 Znode 之后,进程正常开始。 它不需要做任何别的事情 如果进程失败或结束,则自动删除表示它的 Znode。

进程可以通过简单列出 ZG 的孩子来获得组信息。 如果进程想要监视组成员身份的改变,则该进程可以在接收到改变通知时将watch标志设置为真,并且刷新组信息 (将watch标志总是设置为真)。

Simple Locks

虽然ZooKeeper 不是一个锁定服务,它可以用来实现锁。 使用ZooKeeper的应用程序通常使用针对其需要定制的同步原语,如上面所示。 在这里,我们演示如何使用 Zookeeper 实现锁,以显示它可以实现广泛的各种通用同步原语。

最简单的锁实现使用 “锁文件 ”。锁由一个 Znode 表示。 为了获取锁,客户端尝试创建具有短暂标志的指定 Znode。 如果创建成功,则客户端持有锁。 否则,客户端使用watch标志来读取 ZNODE以得到通知,如果当前的领导者死亡 客户端在其死亡或非法删除 Znode 时释放锁。 其他正在等待锁的客户端一旦观察到被删除的 Znode,就会再次尝试获取锁。

虽然这种简单的锁定协议工作,但确实存在问题。 首先,它受到羊群效应的影响。 如果有许多客户端等待获取锁,即使只有一个客户端可以获取锁,它们都会在释放锁的时候争夺锁。 第二,它只实现了排他锁。 下面的两个原语说明如何克服这两个问题。

Simple Locks without Herd Effect

我们定义一个锁znode l来实现这样的锁。直观上,我们排列所有请求锁的客户端,每个客户端都按请求到达的顺序获得锁。因此,希望获得锁的客户如下:
3.png

在锁定第一行中使用SEQUENTIAL标志,使得客户端的请求与其他的请求按顺序请求锁 如果客户端的 Znode 在第 3 行具有最低序列号,则客户端持有锁。 否则,客户端等待那么具有锁的 Znode删除,那么将要具有锁的 Znode删除 通过只观察在客户端的 Znode 之前的 Znode,我们可以避免羊群效应,通过在释放锁或放弃锁时只唤醒一个进程来。 一旦客户端监视的 Znode 消失,客户端必须检查它现在是否持有锁。 (先前的锁定请求可能已经被放弃,并且存在具有较低序列号的 Znode,仍然等待或持有锁)。

释放锁与删除表示锁请求的 Znode N 一样简单。 通过使用创建时的EPHEMERAL标志,崩溃将自动清除任何锁请求或释放他们可能拥有的任何锁。
总之,这种锁定方案具有以下优点 :

  1. Znode 的移除仅导致一个客户端醒来,因为每个 Znode 被恰好另一个客户端监视,所以我们没有羊群效应;
  2. 没有轮询或超时;
  3. 由于我们实现锁定的方式,我们可以通过浏览 Zookeeper 数据来查看锁争用、中断锁和调试锁定问题。

Read/Write Locks

为了实现读/写锁,我们稍微改变了锁程序,并有独立的读锁和写锁过程。解锁过程与普通锁的情况相同。
4.png

此锁定程序与先前的锁稍有不同。 写锁只在命名方面不同。 由于读取锁可以被共享,所以行 3 和 4 稍微改变,因为只有较早的写入锁 Znode 才能阻止客户机获取读取锁。 如果有几个客户端等待读取锁,并且在删除具有较低序列号的 “write - ”znode 时得到通知,这似乎出现“ 羊群效应 ”;实际上,这是我们所需要的行为,所有那些读取客户端都应该被释放,因为它们现在有了锁。

Double Barrier

双重屏障使客户端能够同步计算的开始和结束。 当由屏障入口定义的足够的进程已经加入屏障时,进程开始它们的计算并且一旦它们完成就离开屏障。 我们用 Znode 来表示Zookeeper的障碍,称为 B。 每一个进程 P 都在 B - 通过创建一个 Znode 作为 B 的孩子来注册,在它准备好离开时取消注册 -。 当 B 的子 Znode 的数目超过屏障阈值时,进程可以进入屏障。 进程可以在所有的进程都已移除其孩子时离开屏障。 我们使用watch有效等待进入和退出条件得到满足。 要进入屏障的话,进程监视 B 的一个准备好的孩子的存在,当孩子数量超过障碍阈值时,它由进程创建。 要离开屏障,进程监视某个特定的孩子消失,并且只有在这个特定的 Znode 被删除后才检查退出条件。

ZooKeeper Applications

现在我们描述一些使用ZooKeeper的应用程序,并简要说明它们的使用。 我们以粗体显示每个例子的原型。

The Fetching Service 爬虫是搜索引擎的一个重要组成部分,而 Yahoo! 抓取数十亿的 Web 文档。 获取服务(FS)是 Yahoo! 爬虫的一部分,目前正在生产环境中。 本质上,它有一个主进程,来命令页面抓取进程。 主节点为抓取器提供配置,抓取器将其状态和健康状况写回来。 使用 ZooKeeper for FS 的主要优点是从主机的故障恢复,尽管失败了,但仍然保证可用性,并且将客户端与服务器分离,从而允许他们通过从Zookeeper中读取他们的状态来引导他们重新搜索健康服务器。 因此,FS 使用ZooKeeper 主要是管理配置元数据,虽然它还使用ZooKeeper 选举master(领袖选举)。
5.png

图 2 显示了 FS 使用的ZooKeeper服务器的读取和写入流量,为期三天。 为了生成这个图,我们计算周期期间每秒的次数,每个点对应于那一秒操作的次数。 我们观察到,读取流量比写入流量高得多。 在速率高于每秒 1, 000 次操作的时段期间,读取 : 写入比率在 10∶1 到 100∶1 之间变化。 在此工作负载中的读取操作是 GetData ()、GetPages () 和 Exists (),以增加的顺序。

Katta Katta [17] 是一个使用 ZooKeeper协调的分布式索引器,它是一个非雅虎应用程序的例子。 Katta 通过使用碎片划分了索引的工作。 master将碎片分配给slaves并跟踪进度。 slave可能失败,所以这时master必须重新分配负荷。 master也可能失败,因此其他服务器必须准备好在故障的情况下接管。 Katta 使用ZooKeeper跟踪slave服务器和master服务器的状态(组成员身份),并处理master故障转移(领导者选举)。 Katta 还使用ZooKeeper跟踪和传播碎片的分配给奴隶(配置管理)。

Yahoo! Message Broker 雅虎消息代理 (YMB) 是分布式发布 - 订阅系统。 该系统管理数千个主题,客户端可以发布消息并接收消息。 主题分布在一组服务器中以提供可扩展性。 使用主备份方案复制每个主题,确保将消息复制到两个机器中以确保可靠的消息传递。 组成 YMB 的服务器使用无共享的分布式体系结构,这使得协调对于正确操作至关重要。 YMB 使用ZooKeeper来管理主题的分布 (配置元数据),处理系统中机器的故障 (故障检测和组成员身份),以及控制系统操作。
6.png

图 3 显示了 YMB 的 Znode 数据布局的一部分。 每个代理域都有一个 Znode,称为节点,节点具有组成 YMB 服务的每个活动服务器的临时 Znode。 每个 YMB 服务器节点下创建一个短暂 Znode,它具有负载和状态信息,通过 Zookeeper 提供组成员资格和状态信息。 所有组成服务的服务器都对禁用的节点(如禁用和迁移)进行了维护,并允许对 YMB 进行集中控制。 每个主题在topics下都有一个子节点。这些 Znode 指示了每个主题的主服务器和备份服务器和该主题的订阅者。 主服务器和备份服务器 Znode 不仅允许服务器发现那个负责某一个主题的服务器,而且还管理领导选举和服务器崩溃。
7.png

ZooKeeper Implementation

ZooKeeper通过在构成服务的每个服务器上复制ZooKeeper 数据提供高可用性。 我们假设服务器崩溃,服务器可能会恢复。 图 4 显示了ZooKeeper 服务的高级组件。 接收请求后,服务器将准备执行(请求处理器)。 如果这样的请求需要服务器之间的协调 (写请求),那么它们使用协议 (原子广播的实现),并且最终服务器将提交改变到在该系综的所有服务器上完全复制Zoo-Keeper数据库。 在读取请求的情况下,服务器简单地读取本地数据库的状态并生成对请求的响应。

复制数据库是包含整个数据树的内存数据库。 树中的每个 Znode 默认存储最多 1MB 的数据,但是这个最大值是在特定情况下可以更改的配置参数。 对于可恢复性,我们有效地将更新记录到磁盘上,并且在磁盘介质被应用到内存数据库之前,我们强制写入磁盘介质。 事实上,与 chubby [8]相比,我们保留了提交的操作的重放日志(在我们这里是预写日志),并生成内存中数据库的定期快照。

每个ZooKeeper 服务器都服务客户端。 客户端连接一个服务器来提交请求。 正如我们前面提到的,使用每个服务器数据库的本地副本来服务读取请求。 改变服务状态的请求,写请求,由协议处理。

作为协议的一部分,写请求被转发到称为 leader1 的单个服务器。 其余的ZooKeeper服务器,称为 “跟随者 ”,从leader接收主要由状态更改组成的消息建议,并商定状态更改。

Request Processor

由于消息层是原子的,我们保证本地副本从不偏离,尽管在任何时间点,一些服务器可能比其他服务器应用更多的事务。 与从客户端发送的请求不同,事务是幂等的。 当leader接收到写请求时,它计算当应用写入时系统的状态将是什么,并且将它转换成捕获这个新状态的事务。 由于可能存在尚未应用于数据库的可能存在的事务,因此必须计算该状态。 例如,如果一个客户端在请求中的条件 setData 和版本号匹配正在更新的 Znode 的未来的版本号,则该服务生成一个 SetDataTxN,该 SetDataTxN 包含新数据、新版本号和更新的时间戳。 如果出现错误,如不匹配版本号或要更新的 Znode 不存在,则改为生成 ErrorTXN。

Atomic Broadcast

所有更新ZooKeeper状态的请求都被转发到leader。 leader执行这个请求并通过 ZAB [24],一个原子广播协议向ZooKeepeer广播改变。 接收客户端请求的服务器在其递送对应的状态改变时响应客户端。 ZAB 用大多数的意见来决定一个建议,所以 Zab 和 ZooKeeper 只能工作如果大多数服务器是正确的(即,用 2F + 1 服务器我们可以容忍 F 故障)。

为了获得高吞吐量,ZooKeeper 试图保持请求处理流水线化。 它可能会在处理管道的不同部分的成千上万个请求。 由于状态改变依赖于应用先前状态改变,所以 ZAB 提供比常规原子广播更强的次序保证。 更具体地,ZAB 保证由leader广播的改变按照它们被发送的顺序递送,并且来自先前leader的所有改变在广播其自身改变之前被递送到一个已经建立的leader。

有一些实现细节简化了我们的实现并给我们出色的性能 我们使用 TCP 为我们的传输,所以消息顺序由网络主导,这允许我们简化我们的实现。 我们使用由 ZAB 选择的领导者作为ZooKeeper的领导者,以便与创建事务相同的进程也提出。 我们使用日志来跟踪提议,作为内存中数据库的预写日志,这样我们就不必两次将 信息写入磁盘。

在正常操作期间,ZAB 确实将所有消息按顺序传送,但由于 ZAB 没有持续记录所传递的每个消息的 ID,所以 ZAB 可以在恢复期间重新发送消息。 因为我们使用幂等事务,所以只要顺序交付,多次交付就可以接收。 事实上,ZooKeeper 要求 Zab 至少重新发送在上次快照开始之后交付的所有消息。

Replicated Database

每个副本都有一个在内存里的ZooKeeper 状态的副本。 当管理员服务器从崩溃中恢复时,它需要恢复此内部状态。 在运行服务器一段时间后,重放所有恢复状态的消息以恢复状态将非常耗时,所以 ZooKeeper 使用定期快照,只需要自快照开始后重新发送消息。 我们调用Zoo-Keeper快照模糊快照,因为我们不锁定 Zookeeper 状态来获取快照;相反,我们首先进行树的深度扫描,以原子读取每个znode 的数据和元数据并将它们写入磁盘。 由于所得到的模糊快照可能已经应用了在生成快照期间所传递的状态改变的一些子集,所以结果可能不对应于在任何时间点的ZooKeeper 的状态。 然而,由于状态变化是幂等的,所以只要我们按顺序应用状态改变,我们就可以应用两次。

例如,假设在ZooKeeper 数据树中,两个节点 / foo 和 / goo 分别具有值 f1 和 g1,并且当模糊快照开始时,两个节点都处于版本 1,并且下面的状态改变流具有形式 :(transactionType, path, value, new-version)

(SetDataTXN, /foo, f2, 2)
(SetDataTXN, /goo, g2, 2)
(SetDataTXN, /foo, f3, 3)

在处理这些状态改变之后,/ foo 和 / goo 分别具有版本 3 和 2 的值 f3 和 g2。 然而,模糊快照可能已经记录了 / foo 和 / goo 具有分别具有版本 3 和 1 的值 f3 和 g1,这不是ZooKeeper 数据树的有效状态。 如果服务器在此快照中崩溃并恢复,且 ZAB 重新传递状态更改,则生成的状态对应于崩溃前的服务状态。

Client-Server Interactions

当服务器处理写请求时,它还发送并清除与该更新相对应的任何watch相关的通知。 Servers 按顺序处理写入, 并且不同时处理其他写入或读取。 这确保了通知的严格连续。 注意服务器只处理本地通知。 只有客户端所连接到的服务器才跟踪并触发该客户端的通知。

Read 请求在每个服务器上本地处理。 每个读请求都被处理并标记有一个 zxid,该 zxid 对应于服务器看到的最后一个事务。 此 zxid 定义读取请求的部分顺序,并重新检查写入请求。 通过本地处理读取,我们获得了优异的读取性能,因为它只是本地服务器上的内存操作,并且没有读的磁盘的操作或者协议去运行。 这样设计是实现具有读为主要工作负载的优良性能的目标的关键。

使用快速读取的一个缺点是不能保证读取操作的优先顺序。 也就是说,即使已经提交了对同一 znode 的最近更新,读操作也可以返回旧值。 并不是所有的应用程序都需要优先顺序,但是对于需要优先顺序的应用程序,我们实现了 sync。 此原语异步执行, 并由领导在所有挂起的写入其本地副本之后对其进行排序。 为了保证给定的读操作返回最新的更新值,客户机每次调用一次读操作紧跟着sync。 客户端操作的 FIFO 顺序保证以及同步的全局保证使得读取操作的结果能够反映在调用同步之前发生的任何改变。 在我们的实现中,我们不需要在原子上广播同步,因为我们使用基于领导的算法,并且我们简单地将同步操作放置在请求队列的末端,在领导和服务器之间进行调用。 为了实现这一目标,追随者必须确保领导者仍然是领导者。 如果存在提交的挂起事务,则服务器不怀疑领导。 如果挂起的队列为空,领导需要发出一个空事务来提交,并把sync排在该事务之后同步。 这具有良好的特性,即当领导者处于负载下时,不会生成额外的广播流量。 在我们的实现中,设置超时,使得领导者在追随者放弃他们之前意识到他们不是领导者,因此我们不发布空事务。

ZooKeeper 服务器以 FIFO 顺序处理来自客户端的请求。 Response 包括该响应与之相关的 zxid。 甚至在没有活动的间隔期间的心跳消息也包括客户端连接到的服务器看到的最后一个 zxid。 如果客户端连接到新服务器,则该新服务器通过对照客户端的最后 zxid 检查客户端的最后 zxid 来确保其对 ZooKeeper 数据的视图至少与客户端的视图一样近。 如果客户端具有比服务器更近的视图,则服务器直到追赶上才与客户端建立连接。 保证客户端能够找到具有系统最近视图的另一服务器,因为客户端只看到已经复制到大多数 ZooKeeper 服务器的更改。 这种行为对保证耐久性很重要。

为了检测客户端会话失败,ZooKeeper 使用超时。 如果在会话超时内没有其他服务器从客户端接收到任何信息, 领导将确定发生了故障。 如果客户端发送的请求足够频繁,则不需要发送任何其他消息。 否则,客户端在低活动期间发送心跳消息。 如果客户端无法与服务器发送请求或心跳的服务器通信,则它将连接到不同的 ZooKeeper 服务器以重新建立其会话。 为了防止会话超时,ZooKeeper 客户端库在会话空闲 s / 3ms 之后发送心跳,如果没有从服务器听到 2s / 3ms,则切换到新服务器。这里的s代表会话超时时间,单位是ms。

Evaluation

我们在 50 个服务器的集群上执行了所有的评估。 每个服务器有一个 Xeon 双核 2. 1GHz 处理器,4GB 的 RAM,千兆以太网,和两个 SATA 硬盘。 我们将下面的讨论分为两部分 : 请求的吞吐量和延迟。

Throughput

为了评估我们的系统,我们benchmark了系统饱和时的吞吐量和各种故障时的吞吐量变化。 我们改变了组成 ZooKeeper 服务的服务器的数量,但是总是保持客户端的数量相同。 为了模拟大量的客户端,我们使用 35 台机器来模拟 250 个同时的客户端。

我们有 ZooKeeper 服务器的 Java 实现,Java 和 C 客户机都有。 基于经验,我们使用Java 服务器配置记录一个专用磁盘并在另一个磁盘上获取快照 。 我们的benchmark客户端使用异步 Java 客户端 API,每个客户端至少有 100 个待完成的请求。 每个请求由 1K 数据的读或写组成。 我们不显示其他操作的benchmark,因为修改状态的所有操作的性能近似相同,而不包括sync的非状态修改操作的性能近似相同。 ( 同步的性能与轻量级写入的性能一致,因为请求必须传递给领导者,但不能广播。) 客户每 300ms 发送完成操作次数的计数,我们每 6s 采样一次。 为了防止内存溢出,服务器限制了系统中并发请求的数量。 ZooKeeper 使用请求节流来防止服务器被淹没。 对于这些示例,我们将 ZooKeeper 服务器配置为在j进程中具有最多 2000 个请求。

9.png
图5:饱和系统的吞吐量随读写比率变化示意图
10.png
在图 5 ,我们显示了当我们改变读与写的比率时的吞吐量,每个曲线对应于提供 ZooKeeper 服务的服务器的不同数量。 表 1 示出了读取负载的极值。 读取吞吐量高于写入吞吐量,因为读取不使用原子广播。 该图还示出了服务器的数量也对广播协议的性能具有负面影响。 从这些图表中,我们观察到系统中服务器的数量不仅影响服务能够处理的故障的数量,而且影响服务能够处理的工作负载。 注意,三个服务器的曲线与其他服务器的曲线交叉约 60%。 这种情况下包括三服务器配置,并且由于启用了并行本地读取,所以对所有配置都会发生。 但是,对于图中的其他配置,这是不可见的,因为我们为了可读性设置了最大 y 轴吞吐量的上限。

写入请求比读取请求花费更长的时间有两个原因。 首先,写请求必须经过原子广播,这需要一些额外的处理并增加请求的延迟。 对写请求进行更长的处理的另一个原因是服务器必须确保在将确认发送回leader之前将事务记录到非易失性存储介质。 在实际应用中,这种要求是过分的,但是对于我们的生产系统,我们用性能来换取可靠性,因为 ZooKeeper 构成了应用基础。 我们使用更多服务器来容忍更多故障。 我们通过将 ZooKeeper 数据划分成多个 ZooKeeper 整体来增加写入吞吐量。 Gray 等人先前已经观察到复制和分区之间的这种性能折衷。 [12].

11.png
ZooKeeper 能够通过在组成服务的服务器上分配负载来实现非常高的吞吐量。 我们可以分配负载,因为我们降低一致性保证。 而Chubby客户将所有请求定向到leader。 图 6 显示了如果我们不利用这种降低并强迫客户只连接到leader会发生什么。 正如预期的,对于以读为主的工作负载,吞吐量要低得多,但即使对于以写为主的工作负载,吞吐量也较低。 给客户端提供服务引起的额外 CPU 和网络负载影响leader协调提案的广播的能力,这反过来也会对总体写入性能产生不利的影响。

原子广播协议完成系统的大部分工作,因此比任何其他组件更多地限制了 ZooKeeper 的性能。 图 7 显示了原子广播组件的吞吐量。 为了评估其性能,我们通过直接在leader处生成事务来模拟客户端,因此没有客户端连接或客户端请求和回复。 在最大吞吐量,原子广播组件变为 CPU 绑定。 理论上,图 7 的性能将与具有 100%写入的 ZooKeeper 的性能相匹配。 但是,ZooKeeper 客户端通信、 ACL 检查和请求事务转换都需要 CPU。 CPU 的争用使得 ZooKeeper 吞吐量到比单独的原子广播组件小。 由于 ZooKeeper 是一个关键的生产组件,到目前为止我们对 ZooKeeper 的开发重点是正确性和鲁棒性。 通过消除额外副本、同一对象的多个序列化、更高效的内部数据结构等,有大量的机会显著提高性能。
12.png
13.png

为了显示系统在注入故障时的行为,我们运行了一个由 5 台机器组成的 ZooKeeper 服务。 我们运行了与以前相同的饱和benchmark,但这次我们将写入百分比保持在恒定的 30%,这是我们预期的工作负载的保守比率。 我们定期地杀死一些服务器进程。 图 8 示出了随着时间变化的系统吞吐量。 图中标出的事件如下 :

  1. follower的故障和恢复;

  2. 不同follower的故障和恢复;

  3. leader失败;

  4. 在前两个标记下两个follow(a,b) 的故障,并且在第三标记 (c) 处恢复;

  5. leader失败。

  6. leader恢复。

这幅图中有一些重要的观察结果。 首先,如果follow失败并快速恢复,则 ZooKeeper 能够维持高吞吐量,尽管失败。 一个跟随者的失败并不能阻止服务器形成仲裁,并且仅粗略地通过服务器在失败之前正在处理的读取请求来减少吞吐量。 第二,我们的leader选择算法能够足够快地恢复,以防止吞吐量大幅下降。 在我们的工作中,ZooKeeper 需要不到 200ms 的时间来选举新的leader。 因此,尽管服务器在几分之一秒内停止服务请求,但是由于我们的采样周期(大约为秒),我们没有观察到零吞吐量。 第三,即使follow需要更多的时间来重新恢复,ZooKeeper 也能够在他们开始处理请求时再次提高吞吐量。 在事件 1、2 和 4 之后,我们没有恢复到完全吞吐量水平的一个原因是客户端仅在其到follow的连接中断时切换follow。 因此,在事件 4 之后,直到leader在事件 3 和 5 处失败,客户机才重新分配它们自己的请求。 实际上,随着客户来来往往,这种不平衡随着时间的推移而起作用。

Latency of requests

为了评估请求的延迟,我们创建了一个以 Chubby benchmark [6] 为模型的benchmark。 我们创建了一个工作进程,它简单地发送一个 create,等待它完成,发送一个新节点的异步删除,然后启动下一个 create。 我们相应地改变worker的数量,并且对于每次运行,我们使每个worker创建 50, 000 个节点。 我们通过将完成的创建请求的数量除以所有worker完成的总时间来计算吞吐量。
13.png
表 2 显示了我们的benchmark的结果。 创建请求包括 1K 的数据,而不是 Chubby benchmark中的 5 个字节,以更好地符合我们的预期使用。 即使有了这些更大的请求,ZooKeeper 的吞吐量也比 Chubby 发布的吞吐量高出 3 倍以上。 单个 ZooKeeper worker benchmark的吞吐量显示三个服务器的平均请求延迟为 1.2ms,9 个服务器的平均请求延迟为 1.4ms。
14.png

Performance of barriers

在本实验中,我们执行多个屏障来评估使用 ZooKeeper 原本的性能。 对于给定数量的障碍 b,每个客户端首先进入所有的 b 障碍,然后它连续地离开所有的 b 障碍。 当我们使用第 2. 4 节的双屏障算法时,客户机首先等待所有其他客户机执行 enter () 过程之后才进行下一个调用(类似于 leave ())。

我们在表 3 中报告了我们的实验结果。 在这个实验中,我们有 50、100 和 200 个客户相继进入 b 号屏障,b属于集合 {200; 400; 800; 1600g} 虽然应用程序可以有数以千计的 ZooKeeper 客户端,但通常有小得多的子集参与每个协调操作,因为客户端通常根据应用程序的特性进行分组。

本实验的两个有趣的观察结果是,处理所有障碍的时间与障碍的数量大致成正比,表明对数据树的相同部分的并发访问不会增加任何意外延迟。迟延的增长和客户端的数量成正比。 这是没使 ZooKeeper 服务饱和的结果。 事实上,我们观察到,即使在客户在锁定的步骤下下,屏障操作 (进入和离开) 的吞吐量在所有情况下都在每秒 1, 950 到 3, 100 个操作之间。 在 ZooKeeper 操作中,这对应于每秒 10, 700 到 17, 000 次操作之间的吞吐量值。 在我们的实现中,我们的读取对写入的比率为 4∶1(读取操作的 80%),与 ZooKeeper 可以实现的原始吞吐量相比,我们的benchmark代码使用的吞吐量要低得多(超过图 5 的 40, 000)。 这是由于客户端等待其他客户端。

zookeeper 的目标是提供一种服务,该服务解决了分布式应用程序中协调进程的问题。 为了实现这个目标,它的设计使用了以前的协调服务、容错系统、分布式算法和文件系统的思想。

我们并不是第一个提出一个用于分布式应用程序协作的系统。 一些早期的系统提出了用于事务性应用的分布式锁服务 [13],以及用于在计算机集群中共享信息 [19]。 最近,Chubby 提出了一种为分布式应用程序管理咨询锁的系统 [6]。 Chubby 和 ZooKeeper 有几个共同的目标。 它也具有类似文件系统的接口,并且它使用协议来保证副本的一致性。 但是,ZooKeeper 不是锁定服务。 客户端可以使用它来实现锁,但是在其 API 中没有锁操作。 与 Chubby 不同,ZooKeeper 允许客户端连接到任何 ZooKeeper 服务器,而不仅仅是leader。 ZooKeeper 客户端可以使用他们的本地副本来提供数据和管理watch,因为它的一致性模型比 Chubby 宽松得多。 这使得 ZooKeeper 能够提供比 Chubby 更高的性能,允许应用程序更广泛地使用 ZooKeeper。

在文献中已经提出了容错系统,其目的是减轻构建容错分布式应用程序的问题。 一个早期的系统是 ISIS [5]。 ISIS 系统将抽象的类型规范转换为容错的分布式对象,从而使容错机制对用户透明。 Horus [30] 和Ensemble [31] 是由 ISIS 进化而来的系统。 ZooKeeper 支持 ISIS 的虚拟同步概念。 最后,Totem 保证在利用局域网的硬件广播的体系结构中的消息传递的总顺序 [22]。 ZooKeeper 使用各种各样的网络拓扑,这些拓扑促使我们依赖于服务器进程之间的 TCP 连接,而不是假定任何特殊的拓扑或硬件特性。 我们也不公开在 ZooKeeper 中内部使用的任何集成通信。

构建容错服务的一项重要技术是状态机复制 [26],而 Paxos [20] 是一种能够有效实现异步系统的复制状态机的算法。 我们使用 Paxos 的某些特性的算法,但是它将达成共识所需的事务日志记录与数据树恢复所需的提前写入日志记录结合起来,以实现高效的实现。 已经提出了用于拜占庭容忍复制状态机的实际实现的协议 [7、10、18、1、28]。 ZooKeeper 并不假设服务器可以是拜占庭,但是我们确实使用诸如校验和和理智检查之类的机制来捕捉非恶意的拜占庭错误。 Clement et al等人讨论了在不修改当前服务器代码库的情况下使 ZooKeeper 完全容忍拜占庭错误的方法 [9]。 到目前为止,我们还没有观察到使用完全 Byzantine 容错协议可以防止的生产中的故障。 [29].

Boxwood [21] 是一个使用分布式锁定服务器的系统。 Boxwood 为应用程序提供了更高级的抽象,它依赖于基于 Paxos 的分布式锁服务。 ZooKeeper 和 Boxwood 一样,是构建分布式系统的重要工具。 然而,zookeeper 具有高性能要求,并且更广泛地用于客户端应用程序。 zookeeper 暴露了应用程序用于实现高级原语的低级原语。

Zookeator 类似于小型文件系统,但它只提供文件系统操作的一小部分,并添加大多数文件系统中不存在的功能,例如排序保证和条件写入。 然而,ZooKeeper watch在本质上类似于 AFS 的缓存回调 [16]。

Sinfonia [2] 介绍了一种用于构建可伸缩分布式系统的新范式。 Sinfonia 被设计为存储应用程序数据,而 ZooKeeper 存储应用程序元数据。 ZooKeeper保持其状态完全复制和,保存在内存为了高性能和一致的等待时间。 我们使用文件系统(如操作和排序)功能上类似于mini-transactions。 zNode 是一个方便的抽象,我们在上面添加了watch,这是 Sinfonia 缺少的功能。 dynamo [11] 允许客户端在分布式键值存储中获得和放置相对少量 (小于 1M) 的数据。 与 ZooKeeper 不同,Dynamo 中的键空间不是分层的。 Dynamo 也不能为写入提供强大的持久性和一致性保证,但是解决了读取上的冲突。

DepSpace [4] 使用元组空间来提供 Byzantine 容错服务。 像 ZooKeeper一样 DepSpace使用简单的服务器接口在客户端实现强同步原语。 虽然 DepSpace 的性能比 ZooKeeper 低得多,但它提供了更强的容错和保密性保证。

Conclusions

zookeeper 采用了一种无等待的方法来解决分布式系统中的进程协调问题,方法是向客户端暴露无等待的对象。 我们发现 ZooKeeper 对于 Yahoo! 内外的一些应用是有用的。 Zookeator 通过使用带有watch的快速读取实现了每秒几十万次操作的吞吐量,这些watch都由本地副本提供服务。 尽管我们对读取和watch的一致性保证似乎很弱,但是我们已经用我们的用例表明,这种组合允许我们在客户端实现高效和复杂的协调协议,即使读不是先后顺序的,数据对象的实现是无需等待的。 已经证明,无等待属性对于高性能是必不可少的。

虽然我们只描述了几个应用程序,但还有许多其他应用程序使用 ZooKeeper。 我们相信,我的的成功是由于它的简单的接口,通过这个接口,更强大的抽象可以被实现。 此外,由于 ZozoKeeper 的高吞吐量,应用程序可以广泛地使用它,而不仅仅是粗粒度锁。

Acknowledgements

...

References

[1] M. Abd-El-Malek, G. R. Ganger, G. R. Goodson, M. K. Reiter,
and J. J. Wylie. Fault-scalable byzantine fault-tolerant services.
In SOSP ’05: Proceedings of the twentieth ACM symposium on
Operating systems principles, pages 59–74, New York, NY, USA,
2005. ACM.
[2] M. Aguilera, A. Merchant, M. Shah, A. Veitch, and C. Karamanolis. Sinfonia: A new paradigm for building scalable distributed
systems. In SOSP ’07: Proceedings of the 21st ACM symposium
on Operating systems principles, New York, NY, 2007.
[3] Amazon. Amazon simple queue service. http://aws.
amazon.com/sqs/, 2008.
[4] A. N. Bessani, E. P. Alchieri, M. Correia, and J. da Silva Fraga.
Depspace: A byzantine fault-tolerant coordination service. In
Proceedings of the 3rd ACM SIGOPS/EuroSys European Systems
Conference - EuroSys 2008, Apr. 2008.
[5] K. P. Birman. Replication and fault-tolerance in the ISIS system.
In SOSP ’85: Proceedings of the 10th ACM symposium on Operating systems principles, New York, USA, 1985. ACM Press.
[6] M. Burrows. The Chubby lock service for loosely-coupled distributed systems. In Proceedings of the 7th ACM/USENIX Symposium on Operating Systems Design and Implementation (OSDI),

[7] M. Castro and B. Liskov. Practical byzantine fault tolerance and
proactive recovery. ACM Transactions on Computer Systems,
20(4), 2002.
[8] T. Chandra, R. Griesemer, and J. Redstone. Paxos made live: An
engineering perspective. In Proceedings of the 26th annual ACM
symposium on Principles of distributed computing (PODC), Aug.
2007.
[9] A. Clement, M. Kapritsos, S. Lee, Y. Wang, L. Alvisi, M. Dahlin,
and T. Riche. UpRight cluster services. In Proceedings of the 22
nd ACM Symposium on Operating Systems Principles (SOSP),
Oct. 2009.
[10] J. Cowling, D. Myers, B. Liskov, R. Rodrigues, and L. Shira. Hq
replication: A hybrid quorum protocol for byzantine fault tolerance. In SOSP ’07: Proceedings of the 21st ACM symposium on
Operating systems principles, New York, NY, USA, 2007.
[11] G. DeCandia, D. Hastorun, M. Jampani, G. Kakulapati, A. Lakshman, A. Pilchin, S. Sivasubramanian, P. Vosshall, and W. Vogels. Dynamo: Amazons highly available key-value store. InSOSP ’07: Proceedings of the 21st ACM symposium on Operating systems principles, New York, NY, USA, 2007. ACM Press.
[12] J. Gray, P. Helland, P. O’Neil, and D. Shasha. The dangers of
replication and a solution. In Proceedings of SIGMOD ’96, pages
173–182, New York, NY, USA, 1996. ACM.
[13] A. Hastings. Distributed lock management in a transaction processing environment. In Proceedings of IEEE 9th Symposium on
Reliable Distributed Systems, Oct. 1990.
[14] M. Herlihy. Wait-free synchronization. ACM Transactions on
Programming Languages and Systems, 13(1), 1991.
[15] M. Herlihy and J. Wing. Linearizability: A correctness condition for concurrent objects. ACM Transactions on Programming
Languages and Systems, 12(3), July 1990.
[16] J. H. Howard, M. L. Kazar, S. G. Menees, D. A. Nichols,
M. Satyanarayanan, R. N. Sidebotham, and M. J. West. Scale
and performance in a distributed file system. ACM Trans. Comput. Syst., 6(1), 1988.
[17] Katta. Katta - distribute lucene indexes in a grid. http://
katta.wiki.sourceforge.net/, 2008.
[18] R. Kotla, L. Alvisi, M. Dahlin, A. Clement, and E. Wong.
Zyzzyva: speculative byzantine fault tolerance. SIGOPS Oper.
Syst. Rev., 41(6):45–58, 2007.
[19] N. P. Kronenberg, H. M. Levy, and W. D. Strecker. Vaxclusters (extended abstract): a closely-coupled distributed system.
SIGOPS Oper. Syst. Rev., 19(5), 1985.
[20] L. Lamport. The part-time parliament. ACM Transactions on
Computer Systems, 16(2), May 1998.
[21] J. MacCormick, N. Murphy, M. Najork, C. A. Thekkath, and
L. Zhou. Boxwood: Abstractions as the foundation for storage
infrastructure. In Proceedings of the 6th ACM/USENIX Symposium on Operating Systems Design and Implementation (OSDI),
2004.
[22] L. Moser, P. Melliar-Smith, D. Agarwal, R. Budhia, C. LingleyPapadopoulos, and T. Archambault. The totem system. In Proceedings of the 25th International Symposium on Fault-Tolerant
Computing, June 1995.
[23] S. Mullender, editor. Distributed Systems, 2nd edition. ACM
Press, New York, NY, USA, 1993.
[24] B. Reed and F. P. Junqueira. A simple totally ordered broadcast protocol. In LADIS ’08: Proceedings of the 2nd Workshop
on Large-Scale Distributed Systems and Middleware, pages 1–6,
New York, NY, USA, 2008. ACM.
[25] N. Schiper and S. Toueg. A robust and lightweight stable leader
election service for dynamic systems. In DSN, 2008.
[26] F. B. Schneider. Implementing fault-tolerant services using the
state machine approach: A tutorial. ACM Computing Surveys,
22(4), 1990.
[27] A. Sherman, P. A. Lisiecki, A. Berkheimer, and J. Wein. ACMS:
The Akamai configuration management system. In NSDI, 2005.
[28] A. Singh, P. Fonseca, P. Kuznetsov, R. Rodrigues, and P. Maniatis. Zeno: eventually consistent byzantine-fault tolerance.
In NSDI’09: Proceedings of the 6th USENIX symposium on
Networked systems design and implementation, pages 169–184,
Berkeley, CA, USA, 2009. USENIX Association.
[29] Y. J. Song, F. Junqueira, and B. Reed. BFT for the
skeptics. http://www.net.t-labs.tu-berlin.de/
˜petr/BFTW3/abstracts/talk-abstract.pdf.
[30] R. van Renesse and K. Birman. Horus, a flexible group communication systems. Communications of the ACM, 39(16), Apr.
1996.
[31] R. van Renesse, K. Birman, M. Hayden, A. Vaysburd, and
D. Karr. Building adaptive systems using ensemble. Software

  • Practice and Experience, 28(5), July 1998.
posted @ 2017-11-07 16:05  c_java  阅读(1431)  评论(0编辑  收藏  举报