0%

FastDFS分布式文件系统

一、FastDFS简介

1.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称为存储服务器。

流程架构图:

2.上传流程

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

组名 :文件上传后所在的 storage 组名称,在文件上传成功后有storage 服务器返回,需要客户端自行保存。

虚拟磁盘路径:storage 配置的虚拟路径,与磁盘选项store_path*对应。如果配置了store_path0 则是 M00,如果配置了 store_path1 则是 M01,以此类推。

数据两级目录:storage 服务器在每个虚拟磁盘路径下创建的两级目录,用于存储数据文件。

文件名:与文件上传时不同。是由存储服务器根据特定信息生成,文件名包含:源存储服务器 IP 地址、文件创建时间戳、文件大小、随机数和文件拓展名等信息。

二、FastDFS搭建

1.Docker搭建FastDFS的开发环境

1.1 拉取镜像

1
docker pull morunchang/fastdfs

1.2 运行tracker

1
docker run -d --name tracker --net=host morunchang/fastdfs sh tracker.sh

1.3 运行 storage

  • 模板
1
docker run -d --name storage --net=host -e TRACKER_IP=<your tracker server address>:22122 -e GROUP_NAME=<group name> morunchang/fastdfs sh storage.sh
  • 例子
1
docker run -d --name storage --net=host -e TRACKER_IP=x.x.x.x:22122 -e GROUP_NAME=group1 morunchang/fastdfs sh storage.sh

1.使用的网络模式是–net=host, 替换为你机器的Ip即可

2. 是组名,即storage的组

3.如果想要增加新的storage服务器,再次运行该命令,注意更换 新组名

1.4 修改nginx的配置

  • 进入容器
1
docker exec -it storage /bin/bash
  • 修改nginx.conf
1
vi /etc/nginx/conf/nginx.conf
  • 往配置文件中添加一下内容(新版默认配置已经配置好了,没有就添加,添加之后重启容器)
1
2
3
4
location ~ /M00 {
root /data/fast_data/data;
ngx_fastdfs_module;
}
  • 不需要图片缓存到本地
1
2
3
4
5
location ~ /M00 {
add_header Cache-Control no-store;#不缓存图片到本地
root /data/fast_data/data;
ngx_fastdfs_module;
}

2.Tracker和Storage配置文件

默认配置就行,这里主要是知道怎么修改 参考链接

2.1 Tracker配置文件

  • 进入容器
1
docker exec -it tracker /bin/bash
  • 修改文件tracker.conf(参考)
1
vi /etc/fdfs/tracker.conf
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
# is this config file disabled
# false for enabled
# true for disabled
disabled=false

# bind an address of this host
# empty for bind all addresses of this host
bind_addr=

# the tracker server port
port=22122

# connect timeout in seconds
# default value is 30s
connect_timeout=30

# network timeout in seconds
# default value is 30s
network_timeout=60

# the base path to store data and log files
base_path=/data/fast_data

# max concurrent connections this server supported
max_connections=256

# accept thread count
# default value is 1
# since V4.07
accept_threads=1

# work thread count, should <= max_connections
# default value is 4
# since V2.00
work_threads=4

# the method of selecting group to upload files
# 0: round robin
# 1: specify group
# 2: load balance, select the max free space group to upload file
store_lookup=2

# which group to upload file
# when store_lookup set to 1, must set store_group to the group name
store_group=group2

# which storage server to upload file
# 0: round robin (default)
# 1: the first server order by ip address
# 2: the first server order by priority (the minimal)
store_server=0

# which path(means disk or mount point) of the storage server to upload file
# 0: round robin
# 2: load balance, select the max free space path to upload file
store_path=0
  • 修改文件client.conf
1
vi /etc/fdfs/client.conf

2.2 Storage配置文件

  • 进入容器
1
docker exec -it storage /bin/bash
  • 修改文件storage.conf (参考)
1
vi /etc/fdfs/storage.conf
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
# is this config file disabled
# false for enabled
# true for disabled
disabled=false

# the name of the group this storage server belongs to
#
# comment or remove this item for fetching from tracker server,
# in this case, use_storage_id must set to true in tracker.conf,
# and storage_ids.conf must be configed correctly.
group_name=group1 #组名

# bind an address of this host
# empty for bind all addresses of this host
bind_addr=

# if bind an address of this host when connect to other servers
# (this storage server as a client)
# true for binding the address configed by above parameter: "bind_addr"
# false for binding any address of this host
client_bind=true

# the storage server port
port=23000 #通信端口

# connect timeout in seconds
# default value is 30s
connect_timeout=30 #连接超时时间,秒

# network timeout in seconds
# default value is 30s
network_timeout=60 #网络请求超时时间

# heart beat interval in seconds
heart_beat_interval=30 #心跳

# disk usage report interval in seconds
stat_report_interval=60

# the base path to store data and log files
base_path=/data/fast_data #数据存储的基础目录

# max concurrent connections the server supported
# default value is 256
# more max_connections means more memory will be used
max_connections=256 #最大支持并发数

# the buff size to recv / send data
# this parameter must more than 8KB
# default value is 64KB
# since V2.00
buff_size = 256KB

# accept thread count
# default value is 1
# since V4.07
  • 修改文件client.conf
1
vi /etc/fdfs/client.conf

如果修改了配置文件注意重启容器

三、FastDFS微服务搭建

1.环境搭建

1.1 导入FastDFS依赖

1
2
3
4
5
6
7
8
<dependencies>
<!--FastDFS客户端程序包-->
<dependency>
<groupId>net.oschina.zcx7878</groupId>
<artifactId>fastdfs-client-java</artifactId>
<version>1.27.0.0</version>
</dependency>
</dependencies>

1.2 在resources文件夹下创建fasfDFS的配置文件fdfs_client.conf

1
2
3
4
5
6
7
8
9
10
11
#连接超时时间,单位为秒
connect_timeout = 60
#通信超时时间,单位为秒。
#发送或接收数据时。假设在超时时间后还不能发送或接收数据,则本次网络通信失败
network_timeout = 60
#字符集
charset = UTF-8
#Tracker的Http请求端口
http.tracker_http_port = 8080
#Tracker的TCP通信端口
tracker_server = x.x.x.x:22122

1.3 在resources文件夹下创建application.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
server:
port: 18082
spring:
application:
name: file
servlet:
multipart:
#上传文件大小限制
max‐file‐size: 10MB
#请求文件大小限制
max‐request‐size: 10MB
eureka:
client:
service‐url:
defaultZone: http://127.0.0.1:7001/eureka
instance:
prefer‐ip‐address: true
feign:
hystrix:
enabled: true

1.4 启动springboot

这里在启动时可能会出现以下异常而导致启动失败(因为有mysql包的存在,springboot自动启动了,但是我们并没有进行配置)

1
2
3
4
5
6
7
8
9
10
11
12
Description:

Failed to configure a DataSource: 'url' attribute is not specified and no embedded datasource could be configured.

Reason: Failed to determine a suitable driver class


Action:

Consider the following:
If you want an embedded database (H2, HSQL or Derby), please put it on the classpath.
If you have database settings to be loaded from a particular profile you may need to activate it (no profiles are currently active).

解决办法(在@SpringBootApplication注解中排除自动加载数据源配置)

1
2
3
4
5
6
7
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)//排除自动加载数据源配置
@EnableDiscoveryClient//eureka发现服务
public class FileApplication {
public static void main(String[] args) {
SpringApplication.run(FileApplication.class, args);
}
}

1.5 阿里云端口开放

以下三个端口必须开放

2.使用FastDFS

2.1 添加依赖

1
2
3
4
5
6
<!--FastDFS客户端程序包-->
<dependency>
<groupId>net.oschina.zcx7878</groupId>
<artifactId>fastdfs-client-java</artifactId>
<version>1.27.0.0</version>
</dependency>

2.2 编写封装文件实体类FastDFSFile

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
/**
* 封装文件实体类
*/
@Data
public class FastDFSFile implements Serializable {

//文件名字
private String name;
//文件内容
private byte[] content;
//文件扩展名
private String ext;
//文件MD5摘要值
private String md5;
//文件创建作者
private String author;

// getter and setter ...


public FastDFSFile(String name, byte[] content, String ext) {
this.name = name;
this.content = content;
this.ext = ext;
}

public FastDFSFile(String name, byte[] content, String ext, String md5, String author) {
this.name = name;
this.content = content;
this.ext = ext;
this.md5 = md5;
this.author = author;
}
}

2.3 编写FastDFSUtil工具类

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
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
/**
* 实现FastDFS文件管理
* 文件上传
* 文件删除
* 文件下载
* 文件信息获取
* Storage信息获取
* Tracker信息获取
*/
public class FastDFSUtil {

/**
* 加载Tracker连接信息
*/
static {
try {
//ClassPathResource是spring提供的类,可以获取resources目录下的配置文件信息
String path = new ClassPathResource ( "fdfs_client.conf" ).getURL ( ).getPath ( );
//加载Tracker连接信息
ClientGlobal.init ( path );
} catch (Exception e) {
e.printStackTrace ( );
}
}

/**
* 获取TrackerServer
*
* @return
* @throws Exception
*/
public static TrackerServer getTrackerServer() throws Exception {
//创建一个Tracker客户端对象TrackerClient
TrackerClient trackerClient = new TrackerClient ( );

//通过TrackerClient获取TrackerServer服务连接信息
TrackerServer trackerServer = trackerClient.getConnection ( );

return trackerServer;
}

/**
* 文件上传
*
* @param fastDFSFile:上传的文件信息封装
*/
public static String[] uploadFile(FastDFSFile fastDFSFile) throws Exception {
//调用封装方法获取TrackerServer
TrackerServer trackerServer = getTrackerServer ( );

//通过TrackerServer获取Storage的连接信息,并且创建Storage客户端对象StorageClient存储获取的连接信息
StorageClient storageClient = new StorageClient ( trackerServer, null );

//通过StorageClient访问Storage实现文件上传,并且获取文件上传后的存储信息
/**
* 参数说明:
* 1.file_buff是文件数组
* 2.file_ext_name是文件后缀名 例如:jpg
* 3.meta_list是文件附加信息,不添加填写null 例如:拍摄地点,时间,文件作者等
*
* uploads[]:
* uploads[0]是文件上传所存储的Storage的组名
* uploads[1]是文件上传到Storage的文件名(包含了目录结构,例如:M00/00/00/rBKx1F-ab_SAOzS3ADkUkK8vhDk359.jpg)
*/
/* NameValuePair[] meta_list = new NameValuePair[1];
meta_list[0] = new NameValuePair ( "author",fastDFSFile.getAuthor () );//示例给上传文件添加附加信息*/
String[] uploads = storageClient.upload_file ( fastDFSFile.getContent ( ), fastDFSFile.getExt ( ), null );

return uploads;
}

/**
* 文件下载
* @param groupName:组名
* @param remoteFileName:文件的存储路径名 例如:M00/00/00/rBKx1F-ab_SAOzS3ADkUkK8vhDk359.jpg
* @return
* @throws Exception
*/
public static InputStream downloadFile(String groupName, String remoteFileName) throws Exception {
//调用封装方法获取TrackerServer
TrackerServer trackerServer = getTrackerServer ( );

//通过TrackerServer获取Storage的连接信息,并且创建Storage客户端对象StorageClient存储获取的连接信息
StorageClient storageClient = new StorageClient ( trackerServer, null );

//获取文件字节数组
byte[] bytes = storageClient.download_file ( groupName, remoteFileName );

//将文件字节数组转换为InputStream
ByteArrayInputStream in = new ByteArrayInputStream ( bytes );
return in;
}

/**
* 删除文件
* @param groupName:组名
* @param remoteFileName:文件的存储路径名 例如:M00/00/00/rBKx1F-ab_SAOzS3ADkUkK8vhDk359.jpg
* @throws Exception
*/
public static void deleteFile(String groupName, String remoteFileName) throws Exception {
//调用封装方法获取TrackerServer
TrackerServer trackerServer = getTrackerServer ( );

//通过TrackerServer获取Storage的连接信息,并且创建Storage客户端对象StorageClient存储获取的连接信息
StorageClient storageClient = new StorageClient ( trackerServer, null );

//删除文件
storageClient.delete_file ( groupName, remoteFileName );
}

/**
* 获取文件信息
* @param groupName:组名
* @param remoteFileName:文件的存储路径名 例如:M00/00/00/rBKx1F-ab_SAOzS3ADkUkK8vhDk359.jpg
* @return
* @throws Exception
*/
public static FileInfo getFile(String groupName, String remoteFileName) throws Exception {
//调用封装方法获取TrackerServer
TrackerServer trackerServer = getTrackerServer ( );

//通过TrackerServer获取Storage的连接信息,并且创建Storage客户端对象StorageClient存储获取的连接信息
StorageClient storageClient = new StorageClient ( trackerServer, null );

//获取文件信息
/**
* fileInfo可以获取的信息:
* source_ip_addr是访问的ip地址
* file_size是文件的大小
* create_timestamp是文件的创建时间
* crc32是文件签名信息
*/
FileInfo fileInfo = storageClient.get_file_info ( groupName, remoteFileName );
return fileInfo;
}

/**
* 获取指定组的StorageServer信息
* @param groupName:组名
* @return
* @throws Exception
*/
public static StorageServer getStorageServer(String groupName) throws Exception {
//创建一个Tracker客户端对象TrackerClient
TrackerClient trackerClient = new TrackerClient ( );

//通过TrackerClient获取TrackerServer服务连接信息
TrackerServer trackerServer = trackerClient.getConnection ( );

//获取指定组的StorageServer信息
StorageServer storageServer = trackerClient.getStoreStorage ( trackerServer,groupName);
//StorageServer[] storageServers = trackerClient.getStoreStorages ( trackerServer,groupName);//获取当前组的所有Storage服务器
return storageServer;
}

/**
* 获取StorageServer的ip和端口
* 指定组下的所有StorageServer信息
* @param groupName:组名
* @param remoteFileName:文件的存储路径名 例如:M00/00/00/rBKx1F-ab_SAOzS3ADkUkK8vhDk359.jpg
* @return
* @throws Exception
*/
public static ServerInfo[] getStorageServerInfo(String groupName, String remoteFileName) throws Exception {
//创建一个Tracker客户端对象TrackerClient
TrackerClient trackerClient = new TrackerClient ( );

//通过TrackerClient获取TrackerServer服务连接信息
TrackerServer trackerServer = trackerClient.getConnection ( );

//获取指定组的StorageServerInfo信息
ServerInfo[] storageServers = trackerClient.getFetchStorages ( trackerServer, groupName, remoteFileName );
return storageServers;
}

/**
* 获取TrackerServer的URL
* TrackerServer的端口和Nginx的端口最好保持一致
* @param groupName
* @param remoteFileName
* @return
* @throws Exception
*/
public static String getTrackerServerUrl(String groupName, String remoteFileName) throws Exception {
//调用封装方法获取TrackerServer
TrackerServer trackerServer = getTrackerServer ( );

//获取TrackerServer的IP和请求端口
int port = ClientGlobal.getG_tracker_http_port ( );
String ip = trackerServer.getInetSocketAddress ( ).getHostName ( );
String url = "http://"+ip+":"+port;
return url;
}

public static void main(String[] args) throws Exception {
//获取文件信息
/*FileInfo fileInfo = getFile ( "group1", "M00/00/00/rBKx1F-arI2AHjruAAOyk4wRlUU283.jpg" );
System.out.println ( fileInfo.getSourceIpAddr ( ) );//内网ip
System.out.println ( fileInfo.getFileSize ( ) );
System.out.println ( fileInfo.getCreateTimestamp ( ) );
System.out.println ( fileInfo.getCrc32 ( ) );*/

//文件下载
/* long startTime = System.currentTimeMillis ( );
BufferedInputStream buffer_in = new BufferedInputStream ( downloadFile ( "group1", "M00/00/00/rBKx1F-arI2AHjruAAOyk4wRlUU283.jpg" ) );
BufferedOutputStream buffer_out = new BufferedOutputStream ( new FileOutputStream ( "D:\\a.jpg" ) );

byte[] bytes = new byte[1024];


int len = 0;
while ((len = buffer_in.read (bytes))!=-1){
buffer_out.write ( bytes );
}

buffer_out.close ();
buffer_in.close ();

long endTime = System.currentTimeMillis ( );
System.out.println ((endTime-startTime)+"ms" );*/

//文件删除
//deleteFile ( "group1", "M00/00/00/rBKx1F-ams-ABG71ADkUkK8vhDk958.jpg" );

//获取指定组的StorageServer信息
/* StorageServer storageServer = getStorageServer ( "group1" );
System.out.println (storageServer.getInetSocketAddress () );//公网ip加端口号
System.out.println (storageServer.getInetSocketAddress ().getHostName () );//公网ip
//System.out.println (storageServer.getInetSocketAddress ().getHostString () );//公网ip
System.out.println (storageServer.getSocket ());*/

//获取StorageServer的ip和端口
/*ServerInfo[] storageServerInfos = getStorageServerInfo ( "group1", "M00/00/00/rBKx1F-arI2AHjruAAOyk4wRlUU283.jpg" );
for (ServerInfo storageServerInfo : storageServerInfos) {
System.out.println (storageServerInfo.getIpAddr () );
System.out.println (storageServerInfo.getPort () );
}*/

//获取TrackerServer的URL
System.out.println ( getTrackerServerUrl ( "group1", "M00/00/00/rBKx1F-arI2AHjruAAOyk4wRlUU283.jpg" ) );
}
}

2.4 controller控制层

只举例文件上传

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
@RestController
@RequestMapping("/file")
@CrossOrigin//开启跨域
public class FileController {

/**
* 文件上传
* @param file
* @return
* @throws Exception
*/
@PostMapping("/upload")
public Result<String> upload(@RequestParam("file") MultipartFile file) throws Exception {
//封装文件信息
FastDFSFile fastDFSFile = new FastDFSFile (
file.getOriginalFilename ( ),//获取文件名称
file.getBytes ( ),//获取文件字节数组
StringUtils.getFilenameExtension ( file.getOriginalFilename () )//获取文件后缀名
);

//调用FastDFSUtil工具类将文件传入到FastDFS
String[] uploads = FastDFSUtil.uploadFile ( fastDFSFile );

//拼接访问地址url http://x.x.x.x:8080/group1/M00/00/00/rBKx1F-ab_SAOzS3ADkUkK8vhDk359.jpg
String url = FastDFSUtil.getTrackerServerUrl ( uploads[0],uploads[1])+"/"+uploads[0]+"/"+uploads[1];

return new Result<> ( true, StatusCode.OK, "文件上传成功",url );
}

}

3.Storage的存储目录结构

  • 进入storage容器
1
docker exec -it storage /bin/bash
  • 进入到图中标红线的目录就可以查看存储目录结构了

没办法,要恰饭的嘛!