目前很多文件系统都是基于Fuse开发的。笔者在深入研究Fuse代码后,总结了开发此类文件系统时可以考虑的优化方案,与大家共同探讨。如有不妥之处,还望大家多多指教。在阅读本文之前,我假设您对Fuse有足够的了解(至少知道Fuse有两个模块:FuseKernel和LibFuse,并且知道一个应用程序调用行为是如何传递给我们自己的基于Fuse的文件系统的),否则,请先动。优化一:延长元数据的有效时间。Linux中每个打开的文件在内核中都有两种元数据信息:structdentry和structinode,它们是文件在内核中的基础。所有对文件的操作都需要获取文件的两个结构才能继续,而这两个结构是由具体的文件系统构造和填充的。以下两点说明了元数据优化的必要性:1)。应用程序调用文件系统操作系统的接口时,传入的参数一般是文件路径,如open("a/b/c/d.txt"),内核需要解析路径名。从根目录开始,根据路径中的每个组成部分获取其dentry和inode,然后解析路径的下一个组成部分,直到解析完目标文件的inode和dentry。如果路径名部分包含dentry没有缓存在内存中,需要从特定的文件系统中读取(这会花费很多时间)。2).很多应用喜欢调用stat接口获取文件属性。内核实现实际上是找到文件inode,并从inode中获取文件属性。如果inode没有被缓存,则需要从具体的文件系统中获取它(这可能很耗时)。因为Fuse的内核模块只是一个桥梁,连接应用程序和我们基于Fuse开发的文件系统。因此,按理说,每次获取一个文件/目录的inode和dentry,Fuse内核模块都应该去LibFuse和我们的文件系统。但是这样做的缺点非常明显:IO路径加长,效率变低,如果我们基于fuse开发的文件系统是网络文件系统(如NOS等),可能会增加压力在后端服务器上。鉴于此,Fuse的作者在KernelFuse模块中加入了metadatacache,包括dentry和inodecache。与本地文件系统相比,我们必须时刻警惕一个问题:缓存有效性。因此,如何在尽可能保证正确性的同时提高性能是一个难题。在使用fuse挂载我们自己的文件系统时,我们可以指定dentry和inode属性的有效时间。当然这个有效时间要看具体问题和具体设置,并没有统一的答案。优化方式:fusemountspecified–oentry_timeout=T–oattr_timeout=T优化建议:五颗星优化二:扩大每次写入的页数应用程序首先要通过内核Fuse向基于Fuse开发的文件系统写入文件模块,KernelFuse实际上有很大的权限决定何时向用户态文件系统写入数据。写得越频繁,效率会越低,但一致性可能会更好。控制写入频率其实是一个取舍的过程。如果你对Kernel稍有了解,你可能知道内核的IO其实是以Page为单位的。内核会根据PAGE_SIZE将应用的写请求分成多个页面,然后对页面进行IO,简洁美观。如果不优化,KernelFuse会对应用程序的每个页面调用一次用户态文件系统写操作,所以如果我们的用户态写请求为64KB,按照默认的PAGE_SIZE(4KB),可能会触发16次用户mode写,实际IO次数被放大,效率严重降低。如果采用优化,KernelFuse默认每128KB触发一次用户态文件系统写调用。当然,也可以指定触发写调用的阈值。优化方法:fusemount指定-obig_write-omax_write=N。优化建议:五颗星优化三:启用内核读缓存Linux文件系统充分利用内存来缓存文件数据,使得应用程序经常只读取文件只需要将数据从内核缓冲区复制到用户态缓冲区,而且根本不需要启动磁盘IO。由于Fuse的特殊性,需要严格控制数据缓存行为(参见我们前面提到的元数据缓存),因为我们实现的基于Fuse的文件系统实际上可能是一个网络文件系统,所以如果内核缓存是使用后,可能会读取到脏数据,因为你作为用户态很难控制内核的行为。不过,Fuse的作者还是很有想法的。它提供了多种挂载选项来控制缓存行为,但友情提示:一旦您选择启用缓存,请对您可能读取到的过期数据负责。优化方法:fusemount指定-okernel_cache-oauto_cache顺便说一下:上面说的是参数kernel_cache的行为,并没有说明auto_cache的行为,留给读者仔细研究,提醒:这个选项是基于文件修改时检查内核缓存有效性的优化策略。优化建议:三颗星优化四:扩大预读窗口预读是一件有趣的事情。Linux内核通过预读改变了应用程序原有的读取行为。例如,应用程序发起了一个16KB的读请求,内核可能不知何故读到了64KB的数据等等,当然,它这样做肯定是有原因的。简单地说:一切为了性能,一切为了性能。另外,我会在近期推出预读相关文章,详细阐述预读机制,敬请期待。Fuse允许在挂载用户模式文件系统时指定预读窗口大小。Fuse将使用此设置值作为最大预读窗口大小。如果未指定,它将使用Linux默认的最大预读窗口大小128KB。但实际上,如果你将Fuse的预读窗口设置为超过Linux默认的128KB,也是徒劳的,因为VFS不允许预读窗口超过128KB的限制,所以一般来说,优化的意义不大意义。优化方法:fusemountspecified–omax_readahead=N优化建议:一星优化5:使用DirectIO代替BufferIO有时候,应用程序想绕过OS缓存,自己管理缓存(比如数据库),这就需要文件系统实现DIRECTIO方法.同样,贴心的Fuse作者也为我们提供了directIO读写。与BufferIO方式相比,DirectIO最大的优势在于减少了将数据从应用程序缓冲区复制到内核态的开销。对于大量顺序写的应用场景,可能会有一定的性能提升。当然,如果使用DirectIO,恐怕最大的问题就是read不能使用内核缓存。很多时候这是无法忍受的。通常,文件系统读取的请求多于写入的请求,因此在优化之前请三思。.优化方式:fusemountspecified-odirect_io优化建议:一星
