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 09 - More Replication, CRAQ

9.3 使用Zookeeper实现非扩展锁

上一页9.2 使用Zookeeper实现计数器下一页9.4 使用Zookeeper实现可扩展锁

最后更新于4年前

这有帮助吗?

这一部分我想讨论的例子是非扩展锁。我讨论它的原因并不是因为我强烈的认为这种锁是有用的,而是因为它在Zookeeper论文中出现了。

对于锁来说,常见的操作是Aquire Lock,获得锁。获得锁可以用下面的伪代码实现:

WHILE TRUE:
    IF CREATE("f", data, ephemeral=TRUE): RETURN
    IF EXIST("f", watch=TRUE):
        WAIT

在代码的第2行,是尝试创建锁文件。除了指定文件名,还指定了ephemeral为TRUE(ephemeral的含义详见9.1)。如果锁文件创建成功了,表明我们获得了锁,直接RETURN。

如果锁文件创建失败了,我们需要等待锁释放。因为如果锁文件创建失败了,那表明锁已经被别人占住了,所以我们需要等待锁释放。最终锁会以删除文件的形式释放,所以我们这里通过EXIST函数加上watch=TRUE,来监测文件的删除。在代码的第3行,可以预期锁文件还存在,因为如果不存在的话,在代码的第2行就返回了。

在代码的第4行,等待文件删除对应的watch通知。收到通知之后,再回到循环的最开始,从代码的第2行开始执行。

所以,总的来说,先是通过CREATE创建锁文件,或许可以直接成功。如果失败了,我们需要等待持有锁的客户端释放锁。通过Zookeeper的watch机制,我们会在锁文件删除的时候得到一个watch通知。收到通知之后,我们回到最开始,尝试重新创建锁文件,如果运气足够好,那么这次是能创建成功的。

在这里,我们要问自己一个问题:如果多个客户端并发的请求锁会发生什么?

有一件事情可以确定,如果有两个客户端同时要创建锁文件,Zookeeper Leader会以某种顺序一次只执行一个请求。所以,要么是我的客户端先创建了锁文件,要么是另一个客户端创建了锁文件。如果我的客户端先创建了锁文件,我们的CREATE调用会返回TRUE,这表示我们获得了锁,然后我们直接RETURN返回,而另一个客户端调用CREATE必然会收到了FALSE。如果另一个客户端先创建了文件,那么我的客户端调用CREATE必然会得到FALSE。不管哪种情况,锁文件都会被创建。当有多个客户端同时请求锁时,因为Zookeeper一次只执行一个请求,所以还好。

如果我的客户端调用CREATE返回了FALSE,那么我接下来需要调用EXIST,如果锁在代码的第2行和第3行之间释放了会怎样呢?这就是为什么在代码的第3行,EXIST前面要加一个IF,因为锁文件有可能在调用EXIST之前就释放了。如果在代码的第3行,锁文件不存在,那么EXIST返回FALSE,代码又回到循环的最开始,重新尝试获得锁。

类似的,并且同时也更有意思的是,如果正好在我调用EXIST的时候,或者在与我交互的副本还在处理EXIST的过程中,锁释放了会怎样?不管我与哪个副本进行交互,在它的Log中,可以确保写请求会以某种顺序执行。所以,与我交互的副本,它的Log以某种方式向前增加。因为我的EXIST请求是个只读请求,所以它必然会在两个写请求之间执行。现在某个客户端的DELETE请求要在某个位置被处理,所以,在副本Log中的某处是来自其他客户端的DELETE请求。而我的EXIST请求有两种可能:要么完全的在DELETE请求之前处理,这样的话副本会认为,锁文件还存在,副本会在WATCH表单(详见8.7)中增加一条记录,之后才执行DELETE请求。

而当执行DELETE请求的时候,可以确保我的WATCH请求在副本的WATCH表单中,所以副本会给我发送一个通知,说锁文件被删除了。

要么我的EXIST请求在DELETE请求之后处理。这时,文件并不存在,EXIST返回FALSE,又回到了循环的最开始。

因为Zookeeper的写请求是序列化的,而读请求必然在副本Log的两个写请求之间确定的位置执行,所以这种情况也还好。

学生提问:如果EXIST返回FALSE,回到循环最开始,调用CREATE的时候,已经有其他人创建了锁会怎样呢?

Robert教授:那么CREATE会返回FALSE,我们又回到了EXIST,这次我们还是需要等待WATCH通知锁文件被删除了。

学生提问:为什么我们不关心锁的名字?

Robert教授:这只是一个名字,为了让不同的客户端可以使用同一个锁。所以,它只是个名字而已。当我获得锁之后,我可以对锁保护的数据做任何操作。比如,一次只有一个人可以在这个课堂里讲课,为了讲课,首先需要获得这个课堂的锁,那要先知道锁的名字,比如说34100(猜是教室名字)。这里讨论的锁本质上就是一个znode,但是没有人关心它的内容是什么。所以,我们需要对锁有一个统一的名字。所以,Zookeeper看起来像是一个文件系统,实际上它是一个命名系统(naming system)。

这里的锁设计并不是一个好的设计,因为它和前一个计数器的例子都受羊群效应(Herd Effect)的影响。所谓的羊群效应,对于计数器的例子来说,就是当有1000个客户端同时需要增加计数器时,我们的复杂度是 O(n2)O(n^2)O(n2) ,这是处理完1000个客户端的请求所需要的总时间。对于这一节的锁来说,也存在羊群效应,如果有1000个客户端同时要获得锁文件,为1000个客户端分发锁所需要的时间也是 O(n2)O(n^2)O(n2) 。因为每一次锁文件的释放,所有剩下的客户端都会收到WATCH的通知,并且回到循环的开始,再次尝试创建锁文件。所以CREATE对应的RPC总数与1000的平方成正比。所以这一节的例子也受羊群效应的影响,像羊群一样的客户端都阻塞在Zookeeper这。这一节实现的锁有另一个名字:非扩展锁(Non-Scalable Lock)。它对应的问题是真实存在的,我们会在其他系统中再次看到。