Gun*_*dow 10 freebsd cache nfs unionfs
问题:当通过 NFS 启动二进制文件(例如 /usr/bin)时,例如在网络引导系统中,NFS 可能会很慢。RAM 缓冲区缓存可能不足以避免缓慢。
想法:似乎我们应该能够拥有一个本地磁盘缓存,它可以在从 NFS 中提取文件时将文件保存在本地。
问题:有没有人在任何 UNIX 系统上看到过类似的东西?
背景:
在 FreeBSD 中,有很多使用 unionfs 来构建惊人的堆叠文件系统的好方法。我目前在 AWS 上有一个系统,它只使用 1 GB 的磁盘,因为它通过 NFS 挂载了大部分 /usr 文件系统树。在过去,您可以轻松做到这一点,因为 /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;
}
Run Code Online (Sandbox Code Playgroud)
如果我们正在访问一个(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;
};
Run Code Online (Sandbox Code Playgroud)
坦率地说,我想将所有这些模式作为空间的位域来处理。无论如何,有了这个,我现在可以将上面的代码更改为:
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;
Run Code Online (Sandbox Code Playgroud)
这应该是所有需要的。也就是说,希望所有处理属性和影子目录的事情都从函数 unionfs_copyfile 内部处理,正如它应该的那样。
现在在这种情况下,我们只需要将新的 copy-on-read 策略选项添加到 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);
Run Code Online (Sandbox Code Playgroud)
所以,这将在 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
Run Code Online (Sandbox Code Playgroud)
更多改进:驱逐缓存条目
现在我注意到我可能想添加另一个 whiteout 模式,即:从不。即,我应该能够从上层删除文件,效果是从缓存中驱逐文件,但没有从下层屏蔽文件的白化效果,因此它看起来是空的。这就是在 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;
Run Code Online (Sandbox Code Playgroud)
然后在 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);
Run Code Online (Sandbox Code Playgroud)
然后也可能有一些关于 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);
Run Code Online (Sandbox Code Playgroud)
这也应该做驱逐的事情。
但在我做这一切之前,我想知道,是否存在人们已经找到的现有技巧?
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
Run Code Online (Sandbox Code Playgroud)
我还发现 /var/cache/usr 目录仍然可以直接使用,因此可以通过从那里删除文件来简单地从缓存中逐出!这意味着我们甚至根本不需要弄乱 whiteout 设置。
相反,如果 unionfs_copyfile(...) 调用返回错误“设备上没有剩余空间”,我应该提出一个自动缓存逐出策略来从缓存中删除旧文件,逐出旧文件直到空间被回收,然后重试该操作. 很容易(除了找到旧的文件)。
穷人的轻松缓存驱逐
只需find /var/cache/usr -atime 2 -exec rm \{\}\;
每隔几天运行一次即可删除那些一天未访问的项目。
一个更有趣的更深层次的问题可能是,是否可以通过在读取块时将块写入上层来提高 unionfs_copyfile(...) 函数的效率。甚至可以做整个面向块的事情,这样如果下层的文件是稀疏的,它也会在上层保持稀疏。
NFS v3 或 v4.x 并不慢。所以我假设您谈论的是 NFS v2。
我刚刚浏览了手册页man 5 nfs
。我偶然发现了选项fsc。
这似乎可以完成你想要使用的cachefilesd
。您可能可以在 /dev/shm 上找到该缓存,这应该会进一步加快速度。
我记得在 Solaris 中做过这样的事情,在那里我有一个 700 MB RAM 缓存,用于通过 NFS 向许多并发客户端提供 CD。