Network File System
2014-12-31 #system
接着上一篇博客Distributed Systems 分布式系统来扯淡,之前的博客一再在写文件系统,这次继续,只不过是分布式文件系统。
1. 这篇文章讲什么
这篇文章介绍一种分布式文件系统,名字叫Network File Sytem(NFS),翻译过来就是网络文件系统。NFS是一种分布式文件系统,大概的样子是这样的:
这里多说一句,NFS可不是仅仅指图中那个server,它包含了图中的所有部件 ,client中也有NFS的组件。
在细一点看client与server之间的数据通信,是这样子的:
现在大概知道NFS的基本架构了吧。下面开始具体介绍了。
2. NFS的设计目标
每一种文件系统的设计都有它自己的考虑,NFS的主要考虑是简单而快速的故障恢复。这可能考虑到分布式系统因为机器多,故而故障是经常发生的。况且,这里面只有一个server,而server是系统的核心,这个server一旦出问题怎么办。
3. 核心设计思路
有了基本的设计,那就到了具体的设计环节了,如何达到目的呢?快速恢复要求“隔离”,也就是说系统各个部件之间尽量减少“共享信息”,因为有了共享信息,恢复起来也就复杂了。
Client和Server之间的发生的操作主要是读写,因为读写一般都不是一次性完成的,需要多次操作,那么有两种方式,一种是双方之间保存读取状态,也就是说server保存了每个client的读取状态记录,比如说读到文件的哪个位置了,这种方式称之为stateful;还有一种是 stateless,也就是说server跟client之间没有什么共享信息,server压根就不知道client读取到了文件的什么位置,每次client读取数据,都是自己提供完整的参数,server按照提供的参数进行操作、返回结果就好了。
那么基于设计目标来看的话,第一种方式不是一个好的选择,很大的一个缺点就是client或者server宕机了的话怎么处理,是不是还得同步一下状态,client的话还好说,server恢复过程中还额外需要恢复跟各个client之间的共享状态,简直复杂啊!!所以NFS选择第二种方式,用户操作指令提供server完成操作所需的所有信息,简单直白效率高。
事实上这是NFS的核心设计思路。
3. 具体系统设计
- 命名空间
文件按照层次结构存储,文件夹,文件。那么首先一个问题就是系统怎么唯一标示一个文件(文件夹也是一种“文件”),一般而言文件的inode是唯一的,故而可用它来唯一标示。NFS也是这么做的,但是又补充了两个参量,首先一个是卷标示符(volume identifier),还有一个版本号(generation number)。卷标示符主要是考虑到扩展,因为NFS可以支持多个文件系统(server端的数据存储使用文件系统具体存储文件,可以包含多种文件系统,为了便与区别,称之为子文件系统);版本号就容易理解了,为了确定文件是否是最新版本。
卷标示符、inode number以及版本号共同组成了文件的唯一标识,称之为File handle。 - 怎么寻找文件的File handle呢
举个例子,我们需要操作文件/foo.txt,那么首先需要找到这个文件的FIle handle。根目录的FIle handle在子文件系统介入NFS中的时候就已经或者,可以认为是已知。client向server发送根目录‘/‘的file handle以及文件名foo.txt查询foo.txt的File handle,server查找并返回。如此便获得了文件的File handle了。可以想象,每多增加一级目录就需要多一次查询。 - NFS的操作命令
下面的是NFS的一些样例操作命令:
可以看到常见的文件读、写、创建、删除以及文件夹的创建、删除;LOOKUP用来执行前面提到的查询文件File Handle操作,GETATTR用来获得文件的属性(包括当前版本号啊,啥的)。下面看看具体操作样例:
注意到,具体实现的时候,client在本地存储了一个映射关系,文件(File descriptor)和其对应的File handle以及当前操作到的文件位置(Current file location)。
这个很好懂。接着来看NFS怎么处理异常情况,操作失败。 - 操作失败
遇到操作失败怎么处理,这里的失败指的是异常,比如因为server宕机导致的client操作没有返回。
NFS由于采用stateless的设计,所以它只能对失败的操纵“ 再试一次”,也就是说操作失败的处理方式是再试一次。判断一个操作是否失败也很简单,只要固定时间内操作没有返回就算失败,简单吧。
但是另一个问题来了,是不是所有的操作“再试一次”仍然确保返回结果正确。一般将多次执行且返回结果依然正确的操作称之为 “幂等操作”(idempotent)。首先,读以及查询操作是幂等操作;其次,写操作也是,同样的数据在同一个位置多次写,依然是可以的。所以NFS的大多数操作是幂等的。但是,文件/文件夹的创建操作可不是哦,client向server发送创建文件夹操作,server完成操作,但是因为某种原因client没有收到反馈,于是 再试一次,这时候server那边就出问题了,“文件夹存在啊”。哈哈,需要额外处理吧。 - client cache 为了加速操作,NFS设计了Cache。client端的cache可以用作两方面,首先是缓存已经读取的数据,其次是将写操作也缓存一下。缓存已经读取的数据可以理解,那么缓存写操作干嘛?目的就是想将一些操作合并、调度,比如说前后两个写操作作用于同一个文件的同一个位置,那么前一个写操作就可以不用执行了。
这里面有个缓存一致性(cache consistency)的问题,比如说缓存已读文件,若缓存后其他的client已经修改了这个文件,那么缓存的数据就是过时的数据,这时候再直接使用这个数据就会出错啊!!
client端的缓存一致性问题的本质不就是别人改了文件而我不知道么,那么解决起来也不费劲啊,只要读取缓存文件判断文件是否过时即可。分两步:- flush-on-close,也就是说当一个client写文件完成、将文件关闭之后,立刻将修改后的文件发送到server。这样一来server就可以保持server端文件是最新的;
- client使用缓存数据前,先向server查询文件的当前状态(还记得前面提到的GETATTR操作么),然后确认缓存的数据是否过时,以便决定要不要使用这个缓存数据。
但是,这么一来也有问题,每次client打开文件都需要向server确认,这样无疑加重了server的负担,所以处于性能考虑,NFS设置一个时间区间,比如说3秒,3秒内认为缓存的数据是绝对最新的,之后就需要向server确认,一旦确认,这个“保质期”也是3秒。如此一来就减轻了server的负担了。
那么问题来了,要是在这3秒内文件被修改了怎么办?答案是,不管!!!毕竟这种情况很少。 - server cache server端的cache跟client端cache的作用基本一样,但是也会有缓存一致性问题!!
因为server修改文件为了效率考虑也会使用cache的数据,那么在cahche中修改文件之后就像client返回操作成功,如果此时server宕机了,这个文件的更新就丢失了啊!!最要命的是client还以为已经写文件成功了,它也就不会再试一次了。
于是,对于写操作,server会先将在cache中的修改保存到磁盘之后再想client返回操作成功,如此便可以了。但是感觉很诡异啊!!!!因为每次写都需要设计磁盘操作,所以很容易成为整个系统的瓶颈!!故而有人建议使用battery-backed memory,也就是说宕机后缓存数据不丢失。
写了这么多,大概是吧NFS介绍完了,注意的是这个只是NFS的框架介绍,跟具体的实现可能有点区别,况且NFS不断更新。
4. 参考文献
?
- http://pages.cs.wisc.edu/~remzi/OSTEP/dist-nfs.pdf
?