# 8.3 线性一致（Linearizability）（3）

这里还有另一个简单的请求历史记录的例子。假设我们先写X为1，在这个请求完成之后，有另一个客户端发送了写X为2的请求，并收到了响应说，写入完成。之后，有第三个客户端，发送了一个读X的请求，得到了1。

![](/files/-MCZP4g_hm7B-1u8TAGk)

这是一个很简单的例子，它明显不是线性一致的，因为线性一致要求生成的序列与实际时间匹配，这意味着，唯一可能的序列就是写X为1，之后写X为2，之后读X得到1。但是这个顺序明显违反了线性一致的第二个限制，因为读X得到1的前一个写请求是写X为2，这里读X应该返回2，所以这里明显不是线性一致的。

我提出这个例子的原因是，这是线性一致系统，或者强一致系统不可能提供旧的数据的证据。为什么一个系统有可能会提供旧的数据呢？或许你有大量的副本，每一个副本或许没有看到所有的写请求，或者所有的commit了的写请求。所以，或许所有的副本看到第一个写请求，也就是写X为1的请求，但是只有部分副本看到了第二个写请求，也就是写X为2的请求。所以，当你向一个已经“拖后腿”的副本请求数据时，它仍然只有X的值为1。然而客户端永远也不能在一个线性一致的系统中看到旧的数据（也就是X=1），因为一个线性一致的系统不允许读出旧的数据。

所以这里不是线性一致的，这里的教训是：对于读请求不允许返回旧的数据，只能返回最新的数据。或者说，对于读请求，线性一致系统只能返回最近一次完成的写请求写入的值。

![](/files/-MCZWpw7if6OfdG6IRk3)

好的，我最后还有一个小的例子。现在我们有两个客户端，其中一个提交了一个写X为3的请求，之后是一个写X为4的请求。同时，我们还有另一个客户端，在这个时间点，客户端发出了一个读X的请求，但是客户端没有收到回复。

![](/files/-MCZXOjdCGd3bze4jf6F)

在一个实际的系统实现中，可能有任何原因导致这个结果，例如：

* Leader在某个时间故障了
* 这个客户端发送了一个读请求，但是这个请求丢包了因此Leader没有收到这个请求
* Leader收到了这个读请求并且执行了它，但是回复的报文被网络丢包了
* Leader收到了请求并开始执行，在完成执行之前故障了
* Leader执行了这个请求，但是在返回响应的时候故障了

不管是哪种原因，从客户端的角度来看，就是发送了一个请求，然后就没有回复了。在大多数系统的客户端内部实现机制中，客户端将会重发请求，或许发给一个不同的Leader，或许发送给同一个Leader。所以，客户端发送了第一个请求，之后没有收到回复并且超时之后，或许在这里发送了第二个请求。

![](/files/-MCZmo2V5LvSCf2uDpZ7)

之后，终于收到了一个回复。这将是Lab3的一个场景。

![](/files/-MCZnR4O4w_F7Ad3rMwQ)

服务器处理重复请求的合理方式是，服务器会根据请求的唯一号或者其他的客户端信息来保存一个表。这样服务器可以记住，哦，我之前看过这个请求，并且执行过它，我会发送一个相同的回复给它，因为我不想执行相同的请求两次。例如，假设这是一个写请求，你不会想要执行这个请求两次。所以，服务器必须要有能力能够过滤出重复的请求。第一个请求的回复可能已经被网络丢包了。所以，服务器也必须要有能力能够将之前发给第一个请求的回复，再次发给第二个重复的请求。所以，服务器记住了最初的回复，并且在客户端重发请求的时候将这个回复返回给客户端。如果服务器这么做了，那么因为服务器或者Leader之前执行第一个读请求的时候，可能看到的是X=3，那么它对于重传的请求，可能还是会返回X=3。所以，我们必须要决定，这是否是一个合法的行为。

你可能会说，客户端在这里发送的（重传）请求，这在写X为4的请求之后，所以你这里应该返回4，而不是3。

![](/files/-MCZpCydiVoitwHiuEJG)

这里取决于设计者，但是重传本身是一个底层的行为，或许在RPC的实现里面，或许在一些库里面实现。但是从客户端程序的角度来说，它只知道从第一条竖线的位置发送了一个请求，

![](/files/-MCZqL7sbqxpoFywtdCJ)

并在第二条竖线的位置收到了一个回复，

![](/files/-MCZqVGJLCHxG5e1Px-E)

这是从客户端角度看到的所有事情。所以，返回X为3是完全合法的，因为这个读请求花费了一个很长的时间，它与写X为4的请求是完全并发的，而不是串行的。

因此，对于这个读请求，返回3或者4都是合法的。取决于这个读请求实际上是在这里执行，

![](/files/-MCZsGH-wzmV4PlgYr2C)

还是在这里执行。

![](/files/-MCZsMndPPUTTyLQzPwW)

所以，如果你的客户端有重传，并且你要从客户端的角度来定义线性一致，那么一个请求的区间从第一次传输开始，到最后应用程序实际收到响应为止，期间可能发生了很多次重传。

> 学生提问：如果客户端想要看到的是最新的数据而不是旧数据呢？
>
> Robert教授：你在这里宁愿得到最新的数据而不是老旧的数据。假设这里的请求是查询当前时间，我向服务器发送个请求说，现在是几点，服务器返回给我一个响应。现在如果我发送了一个请求，2分钟过去了因为网络问题，我还没收到任何回复。或许应用程序更喜欢看到的回复是更近的时间，而不是很久之前开始发送请求的时间。现在，事实是，如果你使用一个线性一致的系统，你必须要实现能够容纳线性一致规则的程序。你必须写出正确的应用程序来容忍这样一个场景：应用程序发出了一个请求，过了一会才收到回复，比如在这里，如果我得到了一个值是3的回复，这对于应用程序来说可能不能接受这个值。因为这意味着，我在收到响应的时候，系统中X存储的值是3，这与事实不符（实际上X=4）。所以这里最终取决于应用程序本身。

你们在实验中会完成这样的机制，服务器发现了重复的请求，并将之前的回复重新发给客户端。这里的问题是，服务器最初在这里看到了请求，最后回复的数据是本应在之前一个时间点回复的数据，这样是否合理？我们使用线性一致的定义的一个原因是，它可以用来解释问题。例如，在这个场景里面，我们可以说，这样的行为符合线性一致的原则。

好的，这就是所有我想介绍的有关线性一致的东西。在期中测试我必然会问一个线性一致的问题。


---

# 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.3-xian-xing-yi-zhi-linearizability3.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.
