文件系统 | 网络文件系统 NFS

在分布式环境下,有许多客户端机器和一个(或多个)服务器。服务器将数据存储在其磁盘上,客户端通过结构良好的协议消息来请求数据。这样的做法,可以方便各个客户端共享数据,也便于数据的集中管理。

bc5eeed8517e5e381fa786964f99ef1d.png

但是,要针对以上场景,设计一个网络文件系统,需要考虑哪些方面?哪些地方容易出错?还是有很多值得斟酌之处。

如何应对随时可能发生的服务器崩溃?

仿照os操作的场景,我们显式调用open操作,向操作系统打开一个文件,并获得了文件描述符fd,fd关联了内核中相应的文件数据结构,之后,使用此描述符,我们再显式地调用read\write操作,完成对文件的写入,最后显式调用close,告诉操作系统,相关的资源已经可以被释放,从而完成了对文件的操作。

但是这个过程放在分布式场景中,有何不妥?我们假设服务器随时可能崩溃,客户端向服务器打开一个文件,服务器返回了fd,遗憾的是,服务器紧接着崩溃了,客户端接着使用这个fd进行文件访问,即使服务器成功重启了,fd这样的内存中数据结构也已丢失,无法完成接下来的访问。同样的,我们亦可以假设客户端也是随时可以崩溃的,当服务器向客户端返回fd后,客户端崩溃了,服务器永远也无法知道何时应当释放对应的fd资源,可能对服务器性能造成影响。

NFS的设计者留意到这些问题,决定采用无状态的方法:客户端的每一个请求都包含了本次访问的所有必要信息,无需服务器维护任何信息,服务器倘若发生崩溃,无需花哨的恢复操作,仅需重启继续提供服务就好,客户端所需要做的仅仅是超时重传请求。

NFSv2

如何定义网络协议来支持无状态操作?如何在支持无状态操作的同时,又能够支持POSIX文件系统的API(open\read\write\close...)?

首先,如何描述一个文件依然是必不可少的。file handle是NFS中最重要的数据结构之一,它包含三个重要组件,卷标识符、inode号和generation号,三项一起构成了用户希望访问的文件或目录的唯一标识符。卷标识符用于通知服务器,请求指向哪个文件系统(NFS服务器可以导出多个文件系统),inode号告诉服务器,请求访问该分区中的哪一个文件,generation号在inode被复用时使用,通过在复用inode号时递增它,以确保具有旧文件句柄的客户端不会意外访问新分配的文件。

协议的重要部分摘要:

函数定义
解释
null() returns ()用于测量RTT

lookup(dirfh, name) 

returns (fh, attr)

返回file handle和其属性

create(dirfh, name, attr) 

returns (newfh, attr)

创建一个新文件并返回其fh和属性

remove(dirfh, name) 

returns (status)

移除某一路径下文件
getattr(fh) returns (attr)返回文件属性

setattr(fh, attr) 

returns (attr)

设置文件属性

read(fh, offset, count) 

returns (attr, data)

读fh从offset偏移的count大小数据,返回数据的同时还会返回属性

write(fh, offset, count, data)

returns (attr)

写fh从offset偏移的count大小数据,返回属性
......

客户端首先使用LOOKUP协议消息获取文件句柄,该句柄用于访问文件数据,有了文件句柄,客户端就可以对一个文件发出READ和WRITE协议消息,读取和写入该文件,他们传递的内容包含了文件、文件的偏移及长度,服务器不用维护任何其他元数据,即可向客户端返回文件数据(或错误),下图表述了一次读取文件操作中客户端与服务器的操作流:

57f0124b2998f0207fbcb12ac5f834b6.png

9df5f2e1c365a747b55ce99ceeb0de0f.png

处理服务器故障

当客户端向服务器发送消息时,有时并不会收到回复,失败的原因各种各样,比如网络问题导致消息没有传输到服务器,服务器故障,或者由于网络问题服务器的消息没有传输回来,那么如果遇到这种情况,客户端应该怎么做?

fc32700305704963b8b91721e39c973c.png

在NFSv2中,客户端只会以一种方式处理所有这些故障,那就是:重试。大多数NFS操作都是幂等的,操作执行多次和执行一次的效果相同,这是客户端可以通过重试来解决崩溃恢复问题的核心,LOOKUP和READ操作是简单幂等的,因为它们不会修改服务器数据,WRITE消息也是幂等的,因为WRITE消息包含了数据确切的位置以及数据,重复多次写入不会对数据正确性造成影响,当然,也存在一些操作并不幂等,比如mkdir,对已经存在的目录再次创建,会产生错误,但是并无伤大雅。

客户端缓存与一致性问题

将所有读写请求都通过网络发送,会导致性能下降,因此,客户端采用缓存来减少网络传输量能够提高性能,但是这会带来缓存一致性问题。

NFSv2主要采用了两种方式解决缓存一致性问题,首先,为了解决数据更新时的一致性,客户端实现了“关闭时刷新”的一致性语义,即,如果在文件打开过程中更新了数据,随后关闭文件时,客户端将所有更新刷新到服务器以确保当文件再次打开时,会看到最新的文件版本。

其次,客户端在使用缓存内容前,向服务器发送GETATTR请求,请求中包含文件修改时间,通过修改时间,客户端可以判断当前缓存中的内容是否为最新版本。

但是这导致了一个问题,为了确保数据没有过期,使得整个网络中充斥了大量的GETATTR请求,“轰炸”了服务器,实际上一个文件大部分时间只会由一个客户端修改,因此,为了提高性能,为每个客户端也添加了一个属性缓存,首次访问该文件时,属性也被缓存,在一定时间后超时,这样在超时时间内,不会有额外的服务器通信。(当然,这在大多数情况下都没问题,但是好巧不巧,也有可能偶尔会导致奇怪的行为)

服务器的写入必须是原子的

我们再额外想一个问题,WRITE的回复时机,服务器是收到了WRITE将其写入写缓冲就向客户端汇报成功,还是必须将其写入持久化内存才能向客户端汇报成功?

有可能有这样的一个场景:客户端C向服务器S写入了数据aaa,覆盖原有数据ccc,服务器收到请求后,将数据存入自己的缓存中,然后向客户端发送请求成功,紧接着,服务器宕机了,客户端下一次再向服务器获取数据,服务器由于缓存丢失,最后向客户端返回了ccc,造成了错误,因此,服务器的写入必须是原子的。

当然,每次都落盘势必会造成性能损失,服务器端也可以通过BBU紧急供电来确保缓存中的数据可以被持久化。

本文仅作简要介绍,作者也没有继续深入了,感兴趣的同学可以继续阅读reference中的内容。

reference:

[1] “The Sun Network File System: Design, Implementation and Experience” Russel Sandberg,这是NFS的原生论文。

[2]  Operating Systems: Three Easy Pieces,介绍操作系统的一本好书。

[3] NFS Illustrated, Brent Callaghan,介绍NFS的一本好书, 每个协议都讲得非常彻底和详细。

[4] “NFS: Network File System Protocol Specification” Sun Microsystems, Inc. Request for Comments: 1094, March 1989,规范。

[5] “The NFS version 4 protocol” Brian Pawlowski, David Noveck, David Robinson, Robert Thurlow 2nd International System Administration and Networking Conference (SANE 2000) 

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值