传统文件存储问题
单机存储不了,就将数据分散存储到多台机器,并通过在软件层做封装提供统一的存储接口,使得存储服务的使用者不需要关心数据是如何存储、存储在哪里的。
以磁盘为存储介质的单机文件系统(如ext4、xfs等),文件都是以目录树结构来组织的,当文件数很多时,目录内的文件数会很多、目录层次也会变深,使得路径查找是非常影响性能的,一次路径名查找可能需要多次磁盘IO。
目前分布式文件存储系统的解决方案
TFS、FASTDFS、HDFS都是通过将数据分布到多个存储节点来解决问题
1、通过将多个文件打包存储到大文件(block)以及扁平化的目录结构来解决问题
2、通过block多副本以及按block复制的方式来解决问题。
3、block通常64MB大小,block内部存储的每个文件由一个fileid标识。
步骤一
将索引文件不持久化存储,在内存里组织为hash表;存储时将文件追加到block尾部后,将文件在block内部的offset以及文件size信息,插入到hash表中;访问该文件时,先根据文件的id在hash表中定位文件的offset和size,然后在block对应位置读取文件数据,由于hash表是全内存化的,访问文件只需一次IO。
这种方案的优点在于,每次存储文件时,只需要一次IO操作,不会出现索引与block实际文件数据不一致大情况;缺点在于,索引只存在于内存,当服务重启时,index信息需要根据block的数据来重建,,这就要求文件在block中存储时,必须存储一些额外的头信息,使得block的文件具有自描述能力,在每次启动时,通过扫描block数据来生成index。
以2T磁盘、64MB block为例,磁盘上会有约30000个block;假设扫描一个block需要1s,那么启动时间约为500min * 0.8(磁盘使用率80%)= 400min,显然,每次启动时扫描block来生成index的开销是不可接受的。
步骤二
在步骤一的基础上,每个block对应一个index文件,写文件时先将文件数据追加到block的尾部,然后将文件的index插入到内存hash表,最后将文件的index追加到index文件;在存储服务重启时,每个block根据其index文件来快速建立内存hash表。
步骤二解决了索引重建的问题,但其每次写文件需要两次IO,写文件的延时就高了。为了降低写的延时,facebook在开源贡献了一种折中的方案。文件写入时,往block追加文件数据时同步刷盘,然后将记录插入到内存hash表,接下来追加index记录时,发送完追加请求就认为写文件成功,即index并不立即刷盘,这样写的延时缩短到一次IO。
文件index异步追加,可能导致一个问题,文件在block里存在,但在index文件里没有这个文件的记录,在根据index文件重建内存hash表时,hash表就是不完整的,导致部分文件访问不到。facebook通过在重建时,从block尾部开始扫描,找出所有可能缺失index的文件,并生成index追加到index文件。
步骤三
步骤二中index的数据实际上是存在两份的,一份是内存hash表里的,一份是index文件里的,因为linux的页缓存机制,文件里的index也是可能cache在内存里,所以步骤二对内存的利用不是最优的,可以考虑将index文件和index内存hash表合二为一,将index文件本身以hash表的方式组织,直接使用mmap将index文件映射到内存。
通过将index文件和内存hash表合二为一,操作内存index即为操作index文件,管理index会方便不少。为了解决hash冲突问题,每个index条目需要额外增加一个next字段用于连接冲突链。
这种还存在hash扩展的问题,当block内存储的小文件数量很多时,按照预估的hash桶数(预估值通常不会太大,太大会有很多空间浪费),可能会导致冲突链很长,这时要提高hash查找的效率就必须扩展桶的数量,如果使用步骤一,扩展只会导致额外的内存拷贝,而在这个步骤三里,则会导致整个index文件重写,会产生IO操作。
fastDFS分布式设计原理
FastDFS是一个开源的分布式文件系统,由tracker serverstorage server和client三个部分组成,主要解决了海量数据存储问题,特别适合以中小文件(建议范围:4KB < file_size <500MB)为载体。
1、Storage server
Storage server(后简称storage)以组(卷,group)为单位,一个group内包含多台storage机器,数据互为备份,存储空间以group内容量最小的storage为准,所以建议group内的多个storage尽量配置相同,以免造成存储空间的浪费。
以group为单位存储能方便的进行应用隔离、负载均衡、副本数定制,比如将不同应用数据存到不同的group就能隔离应用数据,同时还可根据应用的访问特性来将应用分配到不同的group来做负载均衡;缺点是group的容量受单机存储容量的限制,同时当group内有机器坏掉时,数据恢复只能依赖group内地其他机器,使得恢复时间会很长。
group内每个storage的存储依赖于本地文件系统,storage可配置多个数据存储目录,比如有10块磁盘,分别挂载在/data/disk1-/data/disk10,则可将这10个目录都配置为storage的数据存储目录。
storage接受到写文件请求时,会根据配置好的规则,选择其中一个存储目录来存储文件。为了避免单个目录下的文件数太多,在storage第一次启动时,会在每个数据存储目录里创建2级子目录,每级256个,总共65536个文件,新写的文件会以hash的方式被路由到其中某个子目录下,然后将文件数据直接作为一个本地文件存储到该目录中。
2、Tracker server
Tracker是FastDFS的协调者,负责管理所有的storage server和group,每个storage在启动后会连接Tracker,告知自己所属的group等信息,并保持周期性的心跳,tracker根据storage的心跳信息,建立group到[storage server list]的映射表。
Tracker需要管理的元信息很少,会全部存储在内存中;另外tracker上的元信息都是由storage汇报的信息生成的,本身不需要持久化任何数据,这样使得tracker非常容易扩展,直接增加tracker机器即可扩展为tracker cluster来服务,cluster里每个tracker之间是完全对等的,所有的tracker都接受stroage的心跳信息,生成元数据信息来提供读写服务。
3、上传原理
4、同步原理
写文件时,客户端将文件写至group内一个storage server即认为写文件成功,storage server写完文件后,会由后台线程将文件同步至同group内其他的storage server。每个storage写文件后,同时会写一份binlog,binlog里不包含文件数据,只包含文件名等元信息,这份binlog用于后台同步,storage会记录向group内其他storage同步的进度,以便重启后能接上次的进度继续同步;进度以时间戳的方式进行记录,所以最好能保证集群内所有server的时钟保持同步。
storage的同步进度会作为元数据的一部分汇报到tracker上,tracke在选择读storage的时候会以同步进度作为参考。
比如一个group内有A、B、C三个storage server,A向C同步到进度为T1 (T1以前写的文件都已经同步到B上了),B向C同步到时间戳为T2(T2 > T1),tracker接收到这些同步进度信息时,就会进行整理,将最小的那个做为C的同步时间戳。
5、下载原理
下载流程图6、小文件合并存储
将小文件合并存储主要解决如下几个问题:
-
本地文件系统inode数量有限,从而存储的小文件数量也就受到限制。
-
多级目录+目录里很多文件,导致访问文件的开销很大(可能导致很多次IO)
-
按小文件存储,备份与恢复的效率低
FastDFS在新版本中,可将多个小文件存储到一个大的文件(trunk file),为了支持这个机制,FastDFS生成的文件fileid需要额外增加16个字节,每个trunk file由一个id唯一标识,trunk file由group内的trunk server负责创建(trunk server是tracker选出来的),并同步到group内其他的storage,文件存储合并存储到trunk file后,根据其offset就能从trunk file读取到文件。文件在trunk file内的offset编码到文件名,决定了其在trunk file内的位置是不能更改的,也就不能通过compact的方式回收trunk file内删除文件的空间。但当trunk file内有文件删除时,其删除的空间是可以被复用的,比如一个100KB的文件被删除,接下来存储一个99KB的文件就可以直接复用这片删除的存储空间。
7、HTTP访问支持
FastDFS的tracker和storage都内置了http协议的支持,客户端可以通过http协议来下载文件,tracker在接收到请求时,通过http的redirect机制将请求重定向至文件所在的storage上;除了内置的http协议外,FastDFS还提供了通过apache或nginx扩展模块下载文件的支持。
网友评论