1. 分布式文件系统应用场景
互联网海量非结构化数据的存储需求
电商网站:海量商品图片
视频网站:海量视频文件
网盘 : 海量文件
社交网站:海量图片
2.FastDFS 介绍
https://github.com/happyfish100/fastdfs
2.1 简介
FastDFS 是一个开源的高性能分布式文件系统(DFS)。 它的主要功能包括:文件存储,文件同步和文件访问,以及高容量和负载平衡。主要解决了海量数据存储问题,特别适合以中小文件(建议范围:4KB < file_size <500MB)为载体的在线服务。
FastDFS 设计是用来存储小文件的,过大的文件处理方案是拆分为小文件,可跟踪小文件的上传情况。 如果应用场景都是处理大文件,可能选择其他分布式文件系统方案会更合适。
2.2 特性
FastDFS 为互联网量身定制,充分考虑了冗余备份、负载均衡、线性扩容等机制,并注重高可用、高性能等指标,使用 FastDFS 很容易搭建一套高性能的文件服务器集群提供文件上传、下载等服务。
优点:
文件不分块存储,文件和系统中的文件一一对应。
对文件内容做 hash 处理,避免出现重复文件,节约磁盘空间。
下载文件支持 HTTP 协议,可基于内置 Web Server 或外部 Web Server。
支持在线扩容,动态添加卷。
支持文件冗余备份和负载均衡。
存储服务器上可以保存文件属性(meta-data)
V2.0 网络通信采用 libevent,支持大并发访问,整体性能更好。
缺点:
直接按文件存储,可直接查看文件内容,缺乏文件安全性。
数据同步无校验,存在静默 IO 问题,降低系统可用性。
单线程数据同步,仅适合存储小文件(1)。
备份数根据存储分卷(分组)决定,缺乏文件备份数设置灵活性。
单个挂载点异常会导致整个存储节点下线。
缺乏多机房容灾支持。
静态的负载均衡机制。
优点与缺点并存,但针对中小型系统已经完全足够使用了。
2.3 角色
Tracker Server:跟踪服务器,主要做调度工作,起到均衡的作用;负责管理所有的 storage server 和 group,每个 storage 在启动后会连接 Tracker,告知自己所属 group 等信息,并保持周期性心跳。
Storage Server:存储服务器,主要提供容量和备份服务;以 group 为单位,每个 group 内可以有多台 storage server,数据互为备份。
Client:客户端,上传下载数据的服务器,也就是我们自己的项目所部署在的服务器。
Tracker 相当于一个调度中心,上传和下载都通过它来进行分配指定。
Storage cluster 部分,由 Volume1、Volume2……VolumeK 组成,它们称为卷(或者叫做组),卷与卷之间是平行的关系,可以根据资源的使用情况随时增加,卷内服务器文件相互同步备份,以达到容灾的目的。
2.4 存储策略
为了支持大容量,存储节点(服务器)采用了分卷(或分组)的组织方式。存储系统由一个或多个卷组成,卷与卷之间的文件是相互独立的,所有卷的文件容量累加就是整个存储系统中的文件容量。一个卷可以由一台或多台存储服务器组成,一个卷下的存储服务器中的文件都是相同的,卷中的多台存储服务器起到了冗余备份和负载均衡的作用。
在卷中增加服务器时,同步已有的文件由系统自动完成,同步完成后,系统自动将新增服务器切换到线上提供服务。当存储空间不足或即将耗尽时,可以动态添加卷。只需要增加一台或多台服务器,并将它们配置为一个新的卷,这样就扩大了存储系统的容量。
2.5 上传过程
当服务启动之后,Storage Server 会定期的向 Tracker Server 发送存储信息。如果 Tracker Server 是集群形式,则每个 Tracker 之间的关系是对等的,客户端上传时选择任意一个 Tracker 即可。
整体流程:当客户端请求 Tracker 进行上传操作时,会获取存储服务器相关信息,主要包括 IP 和端口。根据返回信息上传文件,通过存储服务器写入磁盘,并返回给客户端 file_id、路径信息、文件名等信息。
对应流程图如下:
其中,当 Tracker 收到客户端上传文件的请求时,会为该文件分配一个可以存储文件的 group,当选定了 group 后就要决定给客户端分配 group 中的哪一个 storage server。
当分配好 storage server 后,客户端向 storage 发送写文件请求,storage 将会为文件分配一个数据存储目录。然后为文件分配一个 fileid,最后根据以上的信息生成文件名存储文件。
生成的文件名基本格式如下:
组名:文件上传后所在的 storage
组名称,在文件上传成功后有 storage
服务器返回, 需要客户端自行保存。
虚拟磁盘路径:storage
配置的虚拟路径,与磁盘选项 store_path*
对应。如果配置了 store_path0
则是 M00
,如果配置了 store_path1
则是 M01
,以此类推。
数据两级目录:storage
服务器在每个虚拟磁盘路径下创建的两级目录,用于存储数据 文件。
文件名:与文件上传时不同。是由存储服务器根据特定信息生成,文件名包含:源存储 服务器 IP
地址、文件创建时间戳、文件大小、随机数和文件拓展名等信息。
2.6 文件同步
写文件时,客户端将文件写至 group 内一个 storage server 即认为写文件成功,storage server 写完文件后,会由后台线程将文件同步至同 group 内其他的 storage server。
每个 storage 写文件后,同时会写一份 binlog,binlog 里不包含文件数据,只包含文件名等元信息,这份 binlog 用于后台同步,storage 会记录向 group 内其他 storage 同步的进度,以便重启后能接上次的进度继续同步;进度以时间戳的方式进行记录,所以最好能保证集群内所有 server 的时钟保持同步。
storage 的同步进度会作为元数据的一部分汇报到 tracker 上,tracker 在选择读 storage 的时候会以同步进度作为参考。
2.7 下载过程
跟上传一样,在下载时客户端可以选择任意 Tracker server。
客户端带文件名信息请求 Tracker,Tracker 从文件名中解析出文件的 group、大小、创建时间等信息,然后选择一个 storage 用来服务处理请求,返回对应文件。
对应流程图如下:
如果是基于 Web 的 http 请求,此处的 Client 可以是 Nginx 代理服务。下面这张图更加形象的描述了相关的流程。
3.FastDFS 环境搭建
环境搭建参考:FastDFS环境搭建
4. Java 应用整合 FastDFS
fastdfs-client-java 使用
https://github.com/happyfish100/fastdfs-client-java
引入依赖
<dependency>
<groupId>com.taoyuanx</groupId>
<artifactId>fastdfs-client-java</artifactId>
<version>1.29-SNAPSHOT</version>
</dependency>
复制代码
上传文件
public class UploadDemo {
public static void main(String[] args) throws IOException, MyException {
//1、加载配置文件
ClientGlobal.init("fdfs_client.conf");
//2、创建一个TrackerClient对象。
TrackerClient trackerClient = new TrackerClient();
//3、使用TrackerClient对象获得trackerserver对象。
TrackerServer trackerServer = trackerClient.getTrackerServer();
//4、创建一个StorageClient对象。trackerserver、StorageServer两个参数。
StorageClient storageClient = new StorageClient(trackerServer, null);
String path = System.getProperty("user.dir")+ File.separator+"1.jpg";
//5、使用StorageClient对象上传文件。
String[] strings = storageClient.upload_file(path, "jpg", null);
//返回的数组包含两个元素分别类似于:
//group1
//M00/00/00/wKgDCmDtis2AAsj9AAW3ED-O1WQ151.jpg
for (String string : strings) {
System.out.println(string);
}
}
}
复制代码
下载文件
public class DownloadDemo {
public static void main(String[] args) {
try {
//1、加载配置文件
ClientGlobal.init("fdfs_client.conf");
//2、创建一个TrackerClient对象。
TrackerClient trackerClient = new TrackerClient();
//3、使用TrackerClient对象获得trackerserver对象。
TrackerServer trackerServer = trackerClient.getTrackerServer();
//4、创建一个StorageClient对象。trackerserver、StorageServer两个参数。
StorageClient storageClient = new StorageClient(trackerServer, null);
String path = System.getProperty("user.dir")+ File.separator+"fox.jpg";
//5、使用StorageClient对象下载文件。
storageClient.download_file("group1", "M00/00/00/wKgDDWDtRu6AMPhBARBlpcz7xUs146.jpg",path);
} catch (IOException e) {
e.printStackTrace();
} catch (MyException e) {
e.printStackTrace();
}
}
}
复制代码
fastDFS_Client 使用
在原作者 YuQing 与 yuqih 发布的 java 客户端基础上进行了大量重构工作,便于 Java 工作者学习与阅读。
主要特性
对关键部分代码加入了单元测试,便于理解与服务端的接口交易,提高接口质量
将以前对 byte 硬解析风格重构为使用 对象+注解 的形式,尽量增强了代码的可读性
支持对服务端的连接池管理(commons-pool2)
支持上传图片时候检查图片格式,并且自动生成缩略图
在 SpringBoot 当中自动导入依赖
https://github.com/tobato/FastDFS_Client
上传图片
引入依赖
<dependency>
<groupId>com.github.tobato</groupId>
<artifactId>fastdfs-client</artifactId>
<version>1.27.2</version>
</dependency>
复制代码
在 yml 中配置
# 分布式文件系统FDFS配置
fdfs:
so-timeout: 1500 # 读取时间
connect-timeout: 600 # 连接超时时间
thumb-image: #缩略图生成参数
width: 150
height: 150
tracker-list: #Tracker服务配置地址列表
- 192.168.3.10:22122
- 192.168.3.12:22122
- 192.168.3.13:22122
pool:
#从池中借出的对象的最大数目(配置为-1表示不限制)
max-total: -1
#获取连接时的最大等待毫秒数(默认配置为5秒)
max-wait-millis: 5000
#每个key最大连接数
max-total-per-key: 50
#每个key对应的连接池最大空闲连接数
max-idle-per-key: 10
#每个key对应的连接池最小空闲连接数
min-idle-per-key: 5
upload:
base-url: http://fastdfs.com:8888/
复制代码
大文件上传出现异常:
解决方案:添加 Config 配置
@Configuration
public class FileConfig {
/**
* 解决上传文件过大导致异常的问题。
* the request was rejected because its size (170982031) exceeds the configured
* @return
*/
@Bean
public MultipartConfigElement multipartConfigElement() {
MultipartConfigFactory factory = new MultipartConfigFactory();
factory.setMaxRequestSize(DataSize.ofBytes(200*1048576L));
factory.setMaxFileSize(DataSize.ofBytes(200*1048576L));
return factory.createMultipartConfig();
}
}
复制代码
Controller
@RestController
public class UploadController {
@Autowired
private UploadService uploadService;
@RequestMapping("/upload")
@ResponseBody
public Map<String,Object> upload(MultipartFile file){
Map<String, Object> map =new HashMap<>();
String path = this.uploadService.uploadImage(file);
map.put("path",path);
return map;
}
}
复制代码
Service
@Service
@Slf4j
public class UploadService {
@Autowired
private FastFileStorageClient fastFileStorageClient;
@Value("${upload.base-url}")
private String baseUrl;
/**
* 上传图片
* @param file
* @return
*/
public String uploadImage(MultipartFile file) {
try {
//校验文件
BufferedImage image = ImageIO.read(file.getInputStream());
if (image == null || image.getWidth() == 0 || image.getHeight() == 0) {
throw new RuntimeException("上传文件不是图片");
}
} catch (IOException e) {
log.error("校验文件内容失败....{}", e);
throw new RuntimeException("校验文件内容失败" + e.getMessage());
}
try {
// 获取扩展名
String extension = StringUtils.substringAfterLast(file.getOriginalFilename(), ".");
// 上传到FastDFS
StorePath storePath = fastFileStorageClient.uploadFile(file.getInputStream(), file.getSize(), extension, null);
// 返回路径
return baseUrl+storePath.getFullPath();
} catch (IOException e) {
log.error("【文件上传】上传文件失败!....{}", e);
throw new RuntimeException("【文件上传】上传文件失败!" + e.getMessage());
}
}
}
复制代码
测试: http://localhost:8080/index.html
上传略缩图
//上传略缩图
StorePath storePath = fastFileStorageClient.uploadImageAndCrtThumbImage(file.getInputStream(), file.getSize(), extension, null);
复制代码
缩略图为上传文件名+缩略图后缀(默认_150x150)
如:源图上传后路径为 xxx.jpg,缩略图为 xxx_150x150.jpg
测试:
http://fastdfs.com:8888/group1/M00/00/00/wKgDDGDub22AO4HUAApS0I7eS2U644.jpg
http://fastdfs.com:8888/group1/M00/00/00/wKgDDGDub22AO4HUAApS0I7eS2U644_150x150.jpg
文件下载
/**
* 测试文件下载
*/
@Test
public void download() {
try {
byte[] bytes = fastFileStorageClient.downloadFile("group1", "M00/00/00/wKgDDGDub22AO4HUAApS0I7eS2U644_150x150.jpg", new DownloadByteArray());
FileOutputStream stream = new FileOutputStream("3_150x150.jpg");
stream.write(bytes);
} catch (IOException e) {
e.printStackTrace();
}
}
复制代码
测试:
文件删除
/**
* 测试文件删除
*/
@Test
public void deleteFile(){
fastFileStorageClient.deleteFile("group1","M00/00/00/wKgDDGDub22AWGB_AApS0I7eS2U026.jpg");
}
复制代码
评论