> For the complete documentation index, see [llms.txt](https://mit-public-courses-cn-translatio.gitbook.io/mit6-824/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://mit-public-courses-cn-translatio.gitbook.io/mit6-824/lecture-11-cache-consistency-frangipani/1.3-huan-cun-yi-zhi-xing-cache-coherence.md).

# 11.3 Frangipani的锁服务（Lock Server）

Frangipani的第一个挑战是缓存一致性。在这里我们想要的是线性一致性和缓存带来的好处。对于线性一致性来说，当我查看文件系统中任何内容时，我总是能看到最新的数据。对于缓存来说，我们想要缓存带来的性能提升。某种程度上，我们想要同时拥有这两种特性的优点。

人们通常使用缓存一致性协议（Cache Coherence Protocol）来实现缓存一致性。这些协议在很多不同的场景都有使用，不只在分布式文件系统，在多核处理器每个核的缓存的同步中也有使用。只是不同场景中，使用的协议是不一样的。

Frangipani的缓存一致性核心是由锁保证的，我们之后在原子性和故障恢复中将会再次看到锁。但是现在，我们只讨论用锁来保证缓存一致，用锁来帮助工作站确定当它们缓存了数据时，它们缓存的是最新的数据。

除了Frangipani服务器（也就是工作站），Petal存储服务器，在Frangipani系统中还有第三类服务器，锁服务器。尽管你可以通过分片将锁分布到多个服务器上，但是我接下来会假设只有一个锁服务器。逻辑上，锁服务器是独立的服务器，但是实际上我认为它与Petal服务器运行在一起。在锁服务器里面，有一个表单，就叫做locks。我们假设每一个锁以文件名来命名，所以对于每一个文件，我们都有一个锁，而这个锁，可能会被某个工作站所持有。

![](/files/-MFt2U9FCllNi9ljvS3J)

在这个例子中，我们假设锁是排他锁（Exclusive Lock），尽管实际上Frangipani中的锁更加复杂可以支持两种模式：要么允许一个写入者持有锁，要么允许多个读取者持有锁。

假设文件X最近被工作站WS1使用了，所以WS1对于文件X持有锁。同时文件Y最近被工作站WS2使用，所以WS2对于文件Y持有锁。锁服务器会记住每个文件的锁被谁所持有。当然一个文件的锁也有可能不被任何人持有。

![](/files/-MFt3bqh42yObBxTruNe)

在每个工作站，会记录跟踪它所持有的锁，和锁对应的文件内容。所以在每个工作站中，Frangipani模块也会有一个lock表单，表单会记录文件名、对应的锁的状态和文件的缓存内容。这里的文件内容可能是大量的数据块，也可能是目录的列表。

![](/files/-MFt4rUmQJoyzyVD2JHg)

当一个Frangipani服务器决定要读取文件，比如读取目录 /、读取文件A、查看一个inode，首先，它会向一个锁服务器请求文件对应的锁，之后才会向Petal服务器请求文件或者目录的数据。收到数据之后，工作站会记住，本地有一个文件X的拷贝，对应的锁的状态，和相应的文件内容。

每一个工作站的锁至少有两种模式。工作站可以读或者写相应的文件或者目录的最新数据，可以在创建，删除，重命名文件的过程中，如果这样的话，我们认为锁在Busy状态。

![](/files/-MFt6YtbbBhqfMRQj4s9)

在工作站完成了一些操作之后，比如创建文件，或者读取文件，它会随着相应的系统调用（例如rename，write，create，read）释放锁。只要系统调用结束了，工作站会在内部释放锁，现在工作站不再使用那个文件。但是从锁服务器的角度来看，工作站仍然持有锁。工作站内部会标明，这是锁时Idle状态，它不再使用这个锁。所以这个锁仍然被这个工作站持有，但是工作站并不再使用它。这在稍后的介绍中比较重要。

![](/files/-MFt8-VQAgOM4OYXa2QZ)

现在这里的配置是一致的，锁服务器知道文件X和Y的锁存在，并且都被WS1所持有。工作站WS1也有同等的信息，它内部的表单知道它持有了这两个锁，并且它记住了这两个锁对应的文件或者目录。

这里Frangipani应用了很多的规则，这些规则使得Frangipani以一种提供缓存一致性的方式来使用锁，并确保没有工作站会使用缓存中的旧数据。这些规则、锁、缓存数据需要配合使用。这里的规则包括了：

* 工作站不允许持有缓存的数据，除非同时也持有了与数据相关的锁。所以基本上来说，不允许在没有锁保护的前提下缓存数据。从操作意义上来说，这意味着对于一个工作站来说，在它使用一个数据之前，它首先要从锁服务器获取数据的锁。只有当工作站持有锁了，工作站才会从Petal读取数据，并将数据放在缓存中。所以这里的顺序是，获得锁，之后再从Petal读取数据。所以，直到获取了锁，工作站是不能缓存数据的，要想缓存数据，工作站必须先持有锁，之后，才能从Petal读取数据。

![](/files/-MFtkQ3UwJwXsgotwz4w)

* 如果你在释放锁之前，修改了锁保护的数据，那你必须将修改了的数据写回到Petal，只有在Petal确认收到了数据，你才可以释放锁，也就是将锁归还给锁服务器。所以这里的顺序是，先向Petal存储系统写数据，之后再释放锁。

![](/files/-MFtmKQgRMbEHNdANZA9)

最后再从工作站的lock表单中删除关文件的锁的记录和缓存的数据。

![](/files/-MFtmb0_QCKVciDGvCPP)
