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.3 线性一致(Linearizability)(3)

上一页8.2 线性一致(Linearizability)(2)下一页8.4 Zookeeper

最后更新于4年前

这有帮助吗?

这里还有另一个简单的请求历史记录的例子。假设我们先写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)。所以这里最终取决于应用程序本身。

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

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