关于对分布式文件系统FastDFS的原理和结合Spring Boot使用,最新版本tobato修正总结
关于FastDFS的搭建,这里不过多的介绍,网络上的文章也很多,或者直接用docker拉取一个FastDfS,满足测试开发也是没问题的。
本文主要介绍FastDFS的原理,结合Spring Boot使用FastDFS。
本文参考原理中部分参考 https://www.cnblogs.com/zhangs1986/p/8268927.html ,这篇文章非常全面的讲了FastDFS的配置,想了解搭建和配置的,可以去这里看看
导言
在生产中我们一般希望文件系统能帮我们解决以下问题,如:
- 超大数据存储
- 数据高可用(冗余备份)
- 读/写高性能
- 海量数据计算。
- 最好还得支持多平台多语言,支持高并发。
由于单台服务器无法满足以上要求,这就迫使开发者不得不考虑使用其他方式解决此类问题。分布式文件系统就在这样迫切的需求下孕育而生。
什么是FastDFS?
传统上传文件时的缺陷:
上传本身没有任何问题,问题出在保存文件的方式,我们是保存在服务器机器,就会有下面的问题:
- 单机器存储,存储能力有限
- 无法进行水平扩展,因为多台机器的文件无法共享,会出现访问不到的情况
- 数据没有备份,有单点故障风险
- 并发能力差
这个时候,最好使用分布式文件存储来代替本地文件存储。
FastDFS分布式文件系统
FastDFS是一个开源的轻量级分布式文件系统。它解决了大数据量存储和负载均衡等问题。特别适合以中小文件(建议范围:4KB < file_size <500MB)为载体的在线服务,如相册网站、视频网站等等。在UC基于FastDFS开发向用户提供了:网盘,社区,广告和应用下载等业务的存储服务。
FastDFS是由淘宝的余庆先生所开发的一个轻量级、高性能的开源分布式文件系统。用纯C语言开发,功能丰富:
- 文件存储
- 文件同步
- 文件访问(上传、下载)
- 存取负载均衡
- 在线扩容
适合有大容量存储需求的应用或系统。同类的分布式文件系统有谷歌的GFS、HDFS(Hadoop)、TFS(淘宝)等。
FastDFS架构
FastDFS服务端有二个主要角色:跟踪服务器(tracker server)、存储服务器(storage server)。
- Tracker Server:跟踪服务器,主要负责调度storage节点与client通信,在访问上起负载均衡的作用,和记录storage节点的运行状态,是连接client和storage节点的枢纽。
- Storage Server:存储服务器,保存文件和文件的meta data(元数据),每个storage server会启动一个单独的线程主动向Tracker cluster中每个tracker server报告其状态信息,包括磁盘使用情况,文件同步情况及文件上传下载次数统计等信息
- Group:文件组,多台Storage Server的集群。上传一个文件到同组内的一台机器上后,FastDFS会将该文件即时同步到同组内的其它所有机器上,起到备份的作用。不同组的服务器,保存的数据不同,而且相互独立,不进行通信。
- Tracker Cluster:跟踪服务器的集群,有一组Tracker Server(跟踪服务器)组成。
- Storage Cluster :存储集群,有多个Group组成。
如下图所示:
ps:这样的架构具有以下特点:1.轻量级(相比GFS简化了master角色,不再管理meta数据信息)。2.对等结构。3.分组方式。
FastDFS协议
FastDFS角色间是基于TCP/IP协议进行通信,协议包格式为:header + body。具体结构如图:
上传机制
- Client通过Tracker server查找可用的Storage server。
- Tracker server向Client返回一台可用的Storage server的IP地址和端口号。
- Client直接通过Tracker server返回的IP地址和端口与其中一台Storage server建立连接并进行文件上传。
- 上传完成,Storage server返回Client一个文件ID,文件上传结束。
同步时间管理
当一个文件上传成功后,客户端马上发起对该文件下载请求(或删除请求)时,tracker是如何选定一个适用的存储服务器呢?
其实每个存储服务器都需要定时将自身的信息上报给tracker,这些信息就包括了本地同步时间(即,同步到的最新文件的时间戳)。而tracker根据各个存储服务器的上报情况,就能够知道刚刚上传的文件,在该存储组中是否已完成了同步。同步信息上报如下图:
下载机制
- Client通过Tracker server查找要下载文件所在的的Storage server。
- Tracker server向Client返回包含指定文件的某个Storage server的IP地址和端口号。
- Client直接通过Tracker server返回的IP地址和端口与其中一台Storage server建立连接并指定要下载文件。
- 下载文件成功。
精巧的FID
说到下载就不得不提文件索引(又称:FID)的精巧设计了。文件索引结构如下图,是客户端上传文件后存储服务器返回给客户端,用于以后访问该文件的索引信息。文件索引信息包括:组名,虚拟磁盘路径,数据两级目录,文件名。
ps:
-
组名:文件上传后所在的存储组名称,在文件上传成功后有存储服务器返回,需要客户端自行保存。一个组下可以有多个storage,我感觉组就是为管理storage的
-
虚拟磁盘路径:存储服务器配置的虚拟路径,与磁盘选项store_path*对应。
-
数据两级目录:存储服务器在每个虚拟磁盘路径下创建的两级目录,用于存储数据文件。
-
文件名:与文件上传时不同。是由存储服务器根据特定信息生成,文件名包含:源存储服务器IP地址、文件创建时间戳、文件大小、随机数和文件拓展名等信息。
快速定位文件
知道FastDFS FID的组成后,我们来看看FastDFS是如何通过这个精巧的FID定位到需要访问的文件。
-
通过组名tracker能够很快的定位到客户端需要访问的存储服务器组,并将选择合适的存储服务器提供客户端访问;
-
存储服务器根据“文件存储虚拟磁盘路径”和“数据文件两级目录”可以很快定位到文件所在目录,并根据文件名找到客户端需要访问的文件。
FastDFS的端口
FastDFS分为tracker(默认端口为22122)和storage(默认端口为23000)服务,tracker负责前端的负载及导航功能,storage仅负责存贮数据。
FastDFS小结
本次分享的主要内容包含:FastDFS各角色的任务分工/协作,文件索引的原理设计以及文件上传/下载操作的流程。通过此次学习我们对FastDFS有了初步的了解,如:
-
FastDFS只有三个角色;且跟踪服务器和存储服务器均不存在单点。
-
跟踪服务器被动的接收存储服务器汇报,对存储服务器进行分组管理;并为客户端选定适用的存储服务器。同一存储服务器可以同时向多台跟踪服务器汇报状态信息。
-
存储服务器组内所有存储服务器是对等关系,存储的数据一一对应且相同;所有的存储服务器均是同时在线服务,极大的提高的服务器的使用率,分担了数据访问压力。
Spring Boot整合FastDFS
余庆先生提供了一个Java客户端,但是作为一个C程序员,写的java代码可想而知。而且已经很久不维护了。
这里推荐一个开源的FastDFS客户端,支持最新的SpringBoot2.0。
配置使用极为简单,支持连接池,支持自动生成缩略图。
地址:地址:tobato/FastDFS_client
FastDFS-Client使用方式
1.在项目Pom当中加入依赖
Maven依赖为
<dependency>
<groupId>com.github.tobato</groupId>
<artifactId>fastdfs-client</artifactId>
<version>1.26.6</version>
</dependency>
2.将Fdfs配置引入项目
在Maven当中配置依赖以后,SpringBoot项目将会自动导入FastDFS依赖(感谢@Lzgabel)。
FastDFS-Client 1.26.4版本以前引入方式
将FastDFS-Client客户端引入本地化项目的方式非常简单,在SpringBoot项目/src/[com.xxx.主目录]/conf
当中配置
/**
* 导入FastDFS-Client组件
*
* @author tobato
*
*/
@Configuration
@Import(FdfsClientConfig.class)
// 解决jmx重复注册bean的问题
@EnableMBeanExport(registration = RegistrationPolicy.IGNORE_EXISTING)
public class ComponetImport {
// 导入依赖组件
}
对的,只需要一行注解 @Import(FdfsClientConfig.class)就可以拥有带有连接池的FastDFS Java客户端了。
注意:
@EnableMBeanExport
解决问题JMX重复注册问题,issue #8 issue #18,不要再配置spring.jmx.enabled=false
,以免影响SpringBoot默认的JMX监控。
3.在application.yml当中配置Fdfs相关参数
# ===================================================================
# 分布式文件系统FDFS配置
# ===================================================================
fdfs:
so-timeout: 1501
connect-timeout: 601
thumb-image: #缩略图生成参数
width: 150
height: 150
tracker-list: #TrackerList参数,支持多个
- 192.168.1.105:22122
- 192.168.1.106:22122
如果有必要可以参考 apache.pool2 参数配置连接池属性,默认配置为
fdfs:
..其他配置信息..
pool:
#从池中借出的对象的最大数目(配置为-1表示不限制)
max-total: -1
#获取连接时的最大等待毫秒数(默认配置为5秒)
max-wait-millis: 5*1000
#每个key最大连接数
max-total-per-key: 50
#每个key对应的连接池最大空闲连接数
max-idle-per-key: 10
#每个key对应的连接池最小空闲连接数
max_idle_per_key: 5
注意: key配置的是连接服务端的地址(IP+端口)连接情况,如果有连接不够用的情况可以调整以上参数
4.使用接口服务对Fdfs服务端进行操作
主要接口包括
- TrackerClient - TrackerServer接口
- GenerateStorageClient - 一般文件存储接口 (StorageServer接口)
- FastFileStorageClient - 为方便项目开发集成的简单接口(StorageServer接口)
- AppendFileStorageClient - 支持文件续传操作的接口 (StorageServer接口)
常见问题
1.如何在没有spring-boot的情况下使用
参考下面文章进行改造
https://blog.csdn.net/wzl19870309/article/details/74049204
2.高并发下测试出现上传的文件和得到的返回路径的文件不是同一个
通过加大超时时间后解决
soTimeout: 1500
connectTimeout: 600
3.新手不会用
阅读单元测试,从学习test/java/com/github/tobato/fastdfs/service下的单元测试入手
上面是github的md文件里的介绍,配置方面已经说清楚了,具体的详细的使用可以直接参考tobato仓库的单元测试文章。
实际项目的应用举例
其实想要学习的话,最好的是参照github的仓库的test案例。但是这里给出实际的用法举例,方便大家快速入门和回忆
application.yml文件的配置:
fdfs:
so-timeout: 1501
connect-timeout: 601
thumb-image:
width: 150
height: 150
tracker-list: # tracker地址
- 192.168.1.110:22122
# 这个是设置web访问请求的地址
web-server-url: http://192.168.1.110/
我这里完成一个为用户上传头像的demo,后台用Spring Boot + SpringMVC
首先我这里用一个Controller来接收前台传来的头像文件和对应的用户id:
/** * 上传文件 * @param file * @param userid * @return */
@RequestMapping("/upload")
public ResponseEntity<User> upload(MultipartFile file, String userid) {
User user = userService.upload(file, userid);
return ResponseEntity.ok(user);
}
这是UserService的接口类中声明的方法:
/** * 上传头像 * @param file 客户端上传的文件 * @param userid 用户id * @return 如果上传成功,返回用户信息.否则返回null */
User upload(MultipartFile file, String userid);
这是UserService的实现类UserServiceImpl:
@Autowired
private FastFileStorageClient storageClient;
@Override
public User upload(MultipartFile file, String userid) {
try {
// 返回在FastDFS中的URL路径,这个路径不带http://***/.. 其中元数据可以为null
StorePath storePath = storageClient.uploadImageAndCrtThumbImage(file.getInputStream(), file.getSize(),
"png", null);
String url = file.getFullPath();
// 在FastDFS上传的时候,会生成一个缩略图
// 文件名_150x150.后缀
String[] fileNameList = url.split("\\.");
String fileName = fileNameList[0];
String ext = fileNameList[1];
String picSmallUrl = fileName + "_150x150." + ext;
// 根据用户id获取tbUser
TbUser tbUser = userMapper.selectByPrimaryKey(userid);
// 设置头像大图片
// 设置头像小图片
tbUser.setPicNormal(url);
tbUser.setPicSmall(picSmallUrl);
// 将新的头像URL更新到数据库中
userMapper.updateByPrimaryKey(tbUser);
// 为图片加上真实域名
setTruePicurl(tbUser);
// 将用户信息返回到Controller
User user = new User();
// 将文件转换为tbUser转换为user返回前台
BeanUtils.copyProperties(tbUser, user);
return user;
} catch (IOException e) {
throw new BCException(ExceptionEnum.FALSE_UPLOAD_IMAGE);
}
}
特别注意的是,在application.yml中,设置了缩略图,生成的地址就是在文件名字后面加上_150x150。
翻看这个框架的新版源码的时候(老版是没有的),发现了FastImageFile 这个类,可以更方便的获取缩略图路径。等于把原来的文件又封装了一下,有兴趣的同学可以去看看。
我下载了最新的版本1.26.6,打开了它的源码
上面调用了StringBuilder的insert方法
最终调用的方法是
这个方法是插入字符串到指定偏移量的位置。“.”的位置其实就是在文件名的后面,那就是插入到文件名的后面啦,就是加一个后缀名而已。
可以发现,这只是对老版的获取缩略图的方法的一个简单的封装,加上了前缀而已。
新版还修正了一个方法的名字
现在改正为
目前只发现这么多,剩下的大家可以自行的探究。
感谢阅读,共同进步。