小文件存储
需要小文件存储的原因
思考:
- 什么是海量小文件存储,多小为小,多少文件为海量?
- 为什么要针对海量小文件存储做优化?
小文件应用场景
通常,大小在1MB以内的文件称为小文件;百万级数量及以上称为海量。
典型应用场景:
(1)Facebook存储了600亿张以上的图片,推出了专门针对海量小图片定制优化的Haystack进行存储。
(2)淘宝存储超过200亿张图片,平均大小仅为15KB,也推出了针对小文件优化的TFS文件系统存储这些图片,并且进行了开源。
(3)动漫渲染和影视后期制作应用,会使用大量的视频、音频、图像、纹理等原理素材,一部普通的动画电影可能包含超过500万的小文件,平均大小在10-20KB之间。
(4)金融票据影像,需要对大量原始票据进行扫描形成图片和描述信息文件,单个文件大小为几KB至几百KB的不等,文件数量达到数千万乃至数亿,并且逐年增长。
应用范例:vivo FastDFS 海量小文件存储解决之道。
小文件存储带来的问题
Linux通过node存储文件信息,但inode也会消耗硬盘空间,所以硬盘格式化的时候,操作系统自动将硬盘分成两个区域: 一个是数据区,存放文件数据;另一个是inode区(inode table),存放inode所包含的信息。
每个inode节点的大小,一般是128字节或256字节。inode节点的总数,在格式化时就给定,一般是每1KB或每2KB就设置一个inode。
小文件主要有2个问题:
- 如果小文件都小于<1KB,而系统初始化的时候每个2K设置一个node,则此时一个文件还是至少占用2K的空间,最后导致磁盘空间利用率不高,< 50%。
- 大量的小文件,导致在增加、查找、删除文件的时候需要遍历过多的node节点,影响效率。
FastDFS小文件机制配置
合并文件存储相关的配置都在tracker.conf中。配置完成后,重启tracker和storage server。
支持小文件存储,只需要设置tracker的use_trunk_file=true, store_server=1, 其他保持默认即可.
# 是否启用trunk存储,缺省false,需要打开配置
use_trunk_file=true
#trunk文件最小分配单元 字节,缺省256,即使上传的文件只有10字节,也会分配这么多空间。
slot_min_size = 256
#trunk内部存储的最大文件,超过该值会被独立存储,缺省16M,超过这个size的文件,不会存储到trunk file中,而是作为一个单独的文件直接存储到文件系统中。
slot_max_size = 1MB
#trunk文件大小,默认 64MB,不要配置得过大或者过小,最好不要超过256MB。
trunk_file_size = 64MB
store_server=1
注意slot_max_size 的大小,这样才知道多大的文件触发小文件存储机制。
FastDFS合并存储文件命名与文件
向FastDFS上传文件成功时,服务器返回该文件的存取ID叫做fileid:
- 当没有启动合并存储时该fileid和磁盘上实际存储的文件一一对应。
- 当采用合并存储时就不再一一对应而是多个fileid对应的文件被存储成一个大文件。
为了区别,下面将采用合并存储后的大文件统称为Trunk文件,没有合并存储的文件统称为源文件。注意区分三个概念:
- Trunk文件:storage服务器磁盘上存储的实际文件,默认大小为64MB。
- 合并存储文件的FileId:表示服务器启用合并存储后,每次上传返回给客户端的FileId,注意此时该FileId与磁盘上的文件没有一一对应关系。
- 没有合并存储的FileId:表示服务器未启用合并存储时,Upload时返回的FileID。
Trunk文件文件名格式:fdfs_storage1/data/00/00/000001 文件名从1开始递增,类型为int。
启动小文件存储时服务返回给客户端的fileid有变化
(1)独立文件存储的file id:
4 bytes 4 bytes 8 bytes 4 bytes 2 bytes
+--------+--------+----------------+--------+-----+
| IP | time | file_size | crc32 |校验值|
文件名(不含后缀名)采用Base64编码,包含如下5个字段(每个字段均为4字节整数):
group1/M00/00/00/wKgqHV4OQQyAbo9YAAAA_fdSpmg855.txt
这个文件名中,除了.txt为文件后缀,wKgqHV4OQQyAbo9YAAAA_fdSpmg855 这部分是一个base64
编码缓冲区,组成如下:
- storage_id(ip的数值型)源storage server ID或IP地址。
- timestamp(文件创建时间戳)。
- file_size(若原始值为32位则前面加入一个随机值填充,最终为64位)。
- crc32(文件内容的检验码)。
- 随机数 (引入随机数的目的是防止生成重名文件)。
(2)合并存储(或小文件存储)file id:
如果采用了合并存储,生成的文件ID将变长,文件名后面多了base64文本长度16字符(12个字节)。
这部分同样采用Base64编码,包含如下几个字段:
group1/M00/00/00/eBuDxWCwrDqITi98AAAA-3Qtcs8AAAAAQAAAgAAAAIA833.txt
采用合并的文件ID更长,因为其中需要加入保存的大文件id以及偏移量,具体包括了如下信息:
- storage_id(ip的数值型)源storage server ID或IP地址。
- timestamp(文件创建时间戳)。
- file_size:实际的文件大小。
- crc32:文件内容的crc32码。
- trunk file ID:大文件ID如000001。
- offset:文件内容在trunk文件中的偏移量。
- alloc_size:分配空间,大于或等于文件大小。
- 随机数 (引入随机数的目的是防止生成重名文件)。
4 bytes 4 bytes 8 bytes 4 bytes 4 bytes 4 bytes 4 bytes 2 bytes
+--------+--------+----------------+--------+--------+--------+----------+-----+
| IP | time | file_size | crc32 |trunk ID| offset |alloc_size|校验值|
file_size是小文件实际大小。
trunk ID是对应trunk文件的序号。
offset是对应小文件在trunk文件的偏移起始位置。
alloc_size是实际占用的空间,大小等于小文件大小。
Trunk文件存储结构
磁盘数据内部结构
trunk内部是由多个小文件组成,每个小文件都会有一个trunkHeader,以及紧跟在其后的真实数据,结构如下:
|—————————————————---—————— 24bytes ——————————————————————————————————————|
|— 1byte —|— 4bytes —|— 4bytes —|— 4bytes— |— 4bytes—|———-— 7bytes —--———|
|—filetype—|—alloc_size—|—filesize—|—crc32 —|—mtime —|—formatted_ext_name—|
|--—————————————————————— file_data filesize bytes ——————————————————————-|
|———————————————————————— file_data ——————————————————————————————————————|
Trunk文件为64MB(默认),因此每次创建一次Trunk文件总是会产生空余空间,比如为存储一个10MB文件,创建一个Trunk文件,那么就会剩下接近54MB的空间,下次要想再次存储10MB文件时就不需要创建新的文件,存储在已经创建的Trunk文件中即可。另外当删除一个存储的文件时,也会产生空余空间。
每次创建一个trunk文件时,是安装既定的文件大小去创建该文件trunk_file_size,当要删除trunk文件时,需要该trunk文件没有存储小文件才能删除。
小文件存储平衡树
在Storage内部会为每个store_path构造一颗以空闲块大小作为关键字的空闲平衡树,相同大小的空闲块保存在链表之中。每当需要存储一个文件时会首先到空闲平衡树中查找大于并且最接近的空闲块,然后试着从该空闲块中分割出多余的部分作为一个新的空闲块,加入到空闲平衡树中。
总结
- 小于1MB的文件认为是小文件,百万级别数量及以上的文件称为海量文件。
- 由于Linux inode的存在,即使很小的数据保存成文件也会占用比较大的磁盘空间,导致磁盘空间利用率低。
- 大量的小文件,导致在增加、查找、删除文件时需要遍历的inode节点过多。
- 每个小文件都会有一个trunkHeader,以及紧跟在其后的真实数据。
- 小文件通过解析fileid的信息,找到其对应的trunk id、file size、offset来查找内容。
- 当删除一个存储的小文件时,会产生空间碎片。