技术解析
由一条帖子引发的对文件系统思考
原帖: https://www.v2ex.com/t/589932
楼主大意是讲在项目前期不想费事,想直接用服务器某个路径下放所有需要用的图片文件,会不会有什么性能问题。第一次看到这个帖子还是 2 个月之前,看了下回复没有正面回应的,问题其实就是一个目录下能不能放海量文件,如果不能是因为什么,如果可以的话会不会有性能问题。
直觉上来说我是觉得是不行的,但是到底哪里不行也是一头雾水,趁好奇心还未消失研究一哈。
首先讨论这个问题前必须得根据对应的服务器文件系统以及相关配置结合讨论,否则没有意义。 centos7 默认是 xfs,centos6 默认是 ext4,centos5 默认是 ext3
Ext4 理论支持无限文件个数,但是默认上限是 65000 - 2 = 64998 (扣除了 . 和 .. ),可以通过文件系统的 dir_nlink 属性打开这个限制 参考资料: http://man7.org/linux/man-pages/man5/ext4.5.html
Ext3 最大 31998 个文件个数
XFS 无限制 我具体没找到 XFS 特别详细的资料,不过参考下面: https://serverfault.com/questions/210786/what-are-the-maximum-number-of-files-directories-allowed-per-directory-in-common
https://blog.csdn.net/yipiankongbai/article/details/36862835 在默认块大小为 4KB 的情况下,inode 位图则有 4K Byte = 32K Bit,一个块组则最大记录的块数量为 32K = 32 * 1024 = 32768 个。因此一个块组大小为 32K (数据位图) * 4KB = 128MB。但是在 128M 的空间里,存放 32K 个 inode 太浪费了,只有几乎所有的文件的大小都小于 4K 的情况下,才会需要这么多的 inode。 由此可见,如果文件普遍为小(小于块大小)而多的情况时,inode 数量可以支持更多的文件数量,如果出现大文件多的情况时,因为一个文件只需要一个 inode,而一个大文件几乎就把块组所有的数据块占满,而其他不能分配出去的 inode 占用了磁盘空间,基本等于浪费了。 https://bean-li.github.io/EXT4_DIR_INDEX/
因此,每个块组会预先分配一定数量的 inode: [email protected]:~# tune2fs -l /dev/sdb2 |grep Inode Inode count: 243073024 Inodes per group: 4096 Inode blocks per group: 256 Inode size: 256
4096 * 256 = 4 * 1024 * 256 4096 ( Inodes per group )* 256Byte ( Inode size ) = 4KB ( block size )* 256 ( Inode blocks per group ) 128 * 1024 * 1024 / 4096 = 32 * 1024 128MB (块组大小)/ 4096 ( inode 数)= 32KB (文件系统预期平均文件大小)
从上面的内容不难看出,每个 Inode 的大小为 256 字节,一个块组有 4096 个 inode,所有的 inode 消耗了 256 个 block。这个情况表明,该文件系统一个块组 128M 的空间,预期文件个数不会超过 4096 个,即创建文件系统的人认为,文件系统的文件的平均大小不低于 32K。如果该文件系统中所有的文件均是 1K 或者几 KB 的小文件,很容易拉低文件大小的平均值。然后就会出现,磁盘空间还有大量的剩余,但是 inode 已经分配光的情况。
这个是根据宏观文件系统角度考虑的,单个目录下不使用。考虑到效率来说,inode 和文件都会占用磁盘空间,完美的情况 inode+文件=磁盘可用空间大小。即没用 inode 被浪费,也没用空余的数据块闲置。
参考资料: https://bean-li.github.io/EXT4-packet-meta-blocks https://bean-li.github.io/vfs-inode-dentry/
然而在当前情况下单个目录下海量文件直接导致的会是目录文件过大。当一个 block 无法容纳的下所有的文件名与 inode 对照表时,Linux 会给予该目录多一个 block 来继续记录相关的数据。
读单个文件 Linux 查找目录下的文件,就是一个线性扫描的过程。比如说查询 /home/test.txt 文件,VFS (文件系统与操作系统的中间层)先从根目录 /开始,找到 home 对应的 inode 以及对应的文件(目录是特殊的文件),再从 home 找到 test.txt 的 inode 及其对应的文件。然而文件系统内的文件可能非常庞大,目录树结构可能很深,该树状结构中,可能存在几千万,几亿的文件。大量的工作在查找目录的过程中消耗,这时候就有了 dentry,dentry 是一个纯粹的内存结构,由文件系统在提供文件访问的过程中在内存中直接建立。dentry 中包含了文件名,文件的 inode 号等信息。读取到了该目录后,内核对象会为该文件对象建立一个 dentry,并将其缓存起来,方便下一次读取时直接从内存中取。在 EXT4 中如果一个数据块(默认 4KB )放不下所有 entry,那么采用 hash tree。所以读单个文件应该不会太慢,不会是性能瓶颈。但是有个问题,就是在单目录海量文件下 dentry 大小是很大的,按照每个文件名 255 字节大小来算,每 1Mb 内存能存最多能存 4k 个左右大小的文件( dentry 存的还包括 inode 位置)。
参考资料 https://wizardforcel.gitbooks.io/vbird-linux-basic-4e/content/59.html
写文件,删除文件也经常遇到一个 do_path_lookup() 函数,跟查询基本一样,不同的是还需要对 dentry 进行更新操作。 https://blog.csdn.net/ty_laurel/article/details/51407107 https://www.ibm.com/developerworks/cn/linux/l-cn-usagecounter/
在 linux 的并发数量都要受到系统对用户单一进程同时可打开文件数量的限制(这是因为系统为每个 TCP 连接都要创建一个 socket 句柄,每个 socket 句柄同时也是一个文件句柄)。可使用 ulimit 命令查看系统允许当前用户进程打开的文件数限制: ulimit -n 1024 (阿里云主机默认是 65535 ) 这表示当前用户的每个进程最多允许同时打开 1024 个文件,这 1024 个文件中还得除去每个进程必然打开的标准输入,标准输出,标准错误,服务器监听 socket,进程间通信的 unix 与 socket 等文件,那么剩下的可用于客户端 socket 连接的文件数就只有大概 1024-10=1014 个左右,也就是说缺省情况下,基于 linux 的通信程序最多允许同时 1014 个 tcp 并发连接。
系统级的 cat /proc/sys/fs/file-max 这 表明这台 Linux 系统最多允许同时打开(即包含所有用户打开文件数总和)100977 个文件,是 Linux 系统级硬限制,所有用户级的打开文件数限制都 不应超过这个数值。通常这个系统级硬限制是 Linux 系统在启动时根据系统硬件资源状况计算出来的最佳的最大同时打开文件数限制,如果没有特殊需要,不应该修改此限制,除非想为用户级打开文件数限制设置超过此限制的值。这个参数的默认值是跟内存大小有关系的,增加物理内存以后重启机器,这个值会增大。大约 1G 内存 10 万个句柄的线性关系。
参考资料 https://ivanzz1001.github.io/records/post/linuxops/2018/03/20/linux-openfile-max https://blog.51cto.com/blove/1768522
默认情况下,ls 命令会将输出排序。为了排序,ls 命令先将所有文件的名称读入内存。当遇到一个非常大的目录时,它就在那里不断地读入文件名,并且内存占用越来越大,直到将所有文件一次性以字母数字顺序列出来。 而 ls -1 -f 命令并不执行排序操作,只是读取目录然后立即显示文件。
参考资料: https://www.jianshu.com/p/353a5dbcd423