Files
MIT6.824/lecture-11-cache-consistency-frangipani/1.4-huan-cun-yi-zhi-xie-yi-coherence-protocol.md
2022-01-25 02:41:31 +00:00

8.6 KiB
Raw Blame History

11.4 缓存一致性Cache Coherence

工作站和锁服务器之间的缓存一致协议协议包含了4种不同的消息。本质上你可以认为它们就是一些单向的网络消息。

首先是Request消息从工作站发给锁服务器。Request消息会说hey锁服务器我想获取这个锁。

如果从锁服务器的lock表单中发现锁已经被其他人持有了那锁服务器不能立即交出锁。但是一旦锁被释放了锁服务器会回复一个Grant消息给工作站。这里的Request和Grant是异步的。

如果你向锁服务器请求锁,而另一个工作站现在正持有锁,锁服务器需要持有锁的工作站先释放锁,因为一个锁不能同时被两个人持有。那我们怎么能让这个工作站获取到锁呢?

前面说过如果一个工作站在使用锁并在执行读写操作那么它会将锁标记为Busy。但是通常来说当工作站使用完锁之后不会向锁服务器释放锁。所以如果我创建了一个新文件create函数返回时这些新文件的锁仍然被我的工作站持有。只是说现在锁的状态会变成Idle而不是Busy。但是从锁服务器看来我的工作站仍然持有锁。这里延迟将锁还给锁服务器的原因是如果我在我的工作站上创建了文件Y。我接下来几乎肯定要将Y用于其他目的或许我向它写一些数据或许会从它读数据。所以如果工作站能持有所有最近用过的文件的锁并不主动归还的话会有非常大的优势。在一个常见的例子中我使用了home目录下的一些文件并且其他工作站没有人查看过这些文件。我的工作站最后会为我的文件持有数百个在Idle状态的锁。但是如果某人查看了我的文件他需要先获取锁而这时我就需要释放锁了。

所以这里的工作方式是如果锁服务器收到了一个加锁的请求它查看自己的lock表单可以发现这个锁现在正被工作站WS1所持有锁服务器会发送一个Revoke消息给当前持有锁的工作站WS1。并说现在别人要使用这个文件请释放锁吧。

当一个工作站收到了一个Revoke请求如果锁时在Idle状态并且缓存的数据脏了工作站会首先将修改过的缓存写回到Petal存储服务器中因为前面的规则要求在释放锁之前要先将数据写入Petal。所以如果锁的状态是Idle首先需要将修改了的缓存数据发回给Petal只有在那个时候工作站才会再向锁服务器发送一条消息说好吧我现在放弃这个锁。所以对于一个Revoke请求的响应是工作站会向锁服务器发送一条Release消息。

如果工作站收到Revoke消息时它还在使用锁比如说正在删除或者重命名文件的过程中直到工作站使用完了锁为止或者说直到它完成了相应的文件系统操作它都不会放弃锁。完成了操作之后工作站中的锁的状态才会从Busy变成Idle之后工作站才能注意到Revoke请求在向Petal写完数据之后最终释放锁。

所以这就是Frangipani使用的一致性协议的一个简单版本的描述。如我之前所描述的这里面没有考虑一个事实那就是锁可以是为写入提供的排他锁Exclusive Lock也可以是为只读提供的共享锁Shared Lock

就像Petal只是一个块存储服务并不理解文件系统。锁服务器也不理解文件目录还有文件系统它只是维护lock表单表单中记录的是锁的名字和锁的持有者。Frangipani可以理解锁与某个文件相关联。实际上Frangipani在这里使用的是Unix风格的inode号来作为lock表单的key而不是文件的名字。

接下来我们看一下如何应用这里的缓存一致协议并演示Petal操作和和锁服务器操作之间的关联。我会过一遍工作站修改文件系统数据之后另一个工作站查看对应数据的流程。

所以首先我们有了2个工作站WS1WS2一个锁服务器LS

按照协议如果WS1想要读取并修改文件Z。在它从Petal读取文件之前它需要先获取对于Z的锁所以它向锁服务器发送Request消息下图中ACQ Z

如果当前没有人持有对文件Z的锁或者锁服务器没听过对于文件Z的锁初始化状态锁服务器会在lock表单中增加一条记录并返回Grant消息给工作站说你现在持有了对于Z文件的锁。

从这个时间点开始工作站WS1持有了对文件Z的锁并且被授权可以从Petal读取Z的数据。所以这个时间点WS1会从Petal读取并缓存Z的内容。之后WS1也可以在本地缓存中修改Z的内容。

过了一会坐在工作站WS2前面的用户也想读取文件Z。但是一开始WS2并没有对于文件Z的锁所以它要做的第一件事情就是向锁服务器发送Request消息请求对于文件Z的锁。

但是锁服务器知道不能给WS2回复Grant消息因为WS1现在还持有锁。接下来锁服务器会向WS1发送Revoke消息。

而工作站WS1在向Petal写入修改数据之前不允许释放锁。所以它现在会将任何修改的内容写回给Petal。

写入结束之后WS1才可以向锁服务器发送Release消息。

锁服务器必然会有一个表单记录谁在等待文件Z的锁一旦锁的当前持有者释放了锁锁服务器需要通知等待者。所以当锁服务器收到了这条Release消息时锁服务器会更新自己的表单并最终将Grant消息发送给工作站WS2。

这个时候WS2终于可以从Petal读取文件Z。

这就是缓存一致性协议的工作流程它确保了直到所有有可能私底下在缓存中修改了数据的工作站先将数据写回到Petal其他工作站才能读取相应的文件。所以这里的锁机制确保了读文件总是能看到最新写入文件的数据。

在这个缓存一致性的协议中,有许多可以优化的地方。实际上,我之前已经描述过一个优化点了,

每个工作站用完了锁之后不是立即向锁服务器释放锁而是将锁的状态标记为Idle就是一种优化。

另一个主要的优化是Frangipani有共享的读锁Shared Read Lock和排他的写锁Exclusive Write Lock。如果有大量的工作站需要读取文件但是没有人会修改这个文件它们都可以同时持有对这个文件的读锁。如果某个工作站需要修改这个已经被大量工作站缓存的文件时那么它首先需要Revoke所有工作站的读锁这样所有的工作站都会放弃自己对于该文件的缓存只有在那时这个工作站才可以修改文件。因为没有人持有了这个文件的缓存所以就算文件被修改了也没有人会读到旧的数据。

这就是以锁为核心的缓存一致性。

学生提问:如果没有其他工作站读取文件,那缓存中的数据就永远不写入后端存储了吗?

Robert教授这是一个好问题。实际上在我刚刚描述的机制中是有风险的如果我在我的工作站修改了一个文件但是没有人读取它这时这个文件修改后的版本的唯一拷贝只存在于我的工作站的缓存或者RAM上。这些文件里面可能有一些非常珍贵的信息如果我的工作站崩溃了并且我们不做任何特殊的操作数据的唯一拷贝会丢失。所以为了阻止这种情况不管怎么样工作站每隔30秒会将所有修改了的缓存写回到Petal中。所以如果我的工作站突然崩溃了我或许会丢失过去30秒的数据但是不会丢更多这实际上是模仿Linux或者Unix文件系统的普通工作模式。在一个分布式文件系统中很多操作都是在模仿Unix风格的文件系统这样使用者才不会觉得Frangipani的行为异常因为它基本上与用户在使用的文件系统一样。