-
ZooKeeper旨在提供一个简单和高性能的内核,使得客户端可以构建更复杂的协调原语。 它将组消息传递、共享寄存器和分布式锁等服务整合到一个重新分配的、集中的服务中。 由ZooKeeper暴露出来的接口在共享寄存器方面具有无等待的特性,使用类似于分布式文件系统缓存失效的事件驱动机制来提供简单但是强大的协调服务。
-
ZooKeeper 还为每个客户端提供 FIFO 执行请求的保证。将所有改变ZooKeeper 状态的请求线性化。 这些设计使得实现高性能处理流水线成为可能,满足了本地服务器读的请求。
-
ZooKeeper,实现了一个 API,它可以操作像文件系统那样分层组织的简单的无等待数据对象。
-
ZooKeeper 服务是一个服务器集群,使用复制来实现高可用性和高性能
-
使用 ZooKeeper ,我们能够实现我们的应用程序所需要的所有协调原语,即使只有写入是可线性化的。
-
ZooKeeper不使用句柄来访问 Znode,而是通过完整路径来访问。 这个选择不仅简化了 API(没有 open () 或 close () 方法),而且还消除了服务器需要维护的额外状态。
-
ZooKeeper有两个基本的顺序保证: 线性写:所有更新ZooKeeper状态的操作是串行的,并且可以服从优先级; FIFO客户端顺序:来自客户端的所有请求按客户端发送的顺序依次执行。
-
写请求被转发到一个称为 leader 的服务器,其余的ZooKeeper服务器,称为Follower,它们从leader接收消息(主要是状态),并商定状态更改
-
请求处理器(Request Processor)
- 当leader接收到写请求时,它计算当应用写入时系统的状态将是什么,并且将它转换成捕获这个新状态的事务。 由于可能存在尚未应用于数据库的可能存在的事务,因此必须计算该状态。
-
原子广播(Atomic Broadcast)
- 所有更新ZooKeeper状态的请求都被转发到leader。 leader执行这个请求并通过 一个原子广播协议ZAB 向ZooKeepeer广播变更。接收客户端请求的服务器,在传递其对应的状态改变时,响应客户端(watch)。
- ZAB 保证由leader广播的改变按照它们被发送的顺序递送,并且来自先前leader的所有改变在广播其自身改变之前被递送到一个已经建立的leader。
- 使用日志来跟踪提议,作为内存中数据库的预写日志(write-ahead log,WAL),这样我们就不必两次将信息写入磁盘。
- ZooKeeper 要求 ZAB 至少重新发送在上次快照开始之后交付的所有消息。(与raft类似)
-
复制数据库(Replicated Database)
- 每个副本都有一个在内存里的ZooKeeper 状态的副本。 当管理员服务器从崩溃中恢复时,它需要恢复此内部状态。ZooKeeper 使用定期快照,只需要自快照开始后重新发送消息。
-
客户端与服务器端的交互(Client-Server Interactions)
- 当服务器处理写请求时,它还发送并清除与该更新相对应的任何watch相关的通知。Servers 按顺序处理写入, 并且不同时处理其他写入或读取。 这确保了通知的严格连续。 注意服务器只处理本地通知。 只有客户端所连接到的服务器才跟踪并触发该客户端的通知。
- Read 请求在每个服务器上本地处理。 每个读请求都被处理并标记有一个 zxid,该 zxid 对应于服务器看到的最后一个事务。
- 使用快速读取的一个缺点是不能保证读取操作的优先顺序。 也就是说,即使已经提交了对同一 Znode 的最近更新,读操作也可以返回旧值。
- ZooKeeper 服务器以 FIFO 顺序处理来自客户端的请求,并响应与之相关的 zxid。 甚至在没有活动的间隔期间的心跳消息,也包括了客户端连接到的服务器看到的最后一个 zxid
- 如果客户端连接到新服务器,则该新服务器通过对照客户端的最后 zxid 检查客户端的最后 zxid 来确保其对 ZooKeeper 数据的视图至少与客户端的视图一样近。 如果客户端具有比服务器更近的视图,则服务器直到追赶上才与客户端建立连接。
- 如果在会话超时内没有其他服务器从客户端接收到任何信息, leader将确定发生了故障。 如果客户端发送的请求足够频繁,则不需要发送任何其他消息
-
ZooKeeper数据模型
- 状态: 一种类似文件系统的 znode 树形结构。
- 文件名、文件内容、目录、路径名: 这个状态树包含文件名、文件内容、目录和路径名等元素,形成了一种层次结构。
- 每个 znode 都有一个版本号: 用于跟踪 znode 的变更历史,确保更新的一致性。
- znode 的类型:
- 普通(regular): 常规类型的 znode,用于存储数据和状态。
- 临时(ephemeral): 临时性的 znode,其生命周期与创建它的客户端会话相关。
- 顺序(sequential): 有序的 znode,其名称包括原始名称和序列号,确保创建的有序性。
-
人们如何使用ZooKeeper的znodes?
- 存储配置信息:
- znodes可以包含例如MapReduce(MR)协调器的IP地址、工作节点的IP地址、MR作业描述,以及哪些MR任务被分配或已完成等配置信息。
- 状态表示:
- znode的存在可以表示活跃的MR协调器是否存在,或者表示某个工作节点正在广告自己的存在。
- 分层组织:
- 利用分层结构的路径名,方便不同应用程序共享单个ZooKeeper服务器,并组织每个应用程序的不同类型数据。例如,可以为工作节点广告存在创建一个目录,为作业集创建另一个目录等。
- 通知和事件:
- 可以使用znodes来实现事件通知机制,当某个节点的状态发生变化时,通知其他节点。
- 存储配置信息:
-
sync()
sync
然后read
确保在同一客户端的sync
之前的写入对同一客户端的读取可见。- 客户端也可以选择提交写入,而不是使用
sync
。
-
Example: MapReduce coordinator electionticker():while true:if create("/mr/c", ephemeral=true)act as master...else if exists("/mr/c", watch=true)wait for watch eventelsetry againretire():delete("/mr/c")
-
如果协调器仍然运行并且认为自己仍然是协调器,但是ZooKeeper已经判断它已经死亡并删除了它的临时znode(例如,
/mr/c
),那么很可能会选举出新的协调器。这种情况下,两台计算机可能会认为它们都是协调器,存在竞选冲突的可能性。但是,旧的协调器在ZooKeeper中不能修改状态。因为当ZooKeeper超时一个客户端会话时,它会原子地执行两个操作:
- 删除客户端的临时znodes。
- 停止监听该会话。
这意味着一旦ZooKeeper确定旧协调器的会话已超时,旧协调器将无法再向ZooKeeper发送请求。一旦旧协调器意识到会话已经失效,它可以创建一个新的会话,但此时它知道自己不再是协调器。因此,虽然可能存在一小段时间内的竞争情况,但在会话超时后,ZooKeeper会自动防止旧的协调器修改状态,确保新的协调器能够顺利被选举。
-
ZooKeeper(ZK)提供以下保证:
- 所有写操作的单一顺序(Zxid顺序):
- ZK基于ZooKeeper领导者的日志顺序实施所有写操作的单一顺序,称为“zxid”(ZooKeeper事务ID)。
- 所有客户端都按照zxid顺序看到写入,包括其他客户端的写入。
- 对客户端的一致视图:
- 客户端的读操作看到客户端之前的所有写入。
- 客户端的ZooKeeper跟随者可能需要等待,直到ZooKeeper领导者确认了客户端的最新写入。
- 顺序执行和观察机制保证:
- 写入按照zxid顺序顺序执行,这对于像小型事务(例如,exclusive create())等操作至关重要。
- 客户端保证在观察到触发事件之前看到该事件之后的写入值。
- 所有写操作的单一顺序(Zxid顺序):
-
ZooKeeper的实现旨在实现高性能:
- 数据必须适应内存:
- 数据必须适应内存,以便读取速度快(无需从磁盘读取)。
- 因此,不能在ZooKeeper中存储大量的数据。
- 写入(日志条目)必须写入磁盘并等待:
- 写入(即日志条目)必须写入磁盘,并等待确认。
- 这样可以确保在发生崩溃或电源故障时,已提交的更新不会丢失。
- 这会影响延迟;通过批处理可以提高吞吐量。
- 定期完整快照写入磁盘:
- 定期进行完整快照写入磁盘。
- 使用模糊技术,这意味着快照写入是与请求处理并发进行的。
- 数据必须适应内存:
-
对于6.824课程而言,其中一个引人注目的想法是通过将关键状态保留在容错存储系统(例如ZooKeeper)中,同时在非容错服务器上运行计算,实现容错
- 例如,MapReduce主节点可以在ZooKeeper中保存有关作业、任务状态、工作节点、中间输出位置等的状态信息。如果主节点发生故障,可以选择新的计算机来运行MapReduce主节点软件,并从ZooKeeper加载其状态。这样可以为主节点提供容错,而无需使用状态机复制的复杂性(例如,无需编写使用Raft库的MapReduce主节点)。可以将这种方法看作通过使状态本身具有容错性来提供容错,而使用Raft使整个计算具有容错性。这种通用模式并非新鲜事物,例如数据库支持的网站就是采用这种方式运作的,但如果你的主要关注点是管理容错服务,ZooKeeper则是一个很好的选择。
-
Zookeeper存储的是状态的改变,针对于应用程序。Raft存储的是应用程序的所有计算步骤
-
问:为什么只有更新请求是A-可线性化的?为什么读取不是?
- 作者希望获得高总读取吞吐量,因此他们希望ZooKeeper副本能够在不涉及领导者的情况下满足客户端读取。给定的副本可能不知道已提交的写入(如果它不在领导者等待的大多数中),或者可能知道写入但尚不知道它是否已提交。因此,副本的状态可能落后于领导者和其他副本。因此,从副本提供读取可能会产生不反映最近写入的数据,即读取可能返回陈旧的结果。
-
什么是流水线处理(pipelining)?
- 首先,ZooKeeper领导者(实际上是领导者的Zab层)批处理多个客户端操作,以便有效地通过网络发送它们,并以有效的方式将它们写入磁盘。对于网络和磁盘来说,一次性发送或写入N个小项目通常比逐个发送或写入更有效。这种批处理仅在领导者同时看到许多客户端请求时才有效;因此,它取决于有很多活跃的客户端。
- ZooKeeper通过支持异步操作,使每个客户端能够同时保留许多待处理的写入请求。从客户端的角度来看,它可以发送大量写入请求,而无需等待响应(响应稍后作为写入提交后的通知到达)。从领导者的角度来看,这种客户端行为使领导者能够将请求积累到大的高效批处理中。
- 对于流水线处理的一个担忧是,传播中的操作可能被重新排序,这将导致作者在2.3中谈到的问题。如果领导者正在进行许多写入操作,然后进行“写就绪”操作,不希望对这些操作进行重新排序,因为其他客户端可能在先前的写入被应用之前观察到“就绪”。为确保这种情况不会发生,ZooKeeper保证客户端操作的FIFO;也就是说,客户端操作将按照它们被发出的顺序进行应用。
-
如果客户端提交了一个异步写入,并在此之后立即进行读取,读取是否会看到写入的效果?
答: 论文没有明确说明,但第2.3节的“FIFO客户端顺序”属性的暗示是读取将会看到写入的效果。这似乎意味着ZooKeeper跟随者可能会阻塞读取,直到跟随者收到(从ZooKeeper领导者那里)客户端所有先前写入的操作。ZooKeeper可能通过在读取请求中发送客户端提交的最新先前操作的zxid来管理这一点。
-
实现“模糊快照”的原因是什么?
- 精确的快照对应于日志中的特定点:快照将包含该点之前的每个写入,并且不包含该点之后的任何写入;而且可以清楚地知道在重新启动后从何处开始回放日志条目以使快照保持最新。然而,创建精确的快照需要一种在创建和写入快照时阻止任何写入发生的方法。在创建快照的过程中阻止写入可能会显著降低性能。
- ZooKeeper模糊快照的要点在于,ZooKeeper在允许对数据库进行写入的同时,从其内存数据库创建快照。这意味着快照并不对应于日志中的特定点,即快照包含与创建快照并发的写入的一个或多个更多或更少的随机子集。在重新启动后,ZooKeeper通过从创建快照的点开始回放所有日志条目来构造一致的快照。由于ZooKeeper中的更新是幂等的且以相同的顺序传递的,因此在重新启动和重放之后,应用程序状态将是正确的。某些消息可能会被应用两次(一次是在恢复之前的状态,一次是在恢复之后),但这是可以接受的,因为它们是幂等的。重播修复了模糊快照,使其成为应用程序状态的一致快照。
- Zookeeper领导者将客户端API中的操作转化为幂等事务。例如,如果客户端发出有条件的setData请求,且请求中的版本号匹配,则Zookeeper领导者创建一个包含新数据、新版本号和更新时间戳的setDataTXN。此事务(TXN)是幂等的:Zookeeper可以执行两次,结果将是相同的状态。