8.3 线性一致(Linearizability)(3)
最后更新于
最后更新于
这里还有另一个简单的请求历史记录的例子。假设我们先写X为1,在这个请求完成之后,有另一个客户端发送了写X为2的请求,并收到了响应说,写入完成。之后,有第三个客户端,发送了一个读X的请求,得到了1。
这是一个很简单的例子,它明显不是线性一致的,因为线性一致要求生成的序列与实际时间匹配,这意味着,唯一可能的序列就是写X为1,之后写X为2,之后读X得到1。但是这个顺序明显违反了线性一致的第二个限制,因为读X得到1的前一个写请求是写X为2,这里读X应该返回2,所以这里明显不是线性一致的。
我提出这个例子的原因是,这是线性一致系统,或者强一致系统不可能提供旧的数据的证据。为什么一个系统有可能会提供旧的数据呢?或许你有大量的副本,每一个副本或许没有看到所有的写请求,或者所有的commit了的写请求。所以,或许所有的副本看到第一个写请求,也就是写X为1的请求,但是只有部分副本看到了第二个写请求,也就是写X为2的请求。所以,当你向一个已经“拖后腿”的副本请求数据时,它仍然只有X的值为1。然而客户端永远也不能在一个线性一致的系统中看到旧的数据(也就是X=1),因为一个线性一致的系统不允许读出旧的数据。
所以这里不是线性一致的,这里的教训是:对于读请求不允许返回旧的数据,只能返回最新的数据。或者说,对于读请求,线性一致系统只能返回最近一次完成的写请求写入的值。
好的,我最后还有一个小的例子。现在我们有两个客户端,其中一个提交了一个写X为3的请求,之后是一个写X为4的请求。同时,我们还有另一个客户端,在这个时间点,客户端发出了一个读X的请求,但是客户端没有收到回复。
在一个实际的系统实现中,可能有任何原因导致这个结果,例如:
Leader在某个时间故障了
这个客户端发送了一个读请求,但是这个请求丢包了因此Leader没有收到这个请求
Leader收到了这个读请求并且执行了它,但是回复的报文被网络丢包了
Leader收到了请求并开始执行,在完成执行之前故障了
Leader执行了这个请求,但是在返回响应的时候故障了
不管是哪种原因,从客户端的角度来看,就是发送了一个请求,然后就没有回复了。在大多数系统的客户端内部实现机制中,客户端将会重发请求,或许发给一个不同的Leader,或许发送给同一个Leader。所以,客户端发送了第一个请求,之后没有收到回复并且超时之后,或许在这里发送了第二个请求。
之后,终于收到了一个回复。这将是Lab3的一个场景。
服务器处理重复请求的合理方式是,服务器会根据请求的唯一号或者其他的客户端信息来保存一个表。这样服务器可以记住,哦,我之前看过这个请求,并且执行过它,我会发送一个相同的回复给它,因为我不想执行相同的请求两次。例如,假设这是一个写请求,你不会想要执行这个请求两次。所以,服务器必须要有能力能够过滤出重复的请求。第一个请求的回复可能已经被网络丢包了。所以,服务器也必须要有能力能够将之前发给第一个请求的回复,再次发给第二个重复的请求。所以,服务器记住了最初的回复,并且在客户端重发请求的时候将这个回复返回给客户端。如果服务器这么做了,那么因为服务器或者Leader之前执行第一个读请求的时候,可能看到的是X=3,那么它对于重传的请求,可能还是会返回X=3。所以,我们必须要决定,这是否是一个合法的行为。
你可能会说,客户端在这里发送的(重传)请求,这在写X为4的请求之后,所以你这里应该返回4,而不是3。
这里取决于设计者,但是重传本身是一个底层的行为,或许在RPC的实现里面,或许在一些库里面实现。但是从客户端程序的角度来说,它只知道从第一条竖线的位置发送了一个请求,
并在第二条竖线的位置收到了一个回复,
这是从客户端角度看到的所有事情。所以,返回X为3是完全合法的,因为这个读请求花费了一个很长的时间,它与写X为4的请求是完全并发的,而不是串行的。
因此,对于这个读请求,返回3或者4都是合法的。取决于这个读请求实际上是在这里执行,
还是在这里执行。
所以,如果你的客户端有重传,并且你要从客户端的角度来定义线性一致,那么一个请求的区间从第一次传输开始,到最后应用程序实际收到响应为止,期间可能发生了很多次重传。
学生提问:如果客户端想要看到的是最新的数据而不是旧数据呢?
Robert教授:你在这里宁愿得到最新的数据而不是老旧的数据。假设这里的请求是查询当前时间,我向服务器发送个请求说,现在是几点,服务器返回给我一个响应。现在如果我发送了一个请求,2分钟过去了因为网络问题,我还没收到任何回复。或许应用程序更喜欢看到的回复是更近的时间,而不是很久之前开始发送请求的时间。现在,事实是,如果你使用一个线性一致的系统,你必须要实现能够容纳线性一致规则的程序。你必须写出正确的应用程序来容忍这样一个场景:应用程序发出了一个请求,过了一会才收到回复,比如在这里,如果我得到了一个值是3的回复,这对于应用程序来说可能不能接受这个值。因为这意味着,我在收到响应的时候,系统中X存储的值是3,这与事实不符(实际上X=4)。所以这里最终取决于应用程序本身。
你们在实验中会完成这样的机制,服务器发现了重复的请求,并将之前的回复重新发给客户端。这里的问题是,服务器最初在这里看到了请求,最后回复的数据是本应在之前一个时间点回复的数据,这样是否合理?我们使用线性一致的定义的一个原因是,它可以用来解释问题。例如,在这个场景里面,我们可以说,这样的行为符合线性一致的原则。
好的,这就是所有我想介绍的有关线性一致的东西。在期中测试我必然会问一个线性一致的问题。