在存储系统中, NFS(Network File System,即网络文件系统)是一个重要的概念,已成为兼容POSIX语义的分布式文件系统的基础。它允许在多个主机之间共享公共文件系统,并提供数据共享的优势,从而最小化所需的存储空间。
天翼云终端通过NFS实现共享空间服务, 提供基于用户和部门的权限管理与容量管理, 但是在NFSv3对于共享文件系统很重要文件锁功能并不集成在NFS服务中,而是一个分离的NLM服务,这个锁管理服务必须与原生linux版本的NFS服务配合使用,导致当我们需要自行开发NFS服务端时,无法直接利用此服务完成文件锁功能, 这也是导致多数云服务商的NFSv3服务无法提供文件锁功能的原因。
因为Windows原生只支持v3以下版本的NFS, 具备文件锁功能的NFSv4尚无法替代NFSv3。 本文将深入介绍NLM协议的细节, 以及自行实现NLM协议与NFS服务器集成的注意事项。
一、协议与框架
NLM基于以下协议与RPC框架实现
ONC-RPC:NLM 协议在 ONC-RPC 之上实现,是一种节省字节的rpc协议。
portmap协议:与NFS一样,客户端通过portmap协议来发现服务端的NLM服务端口, 通常在自行实现的NFS服务端, portmap也可以集成在NFS服务中 。
NSM协议:NLM依赖 NSM 协议来通知彼此服务重启/重启,以便在重启后可以重新同步锁。
二、协议主体内容
NLM协议主要通过以下请求达成锁的效果。
Null :相当于PING,用来查看是否正常运行。
Lock :用来在在服务器上申请锁。在请求独占锁的情况下,可能会发生锁争用。
Unlock :释放已申请的锁。
Cancel :通常与Unlock语义一致,在Linux NFS 客户端实现中:Unlock 通常在应用程序通过 fcntl() 调用显式释放锁定时使用。 Cancel 通常在应用程序终止后由锁管理器发出,而没有自行清理锁。
Granted :当阻塞的锁变得可用时,服务器回调客户端以告诉它需要的锁已经可用。
三、协议报文
对于各类锁请求,服务端的主要响应如下信息:
enum nlm_stats {
LCK_GRANTED = 0, //加锁成功
LCK_DENIED = 1, //加锁失败
LCK_DENIED_NOLOCKS = 2,//加锁失败,服务端没有对应的资源
LCK_BLOCKED = 3, //加锁失败,当前不可以加锁,服务端会在可加锁时回调客户端
LCK_DENIED_GRACE_PERIOD = 4 //加锁失败,服务端刚刚重启,无法响应
};
各种锁请求中, 对于锁的描述主要由如下字段构成
struct nlm_lock {
string caller_name<LM_MAXSTRLEN>; /* 调用方的唯一标识 */
netobj fh; /* file handle, 文件标识 */
netobj oh; /* 调用进程或主机的唯一标识 */
int uppid; /* 进程唯一标识 */
unsigned l_offset; /* 锁开始位置 */
unsigned l_len; /* 锁对象长度 */
};
nlm_lock结构 ,是主要的锁请求,如lock,unlock,cancel的主要参数,例如
struct nlm_lockargs {
netobj cookie;
bool block; /* 是否阻塞 */
bool exclusive; /* 共享锁还是排它锁*/
struct nlm_lock alock; /* 锁信息结构 */
bool reclaim; /* 与NSM协议配合使用的字段, 只有在感知到服务端重启时才为true */
int state; /* 客户端NSM状态 */
};
除此以外,NLM协议还要支持 DOS 3.1 及更高版本兼容的文件共享控制,即share与unshare请求。
通常, windows版本的客户端并不使用nlm lock 请求,而是nlm share请求。
struct nlm_share {
string caller_name<LM_MAXSTRLEN>;
netobj fh;
netobj oh;
fsh_mode mode;
fsh_access access;
};
struct nlm_shareargs {
netobj cookie;
nlm_share share; /* actual share data */
bool reclaim; /* used for recovering shares */
};
四、注意事项
1. NLM协议与NFS使用相同的鉴权方式, NLM服务端与NFS集成的方式实际上有利于复杂鉴权功能的实现。
2. 考虑到网络丢包带来的重复加锁的可能性,服务端在实现加锁请求时要考虑锁的可重入。
3. lock与share两种加锁方式有所不同, lock支持共享/排他锁,并且支持分段加锁, 而share则支持分离的读写锁,服务端需要兼容两种协议的实现。