FastDFS分布式文件系统

FastDFS分布式文件系统

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

FastDFS分布式文件系统_第1张图片

存储服务器Storage Server

功能:负责文件的存储和冗余备份

组织方式:

  • FastDFS采用分组方式将Storage集群以分组为单位进行划分,各个组之间互相独立,可以起到负载均衡、应用隔离和副本数定制的作用
  • 同组内可以有多台存储服务器,之间互为备份,同组的Storage之间相互连接进行文件同步

一个分组的存储容量以该组内存储容量最小的那个 Storage 为准,Storage 集群的总存储容量为集群所有分组的存储容量之和

在系统容量不足时,可通过增加 Group 来实现横向扩容,当一个分组内的 Storage 访问压力过大时,可通过在 组内增加 Storage 实现纵向扩容

数据存放形式:

  • Storage存储文件依赖于本地文件系统
  • 第一次启动时会在每个数据存储目录下创建两级子目录,每一级256个,共65536个目录,新写的文件会以哈希方式被存储到某个子目录下
    • 避免单个目录下文件数太多
    • 通过目录加快查询速度(类似索引)

跟踪服务器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进行通信

网络IO模型

FastDFS由三种线程组成:accept线程、work线程、dio线程

FastDFS分布式文件系统_第2张图片

accept线程

功能:接受新连接

工作方式:通过accept接受新连接后从task对象池中取出一个task对象,使用客户端信息封装task对象,通过轮询方式发送给对应的work线程

通信方式:work线程会通过epoll监听管道的文件描述符,accept线程向管道中写入task对象的地址唤醒work线程处理新连接

work线程

功能:处理网络IO事件

工作方式:通过epoll监听文件描述符并根据epoll返回的文件描述符调用相应的事件处理函数

  • 新连接事件:接受新连接并在epoll中注册新连接的可读事件并进行监听
  • 可读事件:获取数据并将数据封装为任务添加到任务队列中同时通过条件变量唤醒dio线程处理任务
  • 可写事件:将用户缓冲区中的内容发送出去

dio线程

功能:从任务队列中取出任务进行磁盘的读写操作

工作方式:任务队列为空时会阻塞,当有任务到来时会被唤醒去任务队列中读取任务并执行

不从epoll检测网络IO事件主要是为了将功能解耦,让work线程处理网络IO,dio线程处理文件IO

协议格式

协议由包头Header和包体Body组成

包头Header

  • pkg_len:8字节,记录body的长度
  • cmd:1字节,记录操作对应的命令
  • status:1字节,记录响应的状态码

文件上传

文件上传机制

FastDFS分布式文件系统_第3张图片

  • 发送请求:客户端向集群中任意一台Tracker Server发起TCP连接,连接建立后客户端向Tracker发送上传请求报文
  • 查询可用的Storage:Tracker收到客户端的上传文件请求后根据策略选择合适的Storage返回给客户端
    • 选择Group:轮询、指定某一个Group、选择最大剩余空间的Group
    • 选择Storage:轮询、按IP排序、按优先级
  • 上传文件:客户端收到Storage信息后就会向对应的Storage发起TCP连接,连接建立后开始上传文件;首先会发送一个报文告知文件大小,然后再将文件发送给Storage
  • 选择存储路径:Storage收到客户端上传文件请求后会根据配置为文件分配一个数据存储目录
    • 轮询、剩余存储空间最大的优先
  • 生成文件名:Storage为文件生成一个文件名,由Storage Server IP、文件创建时间、文件大小、文件crc32、随机数拼接而成,然后再将整个二进制串进行Bae64编码转换为可打印的字符串
    • 随机数主要是为了避免出现相同的文件名
  • 选择两级目录:Storage对FileId进行两次Hash选择文件存储的子目录
  • 生成文件名:当文件被存储到某个子目录后认为文件存储成功,接下来Storage会为该文件生成一个FileId:分组名/存储目录/一级子目录/二级子目录/文件名.后缀名
  • 上传成功返回访问路径:Storage将文件写入磁盘后向客户端返回文件的FileId

文件名机制

文件名:Storage Server IP、文件创建时间、文件大小、文件crc32、随机数拼接而成,然后再将整个二进制串进行Bae64编码转换为可打印的字符串

FileID:分组名/存储目录/一级子目录/二级子目录/文件名.后缀名

FastDFS 通过精巧的文件名机制将文件元信息隐藏在文件名中,使 FastDFS 能直接定位文件的存储位置,从而加快文件的存取速度以及避免服务器存储文件元信息占据内存

FastDFS分布式文件系统_第4张图片

文件下载

文件下载机制

FastDFS分布式文件系统_第5张图片

  • 发送请求:客户端向集群中任意一台Tracker Server发起TCP连接,连接建立后客户端向Tracker发送下载请求报文,报文中携带文件的FileId信息
  • 查询可用的Storage:Tracker收到客户端下载请求后从FileId中解析出文件所属的Group,在Group中选择一台可用的Storage将其IP地址和端口号发送给客户端
    • 文件上传到的源头Storage(只要源Storage存在肯定就包含该文件)
    • 文件创建时间戳 == Storage被同步到的时间戳,且当前时间-文件创建时间戳 > 文件同步最大时间(文件创建后认为经过最大同步时间后肯定已同步到其他Storage中)
    • 文件创建时间 < Storage被同步到的时间戳 (同步时间戳之前的文件确定已经同步了)
    • 当前时间 - 文件创建时间戳 > 同步延迟阈值 (经过同步延迟阈值时间认为文件肯定已经同步了)
  • 下载文件:客户端收到Storage信息后向Storage发送连接请求,连接建立后Storage根据FileId解析出文件路径找到文件并从磁盘读取后返回给客户端

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之间进行同步
  • 源头数据才需要同步,备份数据不需要同步,通过操作类型的大小写字母判断需不需要同步
  • 当新增一台storage是由已有的一台storage将已有的数据(源头和备份)同步给新增服务器

同步机制

增量同步

组内的每个Storage之间都会有一个同步线程进行文件同步

FastDFS分布式文件系统_第6张图片

  • 同步线程读取目标Storage对应的mark文件,从mark文件中获取与目标Storage最近一次同步的binlog文件索引和偏移量
  • 源Storage打开对应的binlog文件并定位到偏移量指定的位置开始同步文件,同步成功后修改mark文件的binlog_offset字段
  • 源Storage向Tracker发送同步时间戳,向Tracker汇报此时间戳之前的所有同步操作已完成

FastDFS的Binlog增量同步是一种异步同步机制,是弱一致性系统,会到来文件同步延迟问题与文件丢失问题

  • 同步延迟问题:FastDFS通过指定源服务器或根据同步时间戳等机制将请求分发到源Storage或已完成同步的服务器上解决
  • 文件丢失问题:可以采用半异步机制,当大部分的Storage同步完成才算文件上传成功

源同步(新增节点的同步)

FastDFS分布式文件系统_第7张图片

  • 当Group中新增一台Storage时,新Storage会先与Tracker建立连接进行通信
  • Tracker收到后会将新Storage状态设置为同步等待状态(WAIT_SYNING),然后将组内Storage列表返回给新Storage,同时将新的Storage列表返回给组内原有的Storage
  • 新Storage向Tracker请求同步的源Storage地址,Tracker将源Storage信息返回给新Storage
  • 组内其他Storage得知有新Storage加入后会向Tracker查询源Storage信息,并将自己的信息与源Storage信息对比,如果发现自己是源Storage则将新Storage状态设置为正在同步状态(SYNING),并将数据同步到新Storage上
  • 当源同步完成之后将新Storage状态设置为离线状态(OFFLINE)并向Tracker返回源同步成功,由Tracker将新Storage状态设置为ACTIVE,即新Storage已完成同步可以对外提供服务

Storage的最后最早被同步时间

记录每台storage最后一次同步给自己的时间戳中的最小个,用于判断某个文件是否存在storage上

FastDFS分布式文件系统_第8张图片

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负载均衡算法

FastDFS已有的负载均衡算法

负载均衡算法

FastDFS将上传文件任务的调度分成两个步骤:

  1. 分组的选择:轮转、手动选择、选择分组中存储空间剩余最大的组
  2. 组内Storage的选择:轮转、按照IP排序的第一个Storage、按照优先级排序的第一个Storage

Tracker能否将任务合理调度到Storage上对于整个系统的性能有很大的影响(Storage的运行状态、CPU等)

下载文件任务不需要进行分组的选择,只需要对组内的Storage进行选择,主要有两种调度算法:轮转、当前文件上传时的源Storage

FastDFS将组分为可用分组和可存储分组:

  • 可用分组
    • 分组中有处于活跃状态的Storage
    • 组内的Storage的剩余空间不小于某个值
  • 可存储分组
    • 在可用分组的基础上增加了存储空间使用率,不能超过规定的比值

FastDFS的负载均衡算法思想时最大剩余空间算法:当任务到达Tracker时会遍历分组链表,在可用分组中寻找剩余空间最大的分组,然后检查分组的空间使用率是否超过规定的值,如果不超过则将任务调度到该分组中(该算法与存储空间有关,剩余空间越大就越有可能被选中)

不足

分组的选择依据只有磁盘剩余空间一个因素

当有上传任务请求到达时磁盘剩余空间越大的分组被调度的可能性越大,分组中分配的任务数量越多处于忙碌状态,可能会导致其他上传任务在Storage中等待被执行,而其他分组处于空闲状态

同时Storage的性能不同,处理请求所需的时间也不同,FastDFS的负载均衡算法没有考虑到Storage的CPU、内存、磁盘IO等多种因素

FastDFS负载均衡算法的改进

算法思想

分组的选择:

在原FastDFS负载均衡算法的基础上增加新的参数因子:任务使用率,任务使用率 = 已分配的任务数量 / 最大任务数量的比值

分组的负载由磁盘使用率和任务 使用率共同决定,显然,磁盘使用率越低,任务连接数量越少的分组其负载越小, 那么该分组被选中的概率越大

分组的负载由组内各个Storage负载累积而成,也就是将各个存储服务器的磁盘使用率与任务使用率的乘积累加

Storage的选择:

考虑Storage的性能和服务器的负载情况,由多个因素共同决定:CPU、内存、磁盘IO以及网络等,采用线性加权法,每个指标对应一个权重系数,权重系数组成一个权向量,然后通过层次分析法计算出权向量并定义指标函数

但是会增加算法的开销,考虑存储服务器是 IO 密集型的,受到磁盘 IO 以及网络 IO 影响较大,而同组内的存储服务器一般处于相同网段,所以在比较相对性能时,只比较磁盘 IO,减小算法开销

算法指标的获取

  • 任务使用率的获取
    • Storage上已分配的任务数量:存储服务器默认在 23000 端口工作,通过netstat 命令的可以显示网络连接、 路由表以及网络接口等信息,查询在 23000 端口已经建立的连接并进行统计就可以获取存储服务器上已经连接的任务数量
    • Storage最大任务数:从配置文件 storage.conf 读取
  • 磁盘读写性能的获取
    • hdparm可用于显示磁盘相关信息,可以测试磁盘速度
  • 磁盘使用率的获取
    • 使用结构体 struct statfs,然后利用函数 statfs 查询文件系统的信息,根据 f_blocks 和 f_bavail 字段获取磁盘总空间和剩余空间,然后将其保存到内存中

在Storage的实现

FastDFS分布式文件系统_第9张图片

Storage通过命令和函数获取相关参数,并通过心跳机制向Tracker进行汇报

在Tracker的实现

FastDFS分布式文件系统_第10张图片

Tracker将Storage上报信息保存在分组链表中,并通过算法定义计算动态负载均衡算法所需的各个变量的大小,进而完成算法的调度

算法测试

实验分5组进行,每组上传不同数量的文件,文件大小10M,每组完成不同数量任务的上传

测试结果:当任务数量在处于较低时,动态负载均衡算法的响应时间提升不大,这说明在任务数量较低时两种算法性能相当,当任务数量超过 500 时,任务数量所起的作用增大,动态负载均衡的算法作用明显,响应时间大约降低了 10%

海量小文件LSOF

通常我们认为大小在1MB以内的文件称为小文件,百万级数量及以上称为海量,由此量化定义海量小文件问题

问题

  • 浪费空间:Linux通过inode存储文件信息,会消耗硬盘空间,当磁盘格式化时操作系统会将磁盘划分为两个区域:数据区和inode区。每个inode节点大小一般是128字节或256字节,inode节点总数在格式化时给定,一般是1kb或2kb设置一个inode,也就是说一个文件至少占用1kb或2kb,那么如果有大量小于1kb的文件就会导致大量的磁盘空间被浪费(磁盘利用率可能<50%)
  • 大量的小文件也会导致在增加删除查找文件时需要遍历过多的inode节点:Linux下文件的访问需要经过目录项、inode、data,文件系统通常采用Hash树、 B+树或B*树来组织和索引目录,这种方法不能在数以亿计的大目录中很好的扩展,海量目录下检索效率会明显下降。正是由于单个目录元数据组织能力的低效,文件系统使用者通常被鼓励把文件分散在多层次的目录中以提高性能。然而,这种方法会进一步加大路径查询的开销

FastDFS对小文件的处理

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文件存储结构

磁盘数据内部结构

Trunk内部由多个小文件组成,每个小文件都会有一个TrunkHeader以及紧跟在后面的真实数据

Trunk文件默认为64MB

小文件存储平衡树

在Storage内部会为每个存储路径构造一颗以空闲块大小作为关键字的空闲平衡树,相同大小的空闲块保存在链表中

每当需要存储一个文件时首先到空闲平衡树中查找大于并最接近的空闲块,然后将该空闲块从中分割出多余的部分作为新的空闲块加入平衡树中

如果查找不到满足的则创建一个新的trunk文件并加入到平衡树中,再执行上面的查找操作

断点续传

FastDFS对于上传和下载都支持断点续传

上传文件

需要先上传appender类型文件,再使用append方法

上传大文件时可以采用append方式多次上传

还可以采用多线程上传1加快上传速度:

  • 上传appender类型文件
  • 调用truncate方法将改appender文件设置为最终文件大小
  • 调用modify方法并发上传文件分片

下载文件

FastDFS可以指定文件偏移量和获取文件内容大小实现断点续传与多线程下载
MB

小文件存储平衡树

在Storage内部会为每个存储路径构造一颗以空闲块大小作为关键字的空闲平衡树,相同大小的空闲块保存在链表中

每当需要存储一个文件时首先到空闲平衡树中查找大于并最接近的空闲块,然后将该空闲块从中分割出多余的部分作为新的空闲块加入平衡树中

如果查找不到满足的则创建一个新的trunk文件并加入到平衡树中,再执行上面的查找操作

断点续传

FastDFS对于上传和下载都支持断点续传

上传文件

需要先上传appender类型文件,再使用append方法

上传大文件时可以采用append方式多次上传

还可以采用多线程上传1加快上传速度:

  • 上传appender类型文件
  • 调用truncate方法将改appender文件设置为最终文件大小
  • 调用modify方法并发上传文件分片

下载文件

FastDFS可以指定文件偏移量和获取文件内容大小实现断点续传与多线程下载

你可能感兴趣的:(服务器)