MIT6.824
  • 简介
  • Lecture 01 - Introduction
    • 1.1 分布式系统的驱动力和挑战(Drivens and Challenges)
    • 1.2 课程结构(Course Structure)
    • 1.3 分布式系统的抽象和实现工具(Abstraction and Implementation)
    • 1.4 可扩展性(Scalability)
    • 1.5 可用性(Availability)
    • 1.6 一致性(Consistency)
    • 1.7 MapReduce基本工作方式
    • 1.8 Map函数和Reduce函数
  • Lecture 03 - GFS
    • 3.1分布式存储系统的难点(Why Hard)
    • 3.2 错误的设计(Bad Design)
    • 3.3 GFS的设计目标
    • 3.4 GFS Master 节点
    • 3.5 GFS读文件(Read File)
    • 3.6 GFS写文件(Write File)(1)
    • 3.7 GFS写文件(Write File)(2)
    • 3.8 GFS的一致性
  • Lecture 04 - VMware FT
    • 4.1 复制(Replication)
    • 4.2 状态转移和复制状态机(State Transfer and Replicated State Machine)
    • 4.3 VMware FT 工作原理
    • 4.4 非确定性事件(Non-Deterministic Events)
    • 4.5 输出控制(Output Rule)
    • 4.6 重复输出(Duplicated Output)
    • 4.7 Test-and-Set 服务
  • Lecture 06 - Raft1
    • 6.1 脑裂(Split Brain)
    • 6.2 过半票决(Majority Vote)
    • 6.3 Raft 初探
    • 6.4 Log 同步时序
    • 6.5 日志(Raft Log)
    • 6.6 应用层接口
    • 6.7 Leader选举(Leader Election)
    • 6.8 选举定时器(Election Timer)
    • 6.9 可能的异常情况
  • Lecture 07 - Raft2
    • 7.1 日志恢复(Log Backup)
    • 7.2 选举约束(Election Restriction)
    • 7.3 快速恢复(Fast Backup)
    • 7.4 持久化(Persistence)
    • 7.5 日志快照(Log Snapshot)
    • 7.6 线性一致(Linearizability)
  • Lecture 08 - Zookeeper
    • 8.1 线性一致(Linearizability)(1)
    • 8.2 线性一致(Linearizability)(2)
    • 8.3 线性一致(Linearizability)(3)
    • 8.4 Zookeeper
    • 8.5 一致保证(Consistency Guarantees)
    • 8.6 同步操作(sync)
    • 8.7 就绪文件(Ready file/znode)
  • Lecture 09 - More Replication, CRAQ
    • 9.1 Zookeeper API
    • 9.2 使用Zookeeper实现计数器
    • 9.3 使用Zookeeper实现非扩展锁
    • 9.4 使用Zookeeper实现可扩展锁
    • 9.5 链复制(Chain Replication)
    • 9.6 链复制的故障恢复(Fail Recover)
    • 9.7 链复制的配置管理器(Configuration Manager)
  • Lecture 10 - Cloud Replicated DB, Aurora
    • 10.1 Aurora 背景历史
    • 10.2 故障可恢复事务(Crash Recoverable Transaction)
    • 10.3 关系型数据库(Amazon RDS)
    • 10.4 Aurora 初探
    • 10.5 Aurora存储服务器的容错目标(Fault-Tolerant Goals)
    • 10.6 Quorum 复制机制(Quorum Replication)
    • 10.7 Aurora读写存储服务器
    • 10.8 数据分片(Protection Group)
    • 10.9 只读数据库(Read-only Database)
  • Lecture 11 - Cache Consistency: Frangipani
    • 11.1 Frangipani 初探
    • 11.2 Frangipani的挑战(Challenges)
    • 11.3 Frangipani的锁服务(Lock Server)
    • 11.4 缓存一致性(Cache Coherence)
    • 11.5 原子性(Atomicity)
    • 11.6 Frangipani Log
    • 11.7 故障恢复(Crash Recovery)
    • 11.8 Frangipani总结
  • Lecture 12 - Distributed Transaction
    • 12.1 分布式事务初探(Distributed Transaction)
    • 12.2 并发控制(Concurrency Control)
    • 12.3 两阶段提交(Two-Phase Commit)
    • 12.4 故障恢复(Crash Recovery)
    • 12.5 总结
由 GitBook 提供支持
在本页

这有帮助吗?

  1. Lecture 08 - Zookeeper

8.7 就绪文件(Ready file/znode)

上一页8.6 同步操作(sync)下一页Lecture 09 - More Replication, CRAQ

最后更新于4年前

这有帮助吗?

在论文中有几个例子场景,通过Zookeeper的一致性保证可以很简答的解释它们。

首先我想介绍的是论文中2.3有关Ready file的一些设计(这里的file对应的就是论文里的znode,Zookeeper以文件目录的形式管理数据,所以每一个数据点也可以认为是一个file)。

我们假设有另外一个分布式系统,这个分布式有一个Master节点,而Master节点在Zookeeper中维护了一个配置,这个配置对应了一些file(也就是znode)。通过这个配置,描述了有关分布式系统的一些信息,例如所有worker的IP地址,或者当前谁是Master。所以,现在Master在更新这个配置,同时,或许有大量的客户端需要读取相应的配置,并且需要发现配置的每一次变化。所以,现在的问题是,尽管配置被分割成了多个file,我们还能有原子效果的更新吗?

为什么要有原子效果的更新呢?因为只有这样,其他的客户端才能读出完整更新的配置,而不是读出更新了一半的配置。这是人们使用Zookeeper管理配置文件时的一个经典场景。

我们这里直接拷贝论文中的2.3节的内容。假设Master做了一系列写请求来更新配置,那么我们的分布式系统中的Master会以这种顺序执行写请求。首先我们假设有一些Ready file,就是以Ready为名字的file。如果Ready file存在,那么允许读这个配置。如果Ready file不存在,那么说明配置正在更新过程中,我们不应该读取配置。所以,如果Master要更新配置,那么第一件事情是删除Ready file。之后它会更新各个保存了配置的Zookeeper file(也就是znode),这里或许有很多的file。当所有组成配置的file都更新完成之后,Master会再次创建Ready file。目前为止,这里的语句都很直观,这里只有写请求,没有读请求,而Zookeeper中写请求可以确保以线性顺序执行。

为了确保这里的执行顺序,Master以某种方式为这些请求打上了tag,表明了对于这些写请求期望的执行顺序。之后Zookeeper Leader需要按照这个顺序将这些写请求加到多副本的Log中。

接下来,所有的副本会履行自己的职责,按照这里的顺序一条条执行请求。它们也会删除(自己的)Ready file,之后执行这两个写请求,最后再次创建(自己的)Ready file。所以,这里是写请求,顺序还是很直观的。

对于读请求,需要更多的思考。假设我们有一些worker节点需要读取当前的配置。我们可以假设Worker节点首先会检查Ready file是否存在。如果不存在,那么Worker节点会过一会再重试。所以,我们假设Ready file存在,并且是经历过一次重新创建。

这里的意思是,左边的都是发送给Leader的写请求,右边是一个发送给某一个与客户端交互的副本的读请求。之后,如果文件存在,那么客户端会接下来读f1和f2。

这里,有关FIFO客户端序列中有意思的地方是,如果判断Ready file的确存在,那么也是从与客户端交互的那个副本得出的判断。所以,这里通过读请求发现Ready file存在,可以说明那个副本看到了Ready file的重新创建这个请求(由Leader同步过来的)。

同时,因为后续的读请求永远不会在更早的log条目号执行,必须在更晚的Log条目号执行,所以,对于与客户端交互的副本来说,如果它的log中包含了这条创建Ready file的log,那么意味着接下来客户端的读请求只会在log中更后面的位置执行(下图中横线位置)。

所以,如果客户端看见了Ready file,那么副本接下来执行的读请求,会在Ready file重新创建的位置之后执行。这意味着,Zookeeper可以保证这些读请求看到之前对于配置的全部更新。所以,尽管Zookeeper不是完全的线性一致,但是由于写请求是线性一致的,并且读请求是随着时间在Log中单调向前的,我们还是可以得到合理的结果。

学生提问:听不清

Robert教授:这是一个很好的问题,你的问题是,在一个实际场景中,会有更多的不确定因素。让我们来看一个更麻烦的场景,这个场景正好我也准备讲。

我们假设Master在完成配置更新之后创建了Ready file。之后Master又要更新配置,那么最开始,它要删除Ready file,之后再执行一些写请求。

这里可能有的问题是,需要读取配置的客户端,首先会在这个点,通过调用exist来判断Ready file是否存在。

在这个时间点,Ready file肯定是存在的。之后,随着时间的推移,客户端读取了组成配置的第一个file,但是,之后在读取第二个file时,Master可能正在更新配置。

所以现在客户端读到的是一个不正常的,由旧配置的f1和新配置的f2组成的配置。没有理由相信,这里获取的信息还是有用的。所以,前一个场景还是很美好的,但是这个场景就是个灾难。

所以,我们现在开始面对一个严重的挑战,而一个仔细设计的针对分布式系统中机器间的协调服务的API(就是说Zookeeper),或许可以帮助我们解决这个挑战。对于Lab3来说,你将会构建一个put/get系统,那样一个系统,也会遇到同样的问题,没有任何现有的工具可以解决这个问题。

Zookeeper的API实际上设计的非常巧妙,它可以处理这里的问题。之前说过,客户端会发送exists请求来查询,Ready file是否存在。但是实际上,客户端不仅会查询Ready file是否存在,还会建立一个针对这个Ready file的watch。

这意味着如果Ready file有任何变更,例如,被删除了,或者它之前不存在然后被创建了,副本会给客户端发送一个通知。在这个场景中,如果Ready file被删除了,副本会给客户端发送一个通知。

客户端在这里只与某个副本交互,所以这里的操作都是由副本完成。当Ready file有变化时,副本会确保,合适的时机返回对于Ready file变化的通知。这里什么意思呢?在这个场景中,这些写请求在实际时间中,出现在读f1和读f2之间。

而Zookeeper可以保证,如果客户端向某个副本watch了某个Ready file,之后又发送了一些读请求,当这个副本执行了一些会触发watch通知的请求,那么Zookeeper可以确保副本将watch对应的通知,先发给客户端,再处理触发watch通知请求(也就是删除Ready file的请求),在Log中位置之后才执行的读请求(有点绕,后面会有更多的解释)。

这里再来看看Log。FIFO客户端序列要求,每个客户端请求都存在于Log中的某个位置,所以,最后log的相对位置如下图所示:

我们之前已经设置好了watch,Zookeeper可以保证如果某个人删除了Ready file,相应的通知,会在任何后续的读请求之前,发送到客户端。客户端会先收到有关Ready file删除的通知,之后才收到其他在Log中位于删除Ready file之后的读请求的响应。这里意味着,删除Ready file会产生一个通知,而这个通知可以确保在读f2的请求响应之前发送给客户端。

这意味着,客户端在完成读所有的配置之前,如果对配置有了新的更改,Zookeeper可以保证客户端在收到删除Ready file的通知之前,看到的都是配置更新前的数据(也就是,客户端读取配置读了一半,如果收到了Ready file删除的通知,就可以放弃这次读,再重试读了)。

学生提问:谁出发了这里的watch?

Robert教授:假设这个客户端与这个副本在交互,它发送了一个exist请求,exist请求是个只读请求。相应的副本在一个table上生成一个watch的表单,表明哪些客户端watch了哪些file。

并且,watch是基于一个特定的zxid建立的,如果客户端在一个副本log的某个位置执行了读请求,并且返回了相对于这个位置的状态,那么watch也是相对于这个位置来进行。如果收到了一个删除Ready file的请求,副本会查看watch表单,并且发现针对这个Ready file有一个watch。watch表单或许是以file名的hash作为key,这样方便查找。

学生提问:这个副本必须要有一个watch表单,如果副本故障了,客户端需要连接到另外一个副本,那新连接的副本中的watch表单如何生成呢?

Robert教授:答案是,如果你的副本故障了,那么切换到的新的副本不会有watch表单。但是客户端在相应的位置会收到通知说,你正在交互的副本故障了,之后客户端就知道,应该重置所有数据,并与新的副本建立连接(包括watch)。

下一节课会继续介绍Zookeeper。