FastDFS 是一个开源的分布式文件系统,它对文件进行管理,功能包括:文件存储、文件同步、文件访问(文件上传、文件下载)等,解决了海量数据存储和负载均衡的问题。特别适合以中小文件(建议范围:4KB < file_size <500MB)为载体的在线服务,如相册网站、视频网站等等。
FastDFS 作为一款轻量级分布式文件系统,版本 V6.01 代码量 6.3 万行。FastDFS 用 C 语言实现,支持 Linux、FreeBSD、MacOS 等类 UNIX 系统。FastDFS 类似 google FS,属于应用级文件系统,不是通用的文件系统,只能通过专有 API 访问,目前提供了 C 和 Java SDK,以及 PHP 扩展 SDK。
FastDFS 服务端有两个角色:跟踪器(tracker)和存储节点(storage)。跟踪器主要做调度工作,在访问上起负载均衡的作用。
客户端和 Storage server 主动连接 Tracker server。Storage server 主动向 Tracker server 报告其状态信息,包括磁盘剩余空间、文件同步状况、文件上传下载次数等统计信息。Storage server 会连接集群中所有的 Tracker server,向他们报告自己的状态。Storage server 启动一个单独的线程来完成对一台 Tracker server 的连接和定时报告。需要说明的是,一个组包含的 Storage server 不是通过配置文件设定的,而是通过 Tracker server 获取到的。
不同组的 Storage server 之间不会相互通信,同组内的 Storage server 之间会相互连接进行文件同步。
Storage server 采用 binlog 文件记录文件上传、删除等更新操作。binlog 中只记录文件名,不记录文件内容。
文件同步只在同组内的 Storage server 之间进行,采用 push 方式,即源头服务器同步给目标服务器。只有源头数据才需要同步,备份数据并不需要再次同步,否则就构成环路了。有个例外,就是新增加一台 Storage server 时,由已有的一台 Storage server 将已有的所有数据(包括源头数据和备份数据)同步给该新增服务器。
Storage server 中由专门的线程根据 binlog 进行文件同步。为了最大程度地避免相互影响以及出于系统简洁性考虑,Storage server 对组内除自己以外的每台服务器都会启动一个线程来进行文件同步。
文件同步采用增量同步方式,系统记录已同步的位置(binlog 文件偏移量)到标识文件中。标识文件名格式:{dest storage IP}_{port}.mark,例如:192.168.10.30_23000.mark。
存储节点存储文件,完成文件管理的所有功能:存储、同步和提供存取接口,FastDFS 同时对文件的 meta data 进行管理。所谓文件的 meta data 就是文件的相关属性,以键值对(key value pair)方式表示,如:width=1024,其中的 key 为 width,value 为 1024。文件 meta data 是文件属性列表,可以包含多个键值对。
跟踪器和存储节点都可以由一台多台服务器构成。跟踪器和存储节点中的服务器均可以随时增加或下线而不会影响线上服务。其中跟踪器中的所有服务器都是对等的,可以根据服务器的压力情况随时增加或减少。
为了支持大容量,存储节点(服务器)采用了分卷(或分组)的组织方式。存储系统由一个或多个卷组成,卷与卷之间的文件是相互独立的,所有卷 的文件容量累加就是整个存储系统中的文件容量。一个卷可以由一台或多台存储服务器组成,一个卷下的存储服务器中的文件都是相同的,卷中的多台存储服务器起 到了冗余备份和负载均衡的作用。
FastDFS 不会对文件进行分块存储,客户端上传的文件和 Storage server 上的文件一一对应。
存储空间以group内容量最小的storage为准,所以建议group内的多个storage尽量配置相同,以免造成存储空间的浪费。
为了避免单个目录下的文件数太多,在storage第一次启动时,会在每个数据存储目录里创建2级子目录,每级256个,总共65536个文件,新写的文件会以hash的方式被路由到其中某个子目录下,然后将文件数据直接作为一个本地文件存储到该目录中。
在卷中增加服务器时,同步已有的文件由系统自动完成,同步完成后,系统自动将新增服务器切换到线上提供服务。
当存储空间不足或即将耗尽时,可以动态添加卷。只需要增加一台或多台服务器,并将它们配置为一个新的卷,这样就扩大了存储系统的容量。
FastDFS 中的文件标识分为两个部分:卷名和文件名,二者缺一不可。
上传文件交互过程:
写文件时,客户端将文件写至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的同步时间戳,本例中T1即为C的同步时间戳为T1(即所有T1以前写的数据都已经同步到C上了);同理,根据上述规则,tracker会为A、B生成一个同步时间戳。
下载文件交互过程:
需要说明的是,client 为使用 FastDFS 服务的调用方,client 也应该是一台服务器,它对 tracker 和 storage 的调用均为服务器间的调用。
将小文件合并存储主要解决如下几个问题:
FastDFS在V3.0版本里的机制,可将多个小文件存储到一个大的文件(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的文件就可以直接复用这片删除的存储空间。
FastDFS的tracker和storage都内置了http协议的支持,客户端可以通过http协议来下载文件,tracker在接收到请求时,通过http的redirect机制将请求重定向至文件所在的storage上;除了内置的http协议外,FastDFS还提供了下载文件的支持。
FastDFS提供了设置/获取文件扩展属性的接口(setmeta/getmeta),扩展属性以key-value对的方式存储在storage上的同名文件(拥有特殊的前缀或后缀),比如/group/M00/00/01/some_file为原始文件,则该文件的扩展属性存储在/group/M00/00/01/.some_file.meta文件(真实情况不一定是这样,但机制类似),这样根据文件名就能定位到存储扩展属性的文件。
以上两个接口作者不建议使用,额外的meta文件会进一步“放大”海量小文件存储问题,同时由于meta非常小,其存储空间利用率也不高,比如100bytes的meta文件也需要占用4K(block_size)的存储空间。
FastDFS还提供appender file的支持,通过upload_appender_file接口存储,appender file允许在创建后,对该文件进行append操作。实际上,appender file与普通文件的存储方式是相同的,不同的是,appender file不能被合并存储到trunk file。
在 FastDFS 中,客户端上传文件时,文件 ID 不是由客户端指定,而是由 Storage server 生成后返回给客户端的。文件 ID 中包含了组名、文件相对路径和文件名,Storage server 可以根据文件 ID 直接定位到文件。因此 FastDFS 集群中根本不需要存储文件索引信息,这是 FastDFS 比较轻量级的一个例证。而其他文件系统则需要存储文件索引信息,这样的角色通常称作 NameServer。其中 mogileFS 采用 MySQL 数据库来存储文件索引以及系统相关的信息,其局限性显而易见,MySQL 将成为整个系统的瓶颈。
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的心跳信息,生成元数据信息来提供读写服务。
从FastDFS的整个设计看,基本上都已简单为原则。比如以机器为单位备份数据,简化了tracker的管理工作;storage直接借助本地文件系统原样存储文件,简化了storage的管理工作;文件写单份到storage即为成功、然后后台同步,简化了写文件流程。但简单的方案能解决的问题通常也有限,FastDFS目前尚存在如下问题(欢迎探讨)。
数据安全性
存储空间利用率
负载均衡
客户端将一个文件上传到一台 Storage server 后,文件上传工作就结束了。由该 Storage server 根据 binlog 中的上传记录将这个文件同步到同组的其他 Storage server。这样的文件同步方式是异步方式,异步方式带来了文件同步延迟的问题。新上传文件后,在尚未被同步过去的 Storage server 上访问该文件,会出现找不到文件的现象。FastDFS 是如何解决文件同步延迟这个问题的呢?
文件的访问分为两种情况:文件更新和文件下载。文件更新包括设置文件附加属性和删除文件。文件的附加属性包括文件大小、图片宽度、图片高度等。FastDFS 中,文件更新操作都会优先选择源 Storage server,也就是该文件被上传到的那台 Storage server。这样的做法不仅避免了文件同步延迟的问题,而且有效地避免了在多台 Storage server 上更新同一文件可能引起的时序错乱的问题。
要回答这个问题,需要先了解文件名中包含了什么样的信息。Storage server 生成的文件名中,包含了源 Storage server 的 IP 地址和文件创建时间等字段。文件创建时间为 UNIX 时间戳,后面称为文件时间戳。从文件名或文件 ID 中,可以反解出这两个字段。
然后我们再来看一下,Tracker server 是如何准确地知道一个文件已被同步到一台 Storage server 上的。前面已经讲过,文件同步采用主动推送的方式。另外,每台 storage server 都会定时向 tracker server 报告它向同组的其他 storage server 同步到的文件时间戳。当 tracker server 收到一台 storage server 的文件同步报告后,它会依次找出该组内各个 storage server(后称作为 S)被同步到的文件时间戳最小值,作为 S 的一个属性记录到内存中。
答案见:下载文件过程
我们在使用 FastDFS 部署一个分布式文件系统的时候,通过 FastDFS 的客户端 API 来进行文件的上传、下载、删除等操作。同时通过 FastDFS 的 HTTP 服务器来提供 HTTP 服务。但是 FastDFS 的 HTTP 服务较为简单,无法提供负载均衡等高性能的服务,我们使用 FastDFS 的 Nginx 模块来弥补这一缺陷。
FastDFS 通过 tracker 服务器,将文件放在 Storage 服务器存储,但是同组之间的服务器需要复制文件,有延迟的问题。假设 tracker 服务器将文件上传到了 192.168.10.32,文件 ID 已经返回客户端,这时后台会将这个文件复制到 192.168.10.33,如果复制没有完成,客户端就用这个 ID 在 192.168.10.33 取文件,肯定会出现错误。而 fastdfs-nginx-module 可以重定向连接到源服务器取文件,避免客户端由于复制延迟的问题出现错误。