NFS协议问题
支持的 NFS 协议版本
目前,CFS 支持 NFS 4.1 协议。
为什么不支持 NFS 的其它版本
从 2.6 系列开始,Linux 内核就已经支持了 NFS 4.0,协议,所以支持 NFS 3 及更低版本的唯一理由可能就是 Windows 操作系统。但 Windows 操作系统对 NFS 协议的支持并不好。在 Windows 上,首选的共享文件系统是 SMB/CIFS 协议,该协议本身也被较新 Linux(内核版本 > 3.4)支持。所以,CFS 选择不支持 NFS 3 及更低版本的 NFS 协议。
NFS 4.X 系列中,使用较为广泛的是 4.0 和 4.1 版本。4.1 修正了 4.0 版本的一些设计缺陷,引入了 session、pNFS、directory delegation 等特性,尽管只有小版本号发生变化,但实际差异较大,所以 CFS 选择优先支持性能更佳的 NFS 4.1 协议。
推荐的操作系统版本
Linux 官方内核对 NFS 4.1 的开发从 2.6.31 版本开始,到 2.6.36 版本发布了实验性支持,最后到 3.7 版本,提供了正式支持。具体信息参见 链接。因此,用户应该尽量选择 3.7 及以上版本的内核。通常情况下,更新的稳定版本内核可以提供更好的性能和体验。
部分操作系统显示的内核版本,其代码与 Linux 官方的同版本内核并不完全一致,例如 CentOS/RHEL,这些发行版会自己做一些 bugfix、修改或者移植的工作,其代码通常超前于同版本的官方内核。因此,各个操作系统的实际情况以官方文档和源代码代码为准。
对于常见的操作系统,我们推荐的版本如下:
- CentOS/RHEL:推荐 6.6 及以上版本。参见 链接 获取该操作系统上更详细的 NFS 协议 bug 信息;
- Ubuntu:推荐 14.04 及以上版本;
- Debian:推荐 8 及以上版本;
- OpenSUSE:推荐 42.3 及以上版本。
不支持的特性
CFS 不支持的 Attributes 包括:FATTR4_ACL,FATTR4_ACLSUPPORT,FATTR4_ARCHIVE,FATTR4_HIDDEN、FATTR4_MIMETYPE、FATTR4_QUOTA_AVAIL_HARD、FATTR4_QUOTA_AVAIL_SOFT、FATTR4_QUOTA_USED、FATTR4_SYSTEM、FATTR4_TIME_BACKUP、FATTR4_TIME_CREATE、FATTR4_DIR_NOTIF_DELAY、FATTR4_DIRENT_NOTIF_DELAY、FATTR4_DACL、FATTR4_SACL、FATTR4_CHANGE_POLICY、FATTR4_FS_STATUS、FATTR4_LAYOUT_HINT、FATTR4_LAYOUT_ALIGNMENT、FATTR4_FS_LOCATIONS_INFO、FATTR4_MDSTHRESHOLD、FATTR4_RETENTION_GET、FATTR4_RETENTION_SET、FATTR4_RETENTEVT_GET、FATTR4_RETENTEVT_SET、FATTR4_RETENTION_HOLD、FATTR4_MODE_SET_MASKED、FATTR4_FS_CHARSET_CAP。
CFS 支持的文件锁为非阻塞(nonblocking)、建议性质,支持锁定整个文件和或者部分字节范围,其它种类的锁定目前不被支持,这意味着,在使用 3 种常见的锁定接口时需要注意:
- flock 需同时指定 LOCK_NB 参数;
- lockf 不支持 F_LOCK 选项;
- fcntl 不支持 F_SETLKW 选项。
CFS 不支持 Delegation、pNFS、访问控制列表(ACL)、Kerberos 安全等特性。
出于性能的考虑,只读操作时 CFS 不更新文件读取的 time_access 属性。
CFS 没有实现持久化 Session、Replay Cache 等信息,当发生故障转移时,客户端会重新建立 Session。该行为符合 RFC 5661 的规定,通常客户端可以自动处理。
挂载的时候报 “mount: wrong fs type, bad option, bad superblock”
多数操作系统默认不会安装 NFS 客户端,需要手动安装。
- 如果您使用 CentOS/RHEL 操作系统,请运行以下命令:
yum install nfs-utils
- 如果您使用 Ubuntu/Debian 操作系统,请运行以下命令:
apt-get install nfs-common
上述命令均需要 root 权限。
遍历文件夹时得到的列表有漏项、重复、更新滞后等情况
当一个客户端通过编程接口 readdir/getdents/getdents64 或者 ls 命令,遍历一个文件夹时,如果有另外一个客户端同时在该文件夹下有并发的创建、删除、重命名操作,用户看到的列表可能会出现漏项和重复。这个属于并发操作互相干扰所致,根本原因是由于一次完整的遍历由若干次连续的获取操作组成,一次只能获取部分文件项,连续的操作中间目录发生了变化。
通常情况下,客户端会使用目录项及属性缓存来提升性能,但是缓存也意味着一个客户端的修改可能不会马上被其它的客户端看到。这就导致了更新滞后的情况。用户可以在挂载的时候使用 noac
选项来禁用缓存。该方式会导致 OP_GETATTR
远程调用的数量大大增加,对性能造成较大的伤害,我们不推荐使用。
主机名和内网 IP 都相同的客户端请求相互干扰
对于同一个文件系统的两个客户端,如果它们的主机名和内网 IP 地址均相同,CFS 无法区分,请求会相互干扰。通常在容器的场景下容易发生这样的问题,因为同一机器上的内网 IP 是相同的,如果主机名再相同,就会被认为是同一客户端。
部分较老的 Linux 内核在编码 NFS 协议客户端标示字符 client owner 的时候,会截断主机名,取前面的部分字符,所以,应该尽可能将主机名字的差异部分编码在比较靠前的位置。根据我们的经验,差异字符控制在前 32 个字符以内,是比较稳妥的。
总之,您应该避免出现主机名和内网 IP 都相同的情况。
客户端无法连接挂载点,内核日志提示 “nfs: server XXX not responding, still trying”
内核日志中 XXX 为挂载点地址,以下以 <挂载点地址> 代表。按照以下步骤排查该问题:
- ping <挂载点地址>,确认该地址可以连通。如果不能连通,检查网页上相应的挂载点地址是否还存在;
- 如果上述步骤无问题,telnet <挂载点地址> 2049,确认和后端服务 NFS 协议端口号是否连通。如不连通,排查安全组策略是否禁用了相关端口号;
- 如果上述步骤无问题,检查 mount 参数是否设置了 noresvport,如果未设置,尝试重新以新选项挂载。
如果仍然存在问题,您可以发工单描述相关问题,联系工程师排查。
目录下发现一些 “.nfsXXXX” 类似名字的文件
这些文件是所谓 “silly rename” 机制产生的。
在类 UNIX 的操作系统上,一个文件在打开之后被删除,只是变成不可见,进程仍然可以通过文件描述符继续读写。当不再有进程打开该文件时,文件才会真正删除。在早期的 NFS 版本(NFS 3 及以前)中,并不能保证服务端继续保留该文件,因此,内核采用了一个小技巧,在删除的时候将文件重命名为 “.nfsXXXX” 这样的隐藏文件,当不再使用的时候删除。这就是所谓的 “silly rename” 机制。如果发生客户端宕机或者网络中断,可能导致客户端没有成功删除掉 ".nfsXXXX" 文件,这些文件就残留了下来。
NFS 4.X 协议已经能够处理其中的问题,但是内核为了向后兼容,仍然使用了 “silly rename” 机制。
您在列举文件夹内项目的时候,可以过滤掉这样的文件。当您确认没有进程在使用这些文件时,可以安全的删除文件。
客户端吞吐或 qps 较低
客户端实际吞吐比较低,可能是由于当前session的slot设置导致的,挂载CFS实例后,可以通过以下步骤确认:
1、安装systool工具。 CentOS/RHEL 操作系统,通过以下命令安装:
yum install sysfsutils
Ubuntu/Debian 操作系统,通过以下命令安装:
apt-get install sysfsutils
2、查看当前slot值,执行前请确保已经成功挂载CFS实例:
systool -v -m nfs
执行结果中“max_session_slot”对应值即是当前session的并发值,如果该值较低,可以调大到 128。
1、修改环境变量
echo options nfs max_session_slots=128 > /etc/modprobe.d/nfsclient.conf
- 重启系统
- 重启后再次通过systool确认是否修改成功。
systool -v -m nfs
多个客户端并发 append 写同一文件数据互相覆盖
NFS 协议不支持原子的 append 写。为了达到本地文件系统上追加写的效果,需要先锁定文件,seek 到正确的位置,再写入数据。示例代码如下:
int lock_file(int fd) {
for (;;) {
int rc = flock(fd, LOCK_EX | LOCK_NB);
if (rc == 0) {
return 0;
}
if (errno == EINTR) {
continue; // interupt by a signal, retry
} else if (errno == EWOULDBLOCK) {
usleep((rand() % 10) * 100); // sleep a while to retry
contunue;
} else {
break;
}
}
return -1; // lock fail
}
int unlock_file(int fd) {
for (;;) {
int rc = flock(fd, LOCK_UN);
if (rc == 0) {
return 0;
}
if (errno == EINTR) {
continue; // interupt by a signal, retry
} else {
break;
}
}
return -1; // unlock fail
}
int append_write(int fd, const char* data, ssize_t size) {
int rc = lock_file(fd);
if (rc != 0) {
return rc;
}
off_t offset = lseek(fd, 0, SEEK_END);
if (offset < 0) {
unlock_file(fd);
return -1;
}
while (size > 0) {
ssize_t nwriten = pwrite(fd, buf, size, offset);
if (nwriten >= 0) {
size -= nwriten;
offset += nwriten;
} else if (errno == EINTR) {
continue;
} else {
unlock_file(fd);
return -1;
}
}
return unlock_file(fd);
}