很久之前对nfs4.0 rfc文档分析的笔记,笔记不是很完善,后来工作变化了也懒得补充了。
共享出来供大家分享吧,有些地方的理解可能会有偏差。
以后阅读rfc的时候还是把原文翻一下比较好,笔记的方式容易引起误解,别人看了不方便考证。
参考资料标准的rfc文档:rfc3530
NFSV4
V4的RPC的Procedure number只有null和compound两个,compound procedure由多个operation组成;例如open、setattr和read 可以组成一个compound procedure。一个RPC消息中可以做多个操作。Server对一个Compound RPC请求中的多个operation顺序执行,遇到一个错误,后面的operation不再执行,server直接返回前面所有的结果给Client;
通过compound组合的方式,可以有效减少RPC调用的次数。
NFS3的操作中,filehandle一般是做为operation的显示式入参;NFS4中,filehandle不再作为显示入参,而是作为一个隐式入参,类似shell中的环境变量。
作为环境变量的filehandle有两个:Current filehandle和Saved filehanle。如果一个operation只涉及到一个文件(例如open、lock),那么只会用到Current filehandle;
如果涉及两个文件(例如link),就会用到Current filehandle+Savedfilehanle。
Currentfilehandle一般是隐式变化的。例如lookup操作之前current filehanle应该是当前目录的filehandle,如果lookup操作成功,那么current filehandle就变为lookup操作所查找的文件的filehandle。NFS4也提供专门的操作用来查询和修改current fd和saved fd,例如GETFH(查询current fd)和SETFH(设置current fd)、SAVEDFH(将saved fd设置为current fd)、RESTOREFH(将current fd设置为saved fd)。
不再需要额外的mount协议。client通过PUTROOTFD的操作,将root fd设置为current fd,后面client只需要进行lookup操作,就可以遍历整个文件树。
4.0中增加了share reservation的概念。这个概念是针对windows API的文件接口引入的。
Windows API中CreateFile(即打开文件)时必须制定文件的share mode,即deny other:none,read,write,both。Posix语义跟windows不同, open file的时候不需要关心是否上锁。
通过SetClientID、setClientID_Conform操作,client跟server之间协商clientID。
Lock_owner包括clientID+threadid
StateID是lock_owner的缩写,由server产生,server负责维护lock_owner与stateID的对应关系。
Linux NFS4 实现:stateid由ownerid+fileid+时间戳组成
seqID for lock:由于RPC可能基于不可靠的UDP协议,NFSclient可能多次调用rpc发送同一个消息;即使基于tcp的rpc,也可能存在多个tcp链路,同一个lock-owner的rpc消息可能并行发送。因此,对于涉及状态的rpc消息(open closelock locku等),每个operation都增加一个流水号即seqid。同一个lockowner的seqid从0开始。
open_conform 用来确认open请求,用于open_owner第一次打开文件。
Nfs4 支持字节级别的锁。
如果需要缩小锁的范围,可以通过unlock操作实现。当然server可以不支持这种细粒度的锁。锁的级别可通过lock操作降低或者提升,server也可以不支持。
阻塞锁:
增加了新的锁类型:READW和WRITEW
具体两种实现方式,采用哪种未明确:
1. server发现锁有冲突,立刻回复client失败,client收到失败结果后,只能向server poll方式再请求锁;
2. server发现有冲突的锁,并不立刻返回client消息,而是等释放了冲突的锁之后,回复client lock 请求成功。
租约主要是为了防止发生重启或者网络中断的客户端,长时间占有锁,并不是为了缓存一致性。
NFS提供专门针对租约的操作 :renew(租约更新,即续租)。对于sever来说,一个client具有一个租约,而不是一个state具有一个租约。
出于性能的考虑,client任何一个涉及到stateid的操作,例如lock locku open close readwrite等,都相当于进行一次隐式的租约renew,避免client经常要发送renew消息带来的消耗。如果open时候stateid字段为空(即不对文件进行共享保护),那么这种open或者close或者后面的read write不会renew 租约。
租约时间nfs4代码中固定的是60s。
如果client崩溃,重启后,client会重新向server申请自己的clientID。Client向server发送的SetClientID消息的clientID结构中有一个verifier字段,该字段client每次启动都会赋新值,因此server可以根据该字段发现client是否重启过。如果server发现client重启,则释放原client申请的全部锁。
Client通过server返回几类错误码来发现server重启,例如clientID陈旧,stateID陈旧等。Server重启后,进入一个所谓的grace period。grace period的时长等于lease time。友好期主要目的是给客户端时间来恢复server重启前client的锁的状态。友好期内,client通过lock操作的reclaim字段或者open操作的claimtype字段来表示自己本次锁操作是恢复原来的锁状态。友好期内,Server的处理分两种情况:
1. server将锁的状态存在持久化存储中,server可以知道重启前文件是否上锁,锁的拥有者是谁,因此也可以判断非回收锁是否跟回收锁有冲突,允许无冲突的非回收锁操作或读写操作。
2. server没有持久化存储,无法判断非回收锁是否引起冲突,因此简单的拒绝一切非回收锁操作和读写操作。
对于回收锁的处理,有两种特殊的边界情况,第一种:
1. clientA申请锁之后跟server网络发生中断,一直无法renew锁的租约。
2. server在clientA的租约超期后把付给了clientB,B又释放了锁。
3. server发生重启,A跟server的链路通了,A去server申请回收锁。
第二种:
1. client A申请锁,之后server重启
2. server重启后由于A与server断链,一直无法申请回收锁
3. A的锁被server释放,Client B又向server申请了锁,成功后B释放锁
4. Server重启后,A跟server链路通,A重新向server申请回收锁。
这两种情况下,server都不应该答应A申请回收锁的请求。简单的做法是让server拒绝一切回收锁的请求。另一种做法是server需要在持久化存储中记录一些信息,以判断上述情况是否发生,包括:
a. Client lease是否超期
b. Client 时间戳,该时间戳在server启动后,client第一次申请锁、共享保护或客户端代理时被更新,后面 server再次重启这个时间戳不会被更新。
c. Server时间戳,记录最近两次server启动的时间。
有了上面的值,server就可以判断出申请回收锁的client的租约是否超期,是否是server上次启动前申请的锁。如果是,都将拒绝client申请。
如果client一直没有收到lock请求的响应消息,client可能放弃重新尝试。但是,同一个lock-owner下一次申请另一个锁操作之前,必须同步上一次lock的状态。同步的方法就是lock-owner将最近一次未收到响应的lock请求从cache中取出,重新发送给server。当然,重新发送的lock请求的seqid是不变的。
分三种情况:
1. server重启
2. 租约到期是client没能及时renew
3. server的管理员人为强制收回某个锁
close前是否需要释放所有的锁,跟server的实现有关系。有些server需要client主动释放所有锁之后才能close,有些server允许client直接发送close消息。
对于close消息的重传,server有两种方式处理:
1. 简单的回复close成功,自己打一个错误日志。
2. 每次收到close时候并不释放state,而是只是将state标记为pending-close,直到clientID或者seqID被释放。这样server就可以判断close消息是否重发。
NFS4.0并不提供一种分布式缓存一致性的机制,仅仅定义一种有限的客户端cache保护机制,使得锁与共享保护机制与客户端缓存机制能够共存。
Delegation涉及三个方面:client,server,file。某个文件的open_owner打开文件时,可以申请delegation。授权分类型。Client取到某个file的delegation之后,该client上对该文件的访问(在授权范围内)都由client来做判断。Server如果发现其他client请求访问该文件,且访问模式跟server对取到delegation的client的委托类型有冲突,那么server需要召回delegation。Server召回delegation通过callback rpc实现。Callback rpc可能受防火墙的影响无法执行,所以server在将delegation赋予一个client之前,需要用一个空的rpc callback操作来测试下callback rpc是否可行。
每个delegation也具有一个stateid,也具有一个lease(租期)。
委托召回时,client需要将本地缓存的数据、元数据全部刷新到server之后才能回复server委托召回成功。Server需要设定一个委托召回的超时时间,如果时间间隔内没收到client的回复,则认为委托失效。
客户端申请锁之前,必须将锁对应的file region的cache 重新生效。如果client发现change字段为真,则需要flush该缓存,或者释放掉缓存块。客户端释放写锁之前,必须将cache全部flush到server。
客户端对于cache的管理可能是基于内存页。但是同一个页内的字节可能属于两个不同的锁的范围。因此客户端的cache刷新需要以字节为单位而不是以页为单位。
客户端在读写时,如果发现读写的范围内具有相应的锁类型,客户端可以直接在本地cache操作,后面在flush到server。如果没有锁,则不能使用cache。
NFS4中,FileHandle是根据pathname生成的,不同的filehandle可能指向同一个文件。以下原则可以用来区分不同的FileHandle是否同一个文件:
1. File attribute里面的fsid属性不同,则肯定不是同一个文件。
2. 文件属性里的unique_handles为true,则不同的filehandle肯定是不同的文件。
3. 文件属性里的fileid字段不同,则不同的filehandle肯定是不同的文件。
客户端向server申请授权时,server从以下几个方面考虑是否允许授权:
1. 客户端的状态时良好的,可以执行callback rpc,上次对该客户端的授权可以成功收回。
2. 一致性判断:当前该文件所有的open mode跟授权类型不冲突,所有的授权状态跟本次申请的授权类型也不冲突。
3. 基于性能的考虑:从最近的访问历史看授权冲突的可能性较低。
授权的类型:读授权和写授权。读授权可以多个同时存在。同一个文件,同一时刻,写授权只能存在一个,且不允许读授权存在。授权也具有一个stateid,授权的stateid跟open的stateid互相独立。当授权被召回,授权的stateid无效时,同一个文件上file lock的stateid仍然有效。
Server决定授予client delegation,那么server对于open的result中不能要求client对本次open做confirm。如果要求confirm,那么对于协议本身过于复杂,因为如果server要求client对delegation做confirm的话,server后面还要判断client的对应confirm消息是否超时了,这样就太复杂了。索性server认为只要对open的结果中包含了delegation,那么这次授权就成功了。