问题:当通过 NFS 启动二进制文件(例如 /usr/bin)时(例如在网络启动系统中),NFS 可能会很慢。RAM 缓冲区缓存可能不足以避免速度缓慢。
主意:看来我们应该能够拥有一个本地磁盘缓存,该缓存可以在从 NFS 拉取文件时在本地保存文件。
问题:有谁在 UNIX 系统上见过类似的东西吗?
背景:
在 FreeBSD 中,有很多使用 unionfs 构建令人惊叹的堆栈文件系统的好方法。我目前在 AWS 上有一个系统,它仅使用 1 GB 磁盘,因为它的大部分 /usr 文件系统树都是通过 NFS 挂载的。在过去,您可以轻松地做到这一点,因为基本启动不需要 /usr。现在它变得更加困难(特别是在 AWS 上,当启动失败时你无法跳出控制台),但我通过在本地驱动器上的 /usr 树中获取最少的必要内容来进行管理,然后,当网络启动时,我在 /usr 树上挂载 NFS。
我什至有一个后门,我仍然可以写入底层最小本地硬盘驱动器 /usr 树,以防我需要更新正在运行的系统上的某些内容。
很美丽。
除了 NFS (Amazon EFS) 非常慢之外。并且缓冲区缓存工作得不够好。例如,用于管理 AWS 资源的 aws 命令行界面使用 Python,每次调用 aws 命令时都会吸收大量包含内容。运行简单的 aws CLI 命令需要 20 秒。即使重复运行它,您也会认为缓存、NFS 属性缓存等可能有帮助,但事实并非如此。
可能的解决方案(在 FreeBSD 上):
所以我想做的就是在 NFS 层之上放置另一个 unionfs 层,这是一个基于本地磁盘的 UFS 文件系统。但它在启动时会是空的,然后,每次我们从 NFS 加载任何内容(假设现在它是稳定的二进制文件,而不是动态更新的数据),它都会在磁盘上留下一个副本。
该解决方案的实施:
所以这就是我认为应该做的事情。在/usr/src/sys/fs/unionfs/union_vnops.c我们有这个非常简单的代码:
static int
unionfs_open(struct vop_open_args *ap)
{
...
if (targetvp == NULLVP) {
if (uvp == NULLVP) {
if ((ap->a_mode & FWRITE) && lvp->v_type == VREG) {
error = unionfs_copyfile(unp,
!(ap->a_mode & O_TRUNC), cred, td);
if (error != 0)
goto unionfs_open_abort;
targetvp = uvp = unp->un_uppervp;
} else
targetvp = lvp;
} else
targetvp = uvp;
}
(ap->a_mode & FWRITE)
如果我们访问仅位于下层的文件进行写入,则这部分将在上层进行复制(uvp == NULLVP) && lvp->v_type == VREG
。
想要尝试添加一项功能来为每个文件创建一个副本(甚至是只读访问的文件)似乎很简单。然后它也会制作该副本,下次我们将从磁盘读取该文件。
为此,我将添加一个新选项/usr/src/sys/fs/unionfs/union.h我会添加一个新选项,即复制策略:
/* copy policy of upper layer */
typedef enum _unionfs_copypolicy {
UNIONFS_COPY_ON_WRITE = 0,
UNIONFS_COPY_ALWAYS
} unionfs_copypolicy;
struct unionfs_mount {
struct vnode *um_lowervp; /* VREFed once */
struct vnode *um_uppervp; /* VREFed once */
struct vnode *um_rootvp; /* ROOT vnode */
unionfs_copypolicy um_copypolicy;
unionfs_copymode um_copymode;
unionfs_whitemode um_whitemode;
uid_t um_uid;
gid_t um_gid;
u_short um_udir;
u_short um_ufile;
};
坦率地说,我想将所有这些模式作为空间的位字段来处理。无论如何,有了这个,我现在可以将上面的代码更改为:
unp = VTOUNIONFS(ap->a_vp);
ump = MOUNTTOUNIONFSMOUNT(ap->a_vp->v_mount);
...
if (targetvp == NULLVP) {
if (uvp == NULLVP) {
if (((ap->a_mode & FWRITE) || (ump->um_copypolicy == UNIONFS_COPY_ALWAYS)) && lvp->v_type == VREG) {
error = unionfs_copyfile(unp,
!(ap->a_mode & O_TRUNC), cred, td);
if (error != 0)
goto unionfs_open_abort;
targetvp = uvp = unp->un_uppervp;
这应该就是所需要的一切。也就是说,希望所有处理属性和影子目录的操作都是从函数 unionfs_copyfile 内部处理的,正如它应该的那样。
在这种情况下,我们现在只需要将新的读时复制策略选项添加到 mount_unionfs 中,该选项也很好地位于内核模块内/usr/src/sys/fs/unionfs/union_vfsops.c
static int
unionfs_domount(struct mount *mp)
{
int error;
...
u_short ufile;
unionfs_copypolicy copypolicy;
unionfs_copymode copymode;
unionfs_whitemode whitemode;
...
ufile = 0;
copypolicy = UNIONFS_COPY_ON_WRITE; /* default */
copymode = UNIONFS_TRANSPARENT; /* default */
whitemode = UNIONFS_WHITE_ALWAYS;
...
if (vfs_getopt(mp->mnt_optnew, "copypolicy", (void **)&tmp,
NULL) == 0) {
if (tmp == NULL) {
vfs_mount_error(mp, "Invalid copy policy");
return (EINVAL);
} else if (strcasecmp(tmp, "always") == 0)
copypolicy = UNIONFS_COPY_ALWAYS;
else if (strcasecmp(tmp, "onwrite") == 0)
copypolicy = UNIONFS_COPY_ON_WRITE;
else {
vfs_mount_error(mp, "Invalid copy policy");
return (EINVAL);
}
}
if (vfs_getopt(mp->mnt_optnew, "copymode", (void **)&tmp,
...
}
if (vfs_getopt(mp->mnt_optnew, "whiteout", (void **)&tmp,
...
}
}
...
UNIONFSDEBUG("unionfs_mount: uid=%d, gid=%d\n", uid, gid);
UNIONFSDEBUG("unionfs_mount: udir=0%03o, ufile=0%03o\n", udir, ufile);
UNIONFSDEBUG("unionfs_mount: copypolicy=%d, copymode=%d, whitemode=%d\n", copypolicy, copymode, whitemode);
所以,这将在 FreeBSD 中实现我想要的功能,我现在需要获取我的系统的源代码,应用此补丁,重新编译 unionfs.ko 内核模块并将其交换到我的系统中,看看它是否可以工作。
# Custom /etc/fstab for FreeBSD VM images
/dev/gpt/rootfs / ufs rw 1 1
/dev/gpt/varfs /var ufs rw 1 1
fdesc /dev/fd fdescfs rw 0 0
proc /proc procfs rw 0 0
/usr /.usr nullfs rw 0 0
fs-xxxxxxxx.efs.rrrr.amazonaws.com:/ /usr nfs rw,nfsv4,minorversion=1,oneopenown,rsize=1048576,wsize=1048576,hard,timeo=600,retrans=2,noresvport,late,bg 0 0
/var/cache/usr /usr unionfs rw,copypolicy=always 0 0
更多改进:逐出缓存条目
现在我注意到我可能想添加另一种白化模式,即:从不。即,我应该能够从上层删除文件,具有从缓存中逐出文件的效果,但没有从下层屏蔽文件的白化效果,因此它看起来是空的。这是在 union.h 中添加 UNIONFS_WHITE_NEVER 的方法:
/* whiteout policy of upper layer */
typedef enum _unionfs_whitemode {
UNIONFS_WHITE_ALWAYS = 0,
UNIONFS_WHITE_WHENNEEDED,
UNIONFS_WHITE_NEVER
} unionfs_whitemode;
然后在 union_vnops.c 中:
static int
unionfs_remove(struct vop_remove_args *ap)
{
...
if (uvp != NULLVP) {
/*
* XXX: if the vnode type is VSOCK, it will create whiteout
* after remove.
*/
if (ump == NULL || ump->um_whitemode == UNIONFS_WHITE_ALWAYS ||
(lvp != NULLVP && ump->um_whitemode != UNIONFS_WHITE_NEVER))
cnp->cn_flags |= DOWHITEOUT;
error = VOP_REMOVE(udvp, uvp, cnp);
} else if (lvp != NULLVP && ump->um_whitemode != UNIONFS_WHITE_NEVER)
error = unionfs_mkwhiteout(udvp, cnp, td, path);
那么可能还有一些关于 rmdir 的内容。
static int
unionfs_rmdir(struct vop_rmdir_args *ap)
{
...
if (uvp != NULLVP) {
if (lvp != NULLVP) {
error = unionfs_check_rmdir(ap->a_vp, cnp->cn_cred, td);
if (error != 0)
return (error);
}
ump = MOUNTTOUNIONFSMOUNT(ap->a_vp->v_mount);
if (ump->um_whitemode == UNIONFS_WHITE_ALWAYS ||
(lvp != NULLVP && ump->um_whitemode != UNIONFS_WHITE_NEVER))
cnp->cn_flags |= DOWHITEOUT;
error = unionfs_relookup_for_delete(ap->a_dvp, cnp, td);
if (!error)
error = VOP_RMDIR(udvp, uvp, cnp);
}
else if (lvp != NULLVP && ump->um_whitemode != UNIONFS_WHITE_NEVER)
error = unionfs_mkwhiteout(udvp, cnp, td, unp->un_path);
这也应该做驱逐的事情。
但在我做这一切之前,我想知道,是否存在人们已经想出的现有技巧?
PS:这是我的完整差异和测试结果。https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=251363
简短的答案是:它实际上工作得很好,还有一件事我不清楚:unionfs 不采用块设备,但它采用目录!所以这真的很酷,您甚至不需要创建设备。我已经更新了我建议的 fstab,尽管我可能根本不会使用它,因为它必须延迟到 NFS 的后期安装之后。因此最好删除它并稍后打开基于 unionfs 的缓存,例如在 /etc/rc.local 中,这很简单:
mount -t unionfs -o copypolicy=always /var/cache/usr /usr
我还发现 /var/cache/usr 目录仍然直接可用,因此只需从那里删除文件即可从缓存中逐出!这意味着我们根本不需要搞乱白化设置。
相反,我应该提出一种自动缓存驱逐策略,如果 unionfs_copyfile(...) 调用返回错误“设备上没有剩余空间”,则从缓存中删除旧 atime 的文件,驱逐旧文件直到空间回收,然后重试该操作。非常简单(除了查找旧 atime 的文件)。
穷人的简单缓存驱逐
只需find /var/cache/usr -atime 2 -exec rm \{\}\;
每隔几天运行一次即可删除那些一天未访问过的项目。
一个更有趣的更深层次的问题可能是,是否可以通过在读取块时将块写入上层来使 unionfs_copyfile(...) 函数更加高效。也许甚至可以将整个事情面向块,这样如果下层中的文件是稀疏的,那么它在上层上也将保持稀疏。
答案1
NFS v3 或 v4.x 并不慢。所以我假设您谈论的是 NFS v2。
我刚刚浏览了手册页man 5 nfs
。我偶然发现了这个选项金融服务委员会。
这似乎可以完成你想要使用的cachefilesd
。您可能可以在 /dev/shm 上找到该缓存,这应该会进一步加快速度。
我记得在 Solaris 中做过这样的事情,在那里我有一个 700 MB RAM 缓存,用于通过 NFS 向许多并发客户端提供 CD。