FastDFS是由国人开发的针对中小文件存储的轻量级分布式文件系统,使用C语言进行开发,效率高、跨平台,可以在类UNIX系统上很好运行。整体设计以简单高效为原则,具有冗余备份、负载均衡、在线扩容等性能。
FastDFS 的 Tracker 和 Storage 都内置了 http 协议的支持,客户端可以通过 http 协议来下载文件,Tracker 在接收到请求时,通过 http 的 redirect 机制将请求重定 向至文件所在的 Storage 上,除了内置的 http 协议外,FastDFS 还提供了通过 apache 或 nginx 扩展模块下载文件的支持。
轻量级
分组方式
FaSlDFS采用了分组存储方式。一个组由一台或多台存储服务器组成,同组内的多台storage之间是互备关系,文件上传、下载、删除等操作可以在组内任意一台storage上进行
优点:灵活(可以自由增删Storage扩展服务能力)、高可用
对等结构
传统的Master-Slave结构中的M是单点,写操作仅针对Master,如果Master失效需要将Slave提升为Master,实现逻辑会比较复杂
FastDFS集群中的Tracker可以有多台,Tracker和Storage都不存在单点问题,Tracker之间是对等关系,组内的Storage也是对等关系
FastDFS由三个部分组成:跟踪服务器Tracker Server、存储服务器Storage Server、客户端Client
存储服务器Storage Server
功能:负责文件的存储和冗余备份
组织方式:
一个分组的存储容量以该组内存储容量最小的那个 Storage 为准,Storage 集群的总存储容量为集群所有分组的存储容量之和
在系统容量不足时,可通过增加 Group 来实现横向扩容,当一个分组内的 Storage 访问压力过大时,可通过在 组内增加 Storage 实现纵向扩容
数据存放形式:
跟踪服务器Tracker Server
功能:负责Storage的管理和调度,接受客户端的请求并选择合适的Storage进行操作(类似中间人)
组织方式:多台Tracker组成Tracker集群,起到负载均衡作用,Tracker之间是对等的关系
工作方式:每个Storage启动后会创建多个线程并主动连接集群中所有的Tracker,向其报告自身的状态信息(所属的Group、磁盘剩余空间、文件同步状况、文件上传下载次数等),并保持周期性心跳。Tracker收到信息后保存在内存中,并根据Storage的心跳信息建立Group到组内的Storage的映射表
Tracker不记录文件所有信息,需要管理的元信息很少,使得Tracker容易扩展为Tracker-Cluster提供服务
其他分布式文件系统如HDFS将大文件切分为多个文件,需要有服务器记录文件的元信息,不适合保存小文件;但是FastDFS是非常适合保存中小文件的,同时FastDFS对小文件存储有自己的一套机制,后面会讲到
客户端Client
作为业务请求的发起方通过FastDFS提供的文件访问接口与Tracker或Storage进行通信
FastDFS由三种线程组成:accept线程、work线程、dio线程
accept线程
功能:接受新连接
工作方式:通过accept接受新连接后从task对象池中取出一个task对象,使用客户端信息封装task对象,通过轮询方式发送给对应的work线程
通信方式:work线程会通过epoll监听管道的文件描述符,accept线程向管道中写入task对象的地址唤醒work线程处理新连接
work线程
功能:处理网络IO事件
工作方式:通过epoll监听文件描述符并根据epoll返回的文件描述符调用相应的事件处理函数
dio线程
功能:从任务队列中取出任务进行磁盘的读写操作
工作方式:任务队列为空时会阻塞,当有任务到来时会被唤醒去任务队列中读取任务并执行
不从epoll检测网络IO事件主要是为了将功能解耦,让work线程处理网络IO,dio线程处理文件IO
协议由包头Header和包体Body组成
包头Header
文件上传机制
文件名机制
文件名:Storage Server IP、文件创建时间、文件大小、文件crc32、随机数拼接而成,然后再将整个二进制串进行Bae64编码转换为可打印的字符串
FileID:分组名/存储目录/一级子目录/二级子目录/文件名.后缀名
FastDFS 通过精巧的文件名机制将文件元信息隐藏在文件名中,使 FastDFS 能直接定位文件的存储位置,从而加快文件的存取速度以及避免服务器存储文件元信息占据内存
文件下载机制
FastDFS下载改进
FastDFS的下载使用单线程下载,由于FastDFS提供的文件下载API接口可以指定下载文件的偏移量以及下载块的大小,对于下载比较大的文件时, 就可以将 FastDFS 采用的单线程从某个存储服务器上下载,改进成为多个线程从服务器上下载,减少文件下载的时间
采用多个线程去分别下载文件的某一部分, 各个线程下载完成后,再根据各自的偏移量和块大小存放到一个文件中,当下载完成之后完成文件的合并
Storage启动时会根据配置文件中Tracker数量,创建相同数目的上报线程与Tracker通信,通过心跳机制向Tracker报告自身的状态信息(所属的Group、磁盘剩余空间、文件同步状况、文件上传下载次数等)
Tracker收到上报信息后以链表形式保存分组和分组内Storage的状态信息
Storage通过与Tracker获取同组内其他Storage的信息并与其建立连接,同组内的Storage通过push方式进行文件同步
binlog文件
每个Storage写文件后会同时写一份binlog
由一条条binlog记录组成,不包含文件数据,只记录文件名等元信息,每一条记录包含三个字段:文件上传时间戳、文件操作类型(源创建、副本创建、源删除、副本删除等等)、文件ID
操作类型使用单个字母进行编码,源头操作使用大写字母表示,副本操作使用小写字母表示,避免循环复制
mark文件
记录本机到组内其他Storage同步状态
binlog_index:本机最近一次同步给组内目标Storage的binlog文件索引(因为binlog文件大小有限制,所以有多个binlog文件)
binlog_offset:本机最近一次同步给组内目标Storage的最后一条binlog偏移量
Storage之间通过binlog文件进行同步,将每次同步完的索引信息记录在对应的mark文件中,下次同步时只需从mark文件记录的操作之后进行即可
增量同步
组内的每个Storage之间都会有一个同步线程进行文件同步
FastDFS的Binlog增量同步是一种异步同步机制,是弱一致性系统,会到来文件同步延迟问题与文件丢失问题
源同步(新增节点的同步)
Storage的最后最早被同步时间
记录每台storage最后一次同步给自己的时间戳中的最小个,用于判断某个文件是否存在storage上
storage会定期将该值传递给tracker,tracker根据该值就可以判断文件是否存在对应的storage上,是否可以从对应的storage上去下载
注意:FastDFS是通过比较时间戳判断文件是否同步完成,因此集群中的服务器时间要保持一致
FastDFS是采用异步同步机制,是弱一致性,保证最终一致性
如何避免数据顺序不一致
对appender文件的修改以及对文件的删除只能在源storage server上进行
FastDFS为了支持文件修改,引入了appender这一文件类型。FastDFS支持对appender类型的文件进行修改和追加等操作。如果在两台storage server上修改同一个appender文件(即使在顺序修改的情况下),可能就会因为时序问题导致数据不一致的现象发生
如何避免读取到不完整数据
先写文件再改名:
FastDFS直接借助底层文件系统来存储和管理文件。在文件复制过程中,为了避免应用端读到不完整的数据,storage server采用先写临时文件,完成后再改名的做法
为了保证数据完整性以及在异常情况下不覆盖上个版本的数据,tracker server和storage server写入重要的数据文件时,均采用了先写临时文件,然后改名的做法
负载均衡算法
FastDFS将上传文件任务的调度分成两个步骤:
Tracker能否将任务合理调度到Storage上对于整个系统的性能有很大的影响(Storage的运行状态、CPU等)
下载文件任务不需要进行分组的选择,只需要对组内的Storage进行选择,主要有两种调度算法:轮转、当前文件上传时的源Storage
FastDFS将组分为可用分组和可存储分组:
FastDFS的负载均衡算法思想时最大剩余空间算法:当任务到达Tracker时会遍历分组链表,在可用分组中寻找剩余空间最大的分组,然后检查分组的空间使用率是否超过规定的值,如果不超过则将任务调度到该分组中(该算法与存储空间有关,剩余空间越大就越有可能被选中)
不足
分组的选择依据只有磁盘剩余空间一个因素
当有上传任务请求到达时磁盘剩余空间越大的分组被调度的可能性越大,分组中分配的任务数量越多处于忙碌状态,可能会导致其他上传任务在Storage中等待被执行,而其他分组处于空闲状态
同时Storage的性能不同,处理请求所需的时间也不同,FastDFS的负载均衡算法没有考虑到Storage的CPU、内存、磁盘IO等多种因素
算法思想
分组的选择:
在原FastDFS负载均衡算法的基础上增加新的参数因子:任务使用率,任务使用率 = 已分配的任务数量 / 最大任务数量的比值
分组的负载由磁盘使用率和任务 使用率共同决定,显然,磁盘使用率越低,任务连接数量越少的分组其负载越小, 那么该分组被选中的概率越大
分组的负载由组内各个Storage负载累积而成,也就是将各个存储服务器的磁盘使用率与任务使用率的乘积累加
Storage的选择:
考虑Storage的性能和服务器的负载情况,由多个因素共同决定:CPU、内存、磁盘IO以及网络等,采用线性加权法,每个指标对应一个权重系数,权重系数组成一个权向量,然后通过层次分析法计算出权向量并定义指标函数
但是会增加算法的开销,考虑存储服务器是 IO 密集型的,受到磁盘 IO 以及网络 IO 影响较大,而同组内的存储服务器一般处于相同网段,所以在比较相对性能时,只比较磁盘 IO,减小算法开销
算法指标的获取
在Storage的实现
Storage通过命令和函数获取相关参数,并通过心跳机制向Tracker进行汇报
在Tracker的实现
Tracker将Storage上报信息保存在分组链表中,并通过算法定义计算动态负载均衡算法所需的各个变量的大小,进而完成算法的调度
算法测试
实验分5组进行,每组上传不同数量的文件,文件大小10M,每组完成不同数量任务的上传
测试结果:当任务数量在处于较低时,动态负载均衡算法的响应时间提升不大,这说明在任务数量较低时两种算法性能相当,当任务数量超过 500 时,任务数量所起的作用增大,动态负载均衡的算法作用明显,响应时间大约降低了 10%
通常我们认为大小在1MB以内的文件称为小文件,百万级数量及以上称为海量,由此量化定义海量小文件问题
问题
FastDFS对小文件采用合并的方式,可以在tracker.conf中将usr_trunk_file设置为true,当文件大小小于slot_max_size时将触发小文件存储机制
合并后的文件命名和源文件命名
合并存储后一个fileId对应的大文件中是由多个小文件组成
将合并存储后的大文件称为Trunk文件,没有合并存储的文件称为源文件
Trunk文件命名:fdfs_storage1/data/00/00/000001 ⽂件名从1开始递增,类型为int
源文件命名:文件存储的fileId需要记录保存的大文件id以及偏移量,由源Storage IP、文件创建时间戳、文件大小、crc32、trunk file ID(Trunk的ID)、offset(在Trunk文件中的偏移、量)、alloc_size(分配空间)、随机数
磁盘数据内部结构
Trunk内部由多个小文件组成,每个小文件都会有一个TrunkHeader以及紧跟在后面的真实数据
Trunk文件默认为64MB
小文件存储平衡树
在Storage内部会为每个存储路径构造一颗以空闲块大小作为关键字的空闲平衡树,相同大小的空闲块保存在链表中
每当需要存储一个文件时首先到空闲平衡树中查找大于并最接近的空闲块,然后将该空闲块从中分割出多余的部分作为新的空闲块加入平衡树中
如果查找不到满足的则创建一个新的trunk文件并加入到平衡树中,再执行上面的查找操作
FastDFS对于上传和下载都支持断点续传
上传文件
需要先上传appender类型文件,再使用append方法
上传大文件时可以采用append方式多次上传
还可以采用多线程上传1加快上传速度:
下载文件
FastDFS可以指定文件偏移量和获取文件内容大小实现断点续传与多线程下载
MB
小文件存储平衡树
在Storage内部会为每个存储路径构造一颗以空闲块大小作为关键字的空闲平衡树,相同大小的空闲块保存在链表中
每当需要存储一个文件时首先到空闲平衡树中查找大于并最接近的空闲块,然后将该空闲块从中分割出多余的部分作为新的空闲块加入平衡树中
如果查找不到满足的则创建一个新的trunk文件并加入到平衡树中,再执行上面的查找操作
FastDFS对于上传和下载都支持断点续传
上传文件
需要先上传appender类型文件,再使用append方法
上传大文件时可以采用append方式多次上传
还可以采用多线程上传1加快上传速度:
下载文件
FastDFS可以指定文件偏移量和获取文件内容大小实现断点续传与多线程下载