# 8.7 就绪文件（Ready file/znode）

在论文中有几个例子场景，通过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中写请求可以确保以线性顺序执行。

![](/files/-MD4cn4IV7ww5YrGiKt_)

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

![](/files/-MD4cwZvdgnIEPe1yniK)

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

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

![](/files/-MD4dXqscEvY4Gy14vni)

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

![](/files/-MD4dnXKz2f5-2Fco1uk)

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

![](/files/-MD4eAZpNeDAD5J6_AsP)

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

![](/files/-MD4f-dq24Mw2bYKK2eO)

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

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

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

![](/files/-MD4gh5ZvfawnjD8EVmA)

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

![](/files/-MD4gwvRkWWR9tsU_200)

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

![](/files/-MD4hCfYWzQOi7wI2JXT)

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

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

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

![](/files/-MD4i6qRy1PiLjnuAY94)

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

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

![](/files/-MD4ieuIHdqYr1Mzd6po)

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

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

![](/files/-MD4jXc9UYccW3nsX1yO)

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

![](/files/-MD4kccYu9ubDOcG15Ac)

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

> 学生提问：谁出发了这里的watch？
>
> Robert教授：假设这个客户端与这个副本在交互，它发送了一个exist请求，exist请求是个只读请求。相应的副本在一个table上生成一个watch的表单，表明哪些客户端watch了哪些file。

![](/files/-MD4kxVi0CYsgDb34H20)

> 并且，watch是基于一个特定的zxid建立的，如果客户端在一个副本log的某个位置执行了读请求，并且返回了相对于这个位置的状态，那么watch也是相对于这个位置来进行。如果收到了一个删除Ready file的请求，副本会查看watch表单，并且发现针对这个Ready file有一个watch。watch表单或许是以file名的hash作为key，这样方便查找。
>
> 学生提问：这个副本必须要有一个watch表单，如果副本故障了，客户端需要连接到另外一个副本，那新连接的副本中的watch表单如何生成呢？
>
> Robert教授：答案是，如果你的副本故障了，那么切换到的新的副本不会有watch表单。但是客户端在相应的位置会收到通知说，你正在交互的副本故障了，之后客户端就知道，应该重置所有数据，并与新的副本建立连接（包括watch）。

下一节课会继续介绍Zookeeper。


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://mit-public-courses-cn-translatio.gitbook.io/mit6-824/lecture-08-zookeeper/8.7-jiu-xu-wen-jian-ready-fileznode.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
