前言
今天给大家介绍的是FastDFS,一个开源的分布式文件系统,也是入职之后接触到的一个技术,由于公司项目业务需求,服务器里存了上亿量级的文件,所以使用了这么一项技术来存储这些文件,我也就随之开始了解这项技术,并且在这里和大家一起从0到1地开始了解它。
FastDFS介绍
FastDFS是一个以C语言开发的开源轻量级分布式文件系统,由阿里巴巴开发并开源。它对文件进行管理,功能包括:文件存储、文件同步、文件访问(上传、下载)等。解决了大容量存储和负载均衡的问题。特别适合以文件为载体的在线服务,如相册网站、视频网站等等。
FastDFS为互联网量身定制,充分考虑了冗余备份、负载均衡、线性扩容等机制,并注重高可用、高性能等指标,使用FastDFS很容易搭建一套高性能的文件服务器集群提供文件上传、下载等服务。
从0,自己的一些疑问:FastDFS过时了吗?
相信这也是很多同学想要问的一些问题,我还没有了解这个技术的时候,也同样有这样的疑问。
首先,现在有很多文件存储都会选择像七牛云、阿里云OSS等云服务,为什么要自己搭一套文件服务器增加维护成本呢?
其次,这并不是面试的热点,甚至在入职之前自己都没有接触过,甚至甚至,没有听说过,反倒是那些热门的技术,就算自己不主动去了解,当遇到的时候,大致也能知道是用来干嘛的。
那么我来说说我的理解,首先这个技术一定是还没有过时的,因为有些特殊的文件,因为信息安全顾虑等原因,不会选择公有云服务器,还有基于成本考虑,还有很多的中型互联网公司仍然是基于FastDFS来做自己的文件服务器的。另外,FastDFS作为一个分布式服务器,对轻量级、横向扩展、容灾备份、高可用、高性能、负载均衡都有着充分的考虑,依旧是一个文件服务器的不二之选。
那么为什么这样一项技术在如今却很少有人了解呢?
第一,我认为是需求所致,与其它业务相比,需要存储大量文件的业务相对之下还是比较少。如果文件存储量不大,按照传统的文件存储方式也不会有什么大的问题。
第二,现在有七牛云、阿里云OSS等公司提供对象存储,加之国内对于”上云“的追捧,就很少有人愿意自己搭服务器来做文件存储服务了。
当然对于一个技术人来说,各种各样的技术都得去学习,去适应,所以这篇文章希望可以帮助到感兴趣的同学,或是在工作中遇到高量级文件存储的同学,FastDFS是不错的选择。
传统文件存储方式
这是传统文件存储的方式,服务器上甚至不需要装任何的应用,只需要有SFTP服务,我们就可以写对应的代码,完成文件的CRUD。
这样的好处就是很方便,只需要一台机器,几行代码就能搞定文件的存储,但是这种方式的瓶颈和缺陷是很明显的。
首先,对于单体服务器来说,不考虑宕机的情况,单体文件服务器的带宽、磁盘容量是有上限的,那么当文件的体积占满了整个磁盘,我们只能选择扩展,但是这种单体服务器的方式,对于扩容就不太友好了,我们可以想想,难道我们要将原有的硬盘数据拷到一个更大的硬盘里,然后更换硬盘吗?
除了扩展以外,我们还需要面临一个问题,就是文件的查找,如果我们将所有文件都存放到一起,文件的数量如果到了一定的数量,就会面临磁盘IO速度瓶颈,不知道大家有没有遇到过下图中这种场景:
如果我们需要在一个磁盘中找到某个文件,如果没有一个路径,或者路径下有很多文件,那么系统就会扫描磁盘,我们都知道,计算机体系结构中,关于速度,CPU>内存>硬盘
,如果在生产环境下,真的需要存储大量的文件,假设存储的是用户的头像,那么用户每次打开APP,就需要等待个十几秒,自己的头像才会显示,那这个app估计没有人会使用吧。
有同学可能会说,那我们可以使用缓存啊,Redis的String类型是可以存储二进制数据的,而且Redis的String类型是一个Key对应一个值,查询效率会很高。的确这样在查询效率上可以达到,但是我们按照一张图片1M来计算,缓存又能存多少张图片呢?很显然这是一个十分昂贵的方式。
刚才我们考虑的都是服务器不宕机的状态,那么假设服务器宕机,那么我们就无法再提供数据存储服务;如果硬盘损坏,那么所有的数据都将丢失。
分布式文件系统
上文中说了传统文件存储方式的一些缺陷和弊端,这其实也是所有“单点“的弊端,无论是单点数据库、或者单点缓存、又或者是单点网关、单点注册中心,都在往分布式集群的方向发展。
总结一下,单点文件系统大致有这些弱点:
1. 磁盘容量存在瓶颈
2. IO速度存在瓶颈
3. 宕机、硬盘损坏数据丢失的风险
那么对于文件系统,我们如何使用分布式的方式来解决上述的缺陷呢?
- 解决磁盘容量瓶颈
上文中提到,单服务器文件系统存在磁盘容量瓶颈的原因是磁盘无法很方便的进行扩容,我们通常需要从硬件的层面来考虑扩容硬盘,如:更换大容量硬盘。
这种方式显然是不现实的,因为更换硬盘意味着我们需要关服务器,生产环境服务器关停三十秒都是很严重的生产事故,所以我们只能使用服务器横向扩展的方式,如果不能更换硬盘,那么我们就加服务器。
- 这样我们就可以使用多台服务器共同来构成我们的文件系统了,每个文件服务器都是一个独立的节点,来存储不同的文件,根据特定的逻辑(这里需要自己写),来决定文件需要存储到哪个文件服务器中。这样即使服务器容量满了,我们也还可以继续横向扩展,理论上这样我们的文件系统是没有容量上限的。
- 解决IO速度瓶颈
刚才我们解决了单点文件服务器容量瓶颈,但是如果某台或者某几台服务器上的文件数量过多(查询效率降低),或者有大量的用户访问某一台服务器,还是会造成IO速度瓶颈。那么要如何解决这个问题。
我们可以想一想类似的案例——MySQL数据库。
众所周知,MySQL的数据也是存在硬盘中的,而我们在写SQL语句的时候,为了保证查询效率,通常会避免全表扫描,而是通过索引让其找到我们对应的那条数据。
所以我们也可以通过这种方式,避免全盘扫描或者大范围扫描,而我们都知道,操作系统对于文件是有一个天然的索引的,就是我们的多级目录。FastDFS也正是利用了这个来增加我们文件IO的效率的,这个暂且不提,下文中会展示。
- 解决宕机、硬盘损坏数据丢失的风险
能否解决这个问题才是分布式文件系统和单机文件系统最根本的区别,因为无论是单机磁盘容量瓶颈还是IO速度瓶颈,我们都可以通过增加硬件配置来解决,只不过不太方便且成本太高罢了。而单机模式是绝对无法解决宕机造成的文件服务失效,或者硬盘损坏造成的数据丢失的,因为数据只存在一份。
那么我们思考一下分布式文件系统是如何来解决这些问题的。
- 首先我们需要解决宕机的问题,如上图,我们有多个文件服务器节点,但是如果我们自己写逻辑来决定某个文件应该存哪个服务器上,假设那个服务器此时宕机了,文件依旧是无法存入的,当然我们可以继续写逻辑来决定如果宕机了之后应该怎么办,但是FastDFS中已经替我们实现了,Tracker节点可以帮助我们选择文件应该上传到哪个服务器上,并且还可以在某个节点宕机的时候选择其从节点(备份节点)进行文件上传,防止因为宕机造成的无法操作文件。
那么根据上面这幅图,第二个问题也就很好理解了,假设某台服务器硬盘损坏了,那么数据依旧会存在备份,即使备份服务器也损坏了,数据也只会丢失一部分,而不会全部丢失。
FastDFS
上面说了这么多的关于分布式文件系统可以解决的一些实际问题,那么就直接切入今天的主题——FastDFS。
- 整体架构
FastDFS文件系统由两大部分组成,客户端和服务端。
客户端通常指我们写的程序(当然FastDFS也提供了客户端测试程序),例如我们使用Java去连接FastDFS、操作文件,那么我们的Java程序就是一个客户端,FastDFS提供专有API访问,目前提供了C、Java和PHP等编程语言的API,用来访问FastDFS文件系统。
服务端由两个部分组成,分别是跟踪器(Tracker)和存储节点(Storage)。
跟踪器(Tracker):主要做调度工作,类似微服务注册中心,在内存中记录集群存储节点的storage的状态信息,是客户端和服务端存储节点storage的枢纽,因为相关信息全部在内存中,每个Storage在启动后会连接Tracker,告知自己所属的group等信息,并周期性发送心跳,TrackerServer的性能非常高,假设我们有上百个Storage节点,我们只需要3台左右的Tracker就足够了。
存储节点(Storage)用于存储文件,包括文件和文件属性(metaData)都保存到服务器磁盘上,完成文件管理的所有功能:文件存储、文件同步和文件访问等。
Storage以group为组织单位,一个group内可以包含多台Storage机器,数据互为备份,总存储空间以group内容量最小的storage为准(木桶),所以建议一个group内的机器存储空间大小应该尽量相同,以免造成空间的浪费。Storage在第一次启动时,会在每一个存储目录里创建二级目录,共计256 * 256个目录,我们上传的文件会以Hash的方式被路由到其中某个子目录下。
- 工作流程
- 上传
下载
- 客户端发送下载请求,Tracker根据文件信息,返回Storage地址和端口(客户端也可以通过自己存储的文件位置直接访问Storage)。
- 客户端访问Storage,Storage根据file_id(组名、虚拟磁盘、二级目录、文件名)查找到文件,返回文件数据。
- 当客户端发起上传请求时,会先访问Tracker,由于Storage定期向Tracker发送状态信息,所以Tracker中存有所有Storage Group的信息。
- Tracker根据本地的Storage Group信息,为客户端上传的文件分配Storage Group,并返回给客户端。
- 客户端拿到Storage Group地址和端口后,上传文件到指定的Storage Group中。
- Storage返回文件的路径信息和文件名。
- Client将文件信息存储到本地。
- 单机安装
- 安装前准备
- 安装
安装FastDFS需要两个源码包,分别是libfastcommon-1.0.43.tar.gz和fastdfs-6.06.tar.gz。
这里附上作者的github地址:fastdfs,libfastcommon,大家可以到这里下载对应的包。
下载完成后,将其上传到我们的linux服务器中
[root@localhost SoftwareInstallPackage]# ll 总用量 971800 -rw-r--r--. 1 root root 809328 9月 9 23:10 fastdfs-6.06.tar.gz -rw-r--r--. 1 root root 166526 9月 9 23:10 libfastcommon-1.0.43.tar.gz
- 分别运行
tar -zxvf fastdfs-6.06.tar.gz
和tar -zxvf libfastcommon-1.0.43.tar.gz
对其进行解压,解压后进入libfastcommon-1.0.43目录中运行sh make.sh
,运行完毕后运行sh make.sh install
,然后进入fastdfs-6.06目录中执行相同的操作,即可完成安装。
安装成功后进入/usr/bin
目录下,如果存在很多fdfs开头的命令,则证明安装成功。
[root@localhost bin]# ll|grep fdfs -rwxr-xr-x. 1 root root 362208 9月 9 23:17 fdfs_appender_test -rwxr-xr-x. 1 root root 361984 9月 9 23:17 fdfs_appender_test1 -rwxr-xr-x. 1 root root 348872 9月 9 23:17 fdfs_append_file -rwxr-xr-x. 1 root root 348480 9月 9 23:17 fdfs_crc32 -rwxr-xr-x. 1 root root 348904 9月 9 23:17 fdfs_delete_file -rwxr-xr-x. 1 root root 349640 9月 9 23:17 fdfs_download_file -rwxr-xr-x. 1 root root 349592 9月 9 23:17 fdfs_file_info -rwxr-xr-x. 1 root root 364912 9月 9 23:17 fdfs_monitor -rwxr-xr-x. 1 root root 349128 9月 9 23:17 fdfs_regenerate_filename -rwxr-xr-x. 1 root root 1280096 9月 9 23:17 fdfs_storaged -rwxr-xr-x. 1 root root 372080 9月 9 23:17 fdfs_test -rwxr-xr-x. 1 root root 367200 9月 9 23:17 fdfs_test1 -rwxr-xr-x. 1 root root 512312 9月 9 23:17 fdfs_trackerd -rwxr-xr-x. 1 root root 349832 9月 9 23:17 fdfs_upload_appender -rwxr-xr-x. 1 root root 350848 9月 9 23:17 fdfs_upload_file
- 而/etfc/fdfs目录中存放了所有的FastDFS的配置文件:
然后进入/etc/fdfs,这里存放了所有fastDFS的配置文件
[root@localhost fdfs]# ll 总用量 32 #客户端连接配置 -rw-r--r--. 1 root root 1909 9月 9 23:17 client.conf.sample #存储节点配置 -rw-r--r--. 1 root root 10246 9月 9 23:17 storage.conf.sample -rw-r--r--. 1 root root 620 9月 9 23:17 storage_ids.conf.sample #追踪器配置 -rw-r--r--. 1 root root 9138 9月 9 23:17 tracker.conf.sample
- 最后一步,我们需要进入FastDFS安装包解压后的conf目录下,找到http.conf和mime.types将其复制到/etc/fdfs目录下。
[root@localhost conf]# pwd /WorkSpace/Software/fastdfs-6.06/conf [root@localhost conf]# cp http.conf /etc/fdfs/ [root@localhost conf]# cp mime.types /etc/fdfs/
- 这样我们就完成了FastDFS的安装。
- 配置文件详解
安装完成后需要先把配置文件配置好才能够正常启动,这里会贴上tracker.conf、storage.conf、client.conf的所有配置项,就当作一个配置模板吧,配置的时候可以直接copy。
首先是tracker.conf:
# 这个配置文件是否 不生效(这里笔者个人认为,改成是否生效会比较好),true代表不生效,false代表生效 disabled = false # 是否绑定ip,如果不填就代表所有的ip都提供服务,常用于服务器有多个ip但只希望一个ip提供服务 bind_addr = # Tracker的端口号 port = 22122 # 连接超时的时间 单位为s connect_timeout = 5 # Tracker的网络超时,单位为秒。发送或接收数据时,如果在超时时间后还不能发送或接收数据,则本次网络通信失败。 network_timeout = 60 # Tracker服务的数据存放地址,这个目录必须是存在的 base_path = /home/yuqing/fastdfs # 系统提供的最大连接数 max_connections = 1024 # 接收线程数 建议为1 accept_threads = 1 # 工作线程数 推荐设置为CPU线程数 work_threads = 4 # 最小网络缓存 默认值为8KB min_buff_size = 8KB # 最大网络缓存 默认值为128KB max_buff_size = 128KB # 上传文件选择group的方式 # 0:轮询 # 1:指定一个group # 2:负载均衡,选择一个最多空闲空间的group来上传文件 store_lookup = 2 # 当store_lookup设置为1时,必须在这个参数中设置一个group用来上传文件 # 当store_lookup设置不为1时,这个参数无效 store_group = group2 # 选择group中的哪一个storage服务器用来上传文件 # 0:轮询 # 1:按照ip地址排序,ip最小的那个服务器用来上传文件 # 2:根据优先级进行排序,上传优先级在storage配置文件中进行配置,配置项为upload_priority store_server = 0 # 选择storage服务器中的哪一个目录进行上传,一个storage中可以有多个存放文件的base_path(可以理解为磁盘分区) # 0:轮询 # 2:负载均衡,选择剩余空间最多的一个路径来上传文件 store_path = 0 # 选择哪个storage服务器作为下载文件的服务器 # 0:轮询 # 1:文件上传到哪个storage服务器就去哪个storage服务器上下载 download_server = 0 # storage服务器上为其他应用保存的空间 # 可以用绝对值或者百分比:10G或者1024M或者1024K或者XX.XX% # 如果同组中的服务器硬盘大小相同 以最小的为准,达到最小的那个值,则无法再进行上传 reserved_storage_space = 20% # 标准日志级别从小到大依次:debug<info<notice<warn for warning<error<crit for critcal<alert<emerg for emergency log_level = info # 操作系统运行FastDFS的用户组,不填默认就是启动FastDFS的用户组 run_by_group= # 操作系统运行FastDFS的用户,不填默认就是启动FastDFS的用户 run_by_user = # 可以连接到此Tracker Server的IP范围 # 例子: # allow_hosts=10.0.1.[1-15,20] # allow_hosts=host[01-08,20-25].domain.com # allow_hosts=192.168.5.64/26 allow_hosts = * # 同步日志缓存到硬盘的时间 # 默认十秒一次(Tracker的日志默认是先写在内存里的) sync_log_buff_interval = 10 # 检查storage服务器存活状态的间隔时间 check_active_interval = 120 # 线程栈大小 需要大于64KB # 默认值256KB # 线程栈越大 work_thread能启动的就越少 thread_stack_size = 256KB # 当storage server IP地址改变时,集群是否自动调整。注:只有在storage server进程重启时才完成自动调整。 # 默认为true storage_ip_changed_auto_adjust = true # storage 同步文件最大延迟时间 # 默认为一天 86400秒 # 注:本参数并不影响文件同步过程。本参数仅在下载文件时,判断文件是否已经被同步完成的一个阈值(经验值) storage_sync_file_max_delay = 86400 # 存储服务器同步一个文件需要消耗的最大时间,缺省为300s,即5分钟。 # 注:本参数并不影响文件同步过程。本参数仅在下载文件时,作为判断当前文件是否被同步完成的一个阈值(经验值) storage_sync_file_max_time = 300 # 是否使用小文件合并,默认为false # trunk_file可以理解为一个容器,专门用来存放小文件的 # 含有很多个slot(槽),每个槽存放一个小文件 use_trunk_file = false # trunk file给小文件分配的最小字节数。比如文件只有16个字节,系统也会分配slot_min_size个字节。 slot_min_size = 256 # 只有文件大小<=这个参数值的文件,才会合并存储。如果一个文件的大小大于这个参数值,将直接保存到一个文件中(即不采用合并存储方式)。 slot_max_size = 1MB # 合并存储的trunk file大小,至少4MB,缺省值是64MB。不建议设置得过大。 trunk_alloc_alignment_size = 256 # 合并后trunk file的连续可用空间(槽)是否合并,默认为true(防止产生碎片) trunk_free_space_merge = true # 删除没有使用的trunk_files delete_unused_trunk_files = false # trunk file的最大大小,必须要大于4MB trunk_file_size = 64MB # 是否提前创建trunk file 默认为false trunk_create_file_advance = false # 创建trunk file的起始(第一次)时间点 默认为2:00 trunk_create_file_time_base = 02:00 # 创建trunk file的时间间隔 默认为1天 86400秒 trunk_create_file_interval = 86400 # 提前创建trunk_file时,需要达到的空闲trunk大小 trunk_create_file_space_threshold = 20G # trunk初始化时,是否检查可用空间是否被占用 trunk_init_check_occupying = false # 是否无条件从trunk binlog中加载trunk可用空间信息 # FastDFS缺省是从快照文件storage_trunk.dat中加载trunk可用空间, # 该文件的第一行记录的是trunk binlog的offset,然后从binlog的offset开始加载 trunk_init_reload_from_binlog = false # 压缩trunk binlog日志的最小时间间隔,0代表从不压缩 trunk_compress_binlog_min_interval = 86400 # 压缩trunk binlog日志的时间间隔 0代表从不压缩 trunk_compress_binlog_interval = 86400 # 首次压缩trunk binlog的时间 trunk_compress_binlog_time_base = 03:00 # trunk binlog的最大备份 0代表无备份 trunk_binlog_max_backups = 7 # 是否使用storage_id作为区别storage的标识 use_storage_id = false # use_storage_id 设置为true,才需要设置本参数 # 在文件中设置组名、server ID和对应的IP地址,参见源码目录下的配置示例:conf/storage_ids.conf storage_ids_filename = storage_ids.conf # 文件名中存储服务器的id类型,值为: # ip:存储服务器的ip地址 # id:存储服务器的服务器id # 此参数仅在use_storage_id设置为true时有效 # 默认值为id id_type_in_filename = id # 是否使用符号链接的方式 # 如果设置为true 一个文件将占用两个文件 符号链接指向原始文件 store_slave_file_use_link = false # 是否定期分割error.log true只支持一天一次 rotate_error_log = false # error.log分割时间 error_log_rotate_time = 00:00 # 是否压缩旧error_log日志 compress_old_error_log = false # 压缩几天前的errorlog,默认7天前 compress_error_log_days_before = 7 # error log按大小分割 # 设置为0表示不按文件大小分割,否则当error log达到该大小,就会分割到新文件中 rotate_error_log_size = 0 # 日志存放时间 # 0表示从不删除旧日志 log_file_keep_days = 0 # 是否使用连接池 use_connection_pool = true # 连接池最大空闲时间 connection_pool_max_idle_time = 3600 # 本tracker服务器的默认HTTP端口 http.server_port = 8080 # 间隔几秒检查storage http服务器的存活 # <=0代表从不检查 http.check_alive_interval = 30 # 检查storage的存活状态的类型 # tcp:连接上就可以,但不发送请求也不获取响应 # http storage必须返回200 http.check_alive_type = tcp # 检查storage状态的uri http.check_alive_uri = /status.html