11.3 Frangipani的锁服务(Lock Server)
最后更新于
最后更新于
Frangipani的第一个挑战是缓存一致性。在这里我们想要的是线性一致性和缓存带来的好处。对于线性一致性来说,当我查看文件系统中任何内容时,我总是能看到最新的数据。对于缓存来说,我们想要缓存带来的性能提升。某种程度上,我们想要同时拥有这两种特性的优点。
人们通常使用缓存一致性协议(Cache Coherence Protocol)来实现缓存一致性。这些协议在很多不同的场景都有使用,不只在分布式文件系统,在多核处理器每个核的缓存的同步中也有使用。只是不同场景中,使用的协议是不一样的。
Frangipani的缓存一致性核心是由锁保证的,我们之后在原子性和故障恢复中将会再次看到锁。但是现在,我们只讨论用锁来保证缓存一致,用锁来帮助工作站确定当它们缓存了数据时,它们缓存的是最新的数据。
除了Frangipani服务器(也就是工作站),Petal存储服务器,在Frangipani系统中还有第三类服务器,锁服务器。尽管你可以通过分片将锁分布到多个服务器上,但是我接下来会假设只有一个锁服务器。逻辑上,锁服务器是独立的服务器,但是实际上我认为它与Petal服务器运行在一起。在锁服务器里面,有一个表单,就叫做locks。我们假设每一个锁以文件名来命名,所以对于每一个文件,我们都有一个锁,而这个锁,可能会被某个工作站所持有。
在这个例子中,我们假设锁是排他锁(Exclusive Lock),尽管实际上Frangipani中的锁更加复杂可以支持两种模式:要么允许一个写入者持有锁,要么允许多个读取者持有锁。
假设文件X最近被工作站WS1使用了,所以WS1对于文件X持有锁。同时文件Y最近被工作站WS2使用,所以WS2对于文件Y持有锁。锁服务器会记住每个文件的锁被谁所持有。当然一个文件的锁也有可能不被任何人持有。
在每个工作站,会记录跟踪它所持有的锁,和锁对应的文件内容。所以在每个工作站中,Frangipani模块也会有一个lock表单,表单会记录文件名、对应的锁的状态和文件的缓存内容。这里的文件内容可能是大量的数据块,也可能是目录的列表。
当一个Frangipani服务器决定要读取文件,比如读取目录 /、读取文件A、查看一个inode,首先,它会向一个锁服务器请求文件对应的锁,之后才会向Petal服务器请求文件或者目录的数据。收到数据之后,工作站会记住,本地有一个文件X的拷贝,对应的锁的状态,和相应的文件内容。
每一个工作站的锁至少有两种模式。工作站可以读或者写相应的文件或者目录的最新数据,可以在创建,删除,重命名文件的过程中,如果这样的话,我们认为锁在Busy状态。
在工作站完成了一些操作之后,比如创建文件,或者读取文件,它会随着相应的系统调用(例如rename,write,create,read)释放锁。只要系统调用结束了,工作站会在内部释放锁,现在工作站不再使用那个文件。但是从锁服务器的角度来看,工作站仍然持有锁。工作站内部会标明,这是锁时Idle状态,它不再使用这个锁。所以这个锁仍然被这个工作站持有,但是工作站并不再使用它。这在稍后的介绍中比较重要。
现在这里的配置是一致的,锁服务器知道文件X和Y的锁存在,并且都被WS1所持有。工作站WS1也有同等的信息,它内部的表单知道它持有了这两个锁,并且它记住了这两个锁对应的文件或者目录。
这里Frangipani应用了很多的规则,这些规则使得Frangipani以一种提供缓存一致性的方式来使用锁,并确保没有工作站会使用缓存中的旧数据。这些规则、锁、缓存数据需要配合使用。这里的规则包括了:
工作站不允许持有缓存的数据,除非同时也持有了与数据相关的锁。所以基本上来说,不允许在没有锁保护的前提下缓存数据。从操作意义上来说,这意味着对于一个工作站来说,在它使用一个数据之前,它首先要从锁服务器获取数据的锁。只有当工作站持有锁了,工作站才会从Petal读取数据,并将数据放在缓存中。所以这里的顺序是,获得锁,之后再从Petal读取数据。所以,直到获取了锁,工作站是不能缓存数据的,要想缓存数据,工作站必须先持有锁,之后,才能从Petal读取数据。
如果你在释放锁之前,修改了锁保护的数据,那你必须将修改了的数据写回到Petal,只有在Petal确认收到了数据,你才可以释放锁,也就是将锁归还给锁服务器。所以这里的顺序是,先向Petal存储系统写数据,之后再释放锁。
最后再从工作站的lock表单中删除关文件的锁的记录和缓存的数据。