分布式文件存储-FastDF

FastDFS简介

FastDFS是一个开源的轻量级分布式文件系统,它对文件进行管理,功能包括:文件存储、文件同步、文件访问(文件上传、文件下载)等,解决了大容量存储和负载均衡的 问题。特别适合以文件为载体的在线服务,如相册网站、视频网站等等。
FastDFS为互联网量身定制,充分考虑了冗余备份、负载均衡、线性扩容等机制,并注重 高可用、高性能等指标,使用FastDFS很容易搭建一套高性能的文件服务器集群提供文件 上传、下载等服务。

分布式文件系统

FastDFS 架构包括 Tracker server 和 Storage server。客户端请求 Tracker server 进行 文件上传、下载,通过Tracker server 调度终由 Storage server 完成文件上传和下 载。
Tracker server 作用是负载均衡和调度,通过 Tracker server 在文件上传时可以根据一 些策略找到Storage server 提供文件上传服务。可以将 tracker 称为追踪服务器或调度服 务器。Storage server 作用是文件存储,客户端上传的文件终存储在 Storage 服务器 上,Storageserver 没有实现自己的文件系统而是利用操作系统的文件系统来管理文件。 可以将storage称为存储服务器。

系统结构图

系统结构

工作流程详解

文件上传:Client会先向Tracker询问存储地址,Tracker查询到存储地址后返回给Client,Client拿着地址直接和对应的Storage通讯,将文件上传至改Storage。

客户端上传文件后存储服务器将文件 ID 返回给客户端,此文件 ID 用于以后访问该文件 的索引信息。文件索引信息包括:组名,虚拟磁盘路径,数据两级目录,文件名。

组名:文件上传后所在的 storage 组名称,在文件上传成功后有storage 服务器返回,需 要客户端自行保存。
虚拟磁盘路径:storage 配置的虚拟路径,与磁盘选项store_path*对应。如果配置了
*store_path0 则是 M00,如果配置了 store_path1 则是 M01,以此类推。
数据两级目录:storage 服务器在每个虚拟磁盘路径下创建的两级目录,用于存储数据
文件。
文件名:与文件上传时不同。是由存储服务器根据特定信息生成,文件名包含:源存储
服务器 IP 地址、文件创建时间戳、文件大小、随机数和文件拓展名等信息。
*

文件上传

文件下载:同样,Client会向Tracker询问地址,并带上要查询的文件名和组名,Tracker查询后会将地址返回给Client,Client拿着地址和指定Storage通讯并下载文件。

文件下载

使用Docker搭建(主要讲解使用,所以直接Docker搭建了)

1. 拉取镜像并启动

docker run -d –restart=always –privileged=true –net=host –name=fastdfs -e IP=192.168.149.128 -e WEB_PORT=80 -v ${HOME}/fastdfs:/var/local/fdfs registry.cn-beijing.aliyuncs.com/tianzuo/fastdfs

其中-v ${HOME}/fastdfs:/var/local/fdfs是指:将${HOME}/fastdfs这个目录挂载到容器里的/var/local/fdfs这个目录里。所以上传的文件将被持久化到${HOME}/fastdfs/storage/data里,IP 后面是自己的服务器公网ip或者虚拟机ip,-e WEB_PORT=80 指定nginx端口

2. 测试上传

//进入容器
docker exec -it fastdfs /bin/bash
//创建文件
echo “Hello FastDFS!”>index.html
//测试文件上传
fdfs_test /etc/fdfs/client.conf upload index.html

使用Java代码 测试上传

创建项目略过

修改pom.xml

1
2
3
4
5
6
7
8
9
10
11
<dependency>
<groupId>net.oschina.zcx7878</groupId>
<artifactId>fastdfs-client-java</artifactId>
<version>1.27.0.0</version>
</dependency>
<!-- spring-core -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>4.3.25.RELEASE</version>
</dependency>

创建fdfs_client.conf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
connect_timeout=30

network_timeout=60

base_path=/home/fastdfs

#改为自己服务器的ip
tracker_server=39.97.251.40:22122

log_level=info

use_connection_pool = false

connection_pool_max_idle_time = 3600

load_fdfs_parameters_from_tracker=false

use_storage_id = false

storage_ids_filename = storage_ids.conf

http.tracker_server_port=80

创建测试类进行文件上传

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
package com.zx;

import org.csource.common.MyException;
import org.csource.fastdfs.*;
import org.springframework.core.io.ClassPathResource;

import java.io.IOException;

/**
* Hello world!
*
*/
public class App
{
public static void main( String[] args ) throws IOException, MyException {
String uploadFilePath="C:/Users/LJH/Pictures/Camera Roll/timg.gif";

String filePath = new ClassPathResource("fdfs_client.conf").getFile().getAbsolutePath();
System.out.println(filePath);
// 1、加载配置文件,配置文件中的内容就是 tracker 服务的地址。
ClientGlobal.init(filePath);
// 2、创建一个 TrackerClient 对象。直接 new 一个。
TrackerClient trackerClient = new TrackerClient();
// 3、使用 TrackerClient 对象创建连接,获得一个 TrackerServer 对象。
TrackerServer trackerServer = trackerClient.getConnection();
// 4、创建一个 StorageServer 的引用,值为 null
StorageServer storageServer = null;
// 5、创建一个 StorageClient 对象,需要两个参数 TrackerServer 对象、StorageServer 的引用
StorageClient storageClient = new StorageClient(trackerServer, storageServer);
// 6、使用 StorageClient 对象上传图片。
//扩展名不带“.”
String[] strings = storageClient.upload_file(uploadFilePath, "gif",
null);
// 7、返回数组。包含组名和图片的路径。
for (String string : strings) {
System.out.println(string);
}
System.out.println("上传完成");
}
}

测试

上传

创建测试类进行文件下载

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/***
* 文件下载
* @param groupName 组名
* @param remoteFileName 文件存储完整名
* @return
*/
public static InputStream downFile(String groupName, String remoteFileName) {
try {
String filePath = new ClassPathResource("fdfs_client.conf").getFile().getAbsolutePath();
System.out.println(filePath);
// 1、加载配置文件,配置文件中的内容就是 tracker 服务的地址。
ClientGlobal.init(filePath);
// 2、创建一个 TrackerClient 对象。直接 new 一个。
TrackerClient trackerClient = new TrackerClient();
// 3、使用 TrackerClient 对象创建连接,获得一个 TrackerServer 对象。
TrackerServer trackerServer = trackerClient.getConnection();
//创建StorageClient
StorageClient storageClient = new StorageClient(trackerServer, null);

//下载文件
byte[] fileByte = storageClient.download_file(groupName, remoteFileName);
InputStream ins = new ByteArrayInputStream(fileByte);
return ins;
} catch (Exception e) {
logger.error("Exception: Get File from Fast DFS failed", e);
}
return null;
}

使用springboot完成文件上传1 (导入的包和2不同)

修改pom.xml

1
2
3
4
5
6
7
fastdfs:
connect_timeout_in_seconds: 120
network_timeout_in_seconds: 120
charset: UTF-8
tracker_servers: 39.97.251.40:22122
#多个 trackerServer中间以逗号分隔

创建UploadService

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
package com.sxt.utils;

import org.apache.commons.lang3.StringUtils;
import org.csource.fastdfs.*;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

import java.util.HashMap;
import java.util.Map;

/**
* @program: fastdfs-demo
* @author: zx
* @create: 2020-01-03 10:05
**/
@Component
public class UploadService {

@Value("${fastdfs.tracker_servers}")
private String tracker_servers;

@Value("${fastdfs.connect_timeout_in_seconds}")
private int connect_timeout;

@Value("${fastdfs.network_timeout_in_seconds}")
private int network_timeout;

@Value("${fastdfs.charset}")
private String charset;

public Map<String,Object> upload(MultipartFile multipartFile) {
if (multipartFile == null) {
throw new RuntimeException("文件不能为空");
}
// 上传至fastDFS, 返回文件id
String fileId = this.fdfsUpload(multipartFile);
if (StringUtils.isEmpty(fileId)) {
System.out.println("上传失败");
throw new RuntimeException("上传失败");
}
Map<String, Object> map=new HashMap<>();
map.put("code",200);
map.put("msg","上传成功");
map.put("fileId",fileId);
return map;
}


/**
* 上传至fastDFS
* @param multipartFile
* @return 文件id
*/
private String fdfsUpload(MultipartFile multipartFile) {
// 1. 初始化fastDFS的环境
initFdfsConfig();
// 2. 获取trackerClient服务
TrackerClient trackerClient = new TrackerClient();
try {
TrackerServer trackerServer = trackerClient.getConnection();
// 3. 获取storage服务
StorageServer storeStorage = trackerClient.getStoreStorage(trackerServer);
// 4. 获取storageClient
StorageClient1 storageClient1 = new StorageClient1(trackerServer, storeStorage);
// 5. 上传文件 (文件字节, 文件扩展名, )
// 5.1 获取文件扩展名
String originalFilename = multipartFile.getOriginalFilename();
String extName = originalFilename.substring(originalFilename.lastIndexOf(".") + 1);
// 5.2 上传
String fileId = storageClient1.upload_file1(multipartFile.getBytes(), extName, null);
return fileId;
} catch (Exception e) {
System.out.println(e);
return null;
}
}

/**
* 初始化fastDFS的环境
*/
private void initFdfsConfig() {
try {
ClientGlobal.initByTrackers(tracker_servers);
ClientGlobal.setG_connect_timeout(connect_timeout);
ClientGlobal.setG_network_timeout(network_timeout);
ClientGlobal.setG_charset(charset);
} catch (Exception e) {
System.out.println(e);
}
}

}

创建UploadController

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/**
* @program: fastdfs-demo
* @author: 雷哥
* @create: 2020-01-03 10:08
**/

@RestController
@RequestMapping("upload")
public class UploadController {

@Autowired
private UploadService uploadService;


/**
* 作上传
*/
@RequestMapping("doUpload")
public Map<String,Object> doUpload(MultipartFile mf){
System.out.println(mf.getOriginalFilename());
Map<String, Object> map = uploadService.upload(mf);
return map;
}

}

创建static/index.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>


<h1>文件上传</h1>
<hr>
<form action="/upload/doUpload" method="post" enctype="multipart/form-data">
<input type="file" name="mf">
<input type="submit" value="上传">
</form>

</body>
</html>

浏览器上传成功

使用springboot完成文件上传2

修改pom.xml

1
2
3
4
5
6
7
<!-- https://mvnrepository.com/artifact/com.github.tobato/fastdfs-client -->
<dependency>
<groupId>com.github.tobato</groupId>
<artifactId>fastdfs-client</artifactId>
<version>1.26.7</version>
</dependency>

创建配置类UploadProperties

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* @program: fastdfs-demo
* @author: zx
* @create: 2020-01-03 10:44
**/

@ConfigurationProperties(prefix = "upload")
@Data
public class UploadProperties {

private String baseUrl;

private List<String> allowTypes;
}

修改yml文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
fdfs:
so-timeout: 2500 # 读取时间
connect-timeout: 600 # 连接超时时间
thumb-image: # 缩略图
width: 100
height: 100
tracker-list: # tracker服务配置地址列表
- 39.97.251.40:22122
upload:
base-url: http://39.97.251.40/
allow-types:
- image/jpeg
- image/png
- image/bmp
- image/gif

创建UploadService

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
package com.zx.utils;

import com.github.tobato.fastdfs.domain.fdfs.StorePath;
import com.github.tobato.fastdfs.service.FastFileStorageClient;
import com.sxt.config.UploadProperties;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.IOException;

/**
* @program: fastdfs-demo
* @author: 雷哥
* @create: 2020-01-03 10:48
**/
@Component
@EnableConfigurationProperties(UploadProperties.class)
public class UploadService {
private Log log= LogFactory.getLog(UploadService.class);

@Autowired
private FastFileStorageClient storageClient;

@Autowired
private UploadProperties prop;

public String uploadImage(MultipartFile file) {
// 1、校验文件类型
String contentType = file.getContentType();
if (!prop.getAllowTypes().contains(contentType)) {
throw new RuntimeException("文件类型不支持");
}
// 2、校验文件内容
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 {
// 3、上传到FastDFS
// 3.1、获取扩展名
String extension = StringUtils.substringAfterLast(file.getOriginalFilename(), ".");
// 3.2、上传
StorePath storePath = storageClient.uploadFile(file.getInputStream(), file.getSize(), extension, null);
// 返回路径
return prop.getBaseUrl() + storePath.getFullPath();
} catch (IOException e) {
log.error("【文件上传】上传文件失败!....{}", e);
throw new RuntimeException("【文件上传】上传文件失败!"+e.getMessage());
}
}
}