版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u010046908/article/details/54906751
目前项目中需要存储一些文件、视频等。于是乎,查找了一些关于文件服务器资料。其中有Lustre、HDFS、Gluster、Alluxio、Ceph 、FastDFS。下面简单介绍一下:
Lustre 是一个大规模的、安全可靠的、具备高可用性的集群文件系统,它是由SUN公司开发和维护的。该项目主要的目的就是开发下一代的集群文件系统,目前可以支持超过10000个节点,数以PB的数据存储量。
HDFS Hadoop Distributed File System,简称HDFS,是一个分布式文件系统。HDFS是一个高度容错性的系统,适合部署在廉价的机器上。HDFS能提供高吞吐量的数据访问,非常适合大规模数据集上的应用。
GlusterFS 是一个集群的文件系统,支持PB级的数据量。GlusterFS 通过RDMA和TCP/IP方式将分布到不同服务器上的存储空间汇集成一个大的网络化并行文件系统。
Alluxio 前身是Tachyon,是以内存为中心的分布式文件系统,拥有高性能和容错能力,能够为集群框架(如Spark、MapReduce)提供可靠的内存级速度的文件共享服务。
Ceph 是新一代开源分布式文件系统,主要目标是设计成基于POSIX的没有单点故障的分布式文件系统,提高数据的容错性并实现无缝的复制。
FastDFS是一个开源的轻量级分布式文件系统,它对文件进行管理,功能包括:文件存储、文件同步、文件访问(文件上传、文件下载)等,解决了大容量存储和负载均衡的问题。特别适合以文件为载体的在线服务,如相册网站、视频网站等等。FastDFS为互联网量身定制,充分考虑了冗余备份、负载均衡、线性扩容等机制,并注重高可用、高性能等指标,使用FastDFS很容易搭建一套高性能的文件服务器集群提供文件上传、下载等服务。
通过以上6中文件的服务器的介绍,我们业务非常适合选择用FastDFS,所以就了解学习了一番,感觉确实颇为强大,在此再次感谢淘宝资深架构师余庆大神开源了如此优秀的轻量级分布式文件系统,本篇文章就记录一下FastDFS的最新版本5.0.9在CentOS7中的安装与配置。
1.Fastdfs的简介
了解一下基础概念,FastDFS是一个开源的轻量级分布式文件系统,由跟踪服务器(tracker server)、存储服务器(storage server)和客户端(client)三个部分组成,主要解决了海量数据存储问题,特别适合以中小文件(建议范围:4KB < file_size <500MB)为载体的在线服务。
FastDFS系统结构如下图所示:
跟踪器和存储节点都可以由一台多台服务器构成。跟踪器和存储节点中的服务器均可以随时增加或下线而不会影响线上服务。其中跟踪器中的所有服务器都是对等的,可以根据服务器的压力情况随时增加或减少。
为了支持大容量,存储节点(服务器)采用了分卷(或分组)的组织方式。存储系统由一个或多个卷组成,卷与卷之间的文件是相互独立的,所有卷 的文件容量累加就是整个存储系统中的文件容量。一个卷可以由一台或多台存储服务器组成,一个卷下的存储服务器中的文件都是相同的,卷中的多台存储服务器起 到了冗余备份和负载均衡的作用。
在卷中增加服务器时,同步已有的文件由系统自动完成,同步完成后,系统自动将新增服务器切换到线上提供服务。
当存储空间不足或即将耗尽时,可以动态添加卷。只需要增加一台或多台服务器,并将它们配置为一个新的卷,这样就扩大了存储系统的容量。
2.FastDFS的下载
Fastdfs的稳定版下载地址
3.FastDFS的 安装
详细见
CentOS 7 安装配置分布式文件系统 FastDFS 5.0.5
4.SpringMVC上传文件到FastDFS
4.1 fast_client.cnf配置
1
2
3
4
5
6
7
8
9
10
11
12 1connect_timeout = 2
2#网络超时时间
3network_timeout = 30
4#字符集
5charset = UTF-8
6#跟踪服务器的端口
7http.tracker_http_port = 9099
8http.anti_steal_token = no
9http.secret_key = FastDFS1234567890
10#跟踪服务器地址 。跟踪服务器主要是起到负载均衡的作用
11tracker_server = 192.168.0.116:22122
12
4.2 fastdfs文件上传的流程
上传文件交互过程:
- client询问tracker上传到的storage,不需要附加参数;
- tracker返回一台可用的storage;
- client直接和storage通讯完成文件上传。
4.3 FastDFS文件下载的流程
下载文件交互过程:
- client询问tracker下载文件的storage,参数为文件标识(卷名和文件名);
- tracker返回一台可用的storage;
- client直接和storage通讯完成文件下载。
需要说明的是,client为使用FastDFS服务的调用方,client也应该是一台服务器,它对tracker和storage的调用均为服务器间的调用。
4.4 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
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396 1package com.lidong.dubbo.util;
2
3import org.csource.common.MyException;
4import org.csource.common.NameValuePair;
5import org.csource.fastdfs.*;
6import org.slf4j.Logger;
7import org.slf4j.LoggerFactory;
8import org.springframework.core.io.ClassPathResource;
9import org.springframework.web.multipart.MultipartFile;
10
11import javax.servlet.http.HttpServletResponse;
12import java.io.*;
13import java.util.HashMap;
14import java.util.Map;
15
16/**
17 * @项目名称:lidong-dubbo
18 * @类名:FastDFSUtil
19 * @类的描述: FastDFS 上传文件到文件服务器
20 * @作者:lidong
21 * @创建时间:2017/2/6 下午5:23
22 * @公司:chni
23 * @QQ:1561281670
24 * @邮箱:lidong1665@163.com
25 */
26public class FastDFSUtil {
27
28 private final static
29
30 Logger logger = LoggerFactory.getLogger(FastDFSUtil.class);
31
32
33 /**
34 *上传服务器本地文件-通过Linux客户端,调用客户端命令上传
35 * @param filePath 文件绝对路径
36 * @return Map<String,Object> code-返回代码, group-文件组, msg-文件路径/错误信息
37 */
38 public static Map<String, Object> uploadLocalFile(String filePath) {
39 Map<String, Object> retMap = new HashMap<String, Object>();
40 /**
41 * 1.上传文件的命令
42 */
43 String command = "fdfs_upload_file /etc/fdfs/client.conf " + filePath;
44 /**
45 * 2.定义文件的返回信息
46 */
47 String fileId = "";
48 InputStreamReader inputStreamReader = null;
49 BufferedReader bufferedReader = null;
50 try {
51 /**
52 * 3.通过调用api, 执行linux命令上传文件
53 */
54 Process process = Runtime.getRuntime().exec(command);
55 /**
56 * 4.读取上传后返回的信息
57 */
58 inputStreamReader = new InputStreamReader(process.getInputStream());
59 bufferedReader = new BufferedReader(inputStreamReader);
60 String line;
61 if ((line = bufferedReader.readLine()) != null) {
62 fileId = line;
63 }
64 /**
65 * 5.如果fileId包含M00,说明文件已经上传成功。否则文件上传失败
66 */
67 if (fileId.contains("M00")) {
68 retMap.put("code", "0000");
69 retMap.put("group", fileId.substring(0, 6));
70 retMap.put("msg", fileId.substring(7, fileId.length()));
71 } else {
72 retMap.put("code", "0001"); //上传错误
73 retMap.put("msg", fileId); //返回信息
74 }
75
76 } catch (Exception e) {
77 logger.error("IOException:" + e.getMessage());
78 retMap.put("code", "0002");
79 retMap.put("msg", e.getMessage());
80 }finally {
81 if (inputStreamReader!=null){
82 try {
83 inputStreamReader.close();
84 } catch (IOException e) {
85 e.printStackTrace();
86 }
87 }
88 if (bufferedReader != null) {
89 try {
90 bufferedReader.close();
91 } catch (IOException e) {
92 e.printStackTrace();
93 }
94 }
95 }
96 return retMap;
97 }
98
99
100 /**
101 * Description: 直接通过fdfs java客户端上传到服务器-读取本地文件上传
102 *
103 * @param filePath 本地文件绝对路径
104 * @return Map<String,Object> code-返回代码, group-文件组, msg-文件路径/错误信息
105 */
106 public static Map<String, Object> upload(String filePath) {
107 Map<String, Object> retMap = new HashMap<String, Object>();
108 File file = new File(filePath);
109 TrackerServer trackerServer = null;
110 StorageServer storageServer = null;
111 if (file.isFile()) {
112 try {
113 String tempFileName = file.getName();
114 byte[] fileBuff = FileUtil.getBytesFromFile(file);
115 String fileId = "";
116 //截取后缀
117 String fileExtName = tempFileName.substring(tempFileName.lastIndexOf(".") + 1);
118 ConfigAndConnectionServer configAndConnectionServer = new ConfigAndConnectionServer().invoke(1);
119 StorageClient1 storageClient1 = configAndConnectionServer.getStorageClient1();
120 storageServer = configAndConnectionServer.getStorageServer();
121 trackerServer = configAndConnectionServer.getTrackerServer();
122
123 /**
124 * 4.设置文件的相关属性。调用客户端的upload_file1的方法上传文件
125 */
126 NameValuePair[] metaList = new NameValuePair[3];
127 //原始文件名称
128 metaList[0] = new NameValuePair("fileName", tempFileName);
129 //文件后缀
130 metaList[1] = new NameValuePair("fileExtName", fileExtName);
131 //文件大小
132 metaList[2] = new NameValuePair("fileLength", String.valueOf(file.length()));
133 //开始上传文件
134 fileId = storageClient1.upload_file1(fileBuff, fileExtName, metaList);
135 retMap = handleResult(retMap, fileId);
136 } catch (Exception e) {
137 e.printStackTrace();
138 retMap.put("code", "0002");
139 retMap.put("msg", e.getMessage());
140 }finally {
141 /**
142 * 5.关闭跟踪服务器的连接
143 */
144 colse(storageServer, trackerServer);
145 }
146 } else {
147 retMap.put("code", "0001");
148 retMap.put("msg", "error:本地文件不存在!");
149 }
150 return retMap;
151 }
152
153
154 /**
155 * Description:远程选择上传文件-通过MultipartFile
156 *
157 * @param file 文件流
158 * @return Map<String,Object> code-返回代码, group-文件组, msg-文件路径/错误信息
159 */
160 public static Map<String, Object> upload(MultipartFile file) {
161 Map<String, Object> retMap = new HashMap<String, Object>();
162 TrackerServer trackerServer = null;
163 StorageServer storageServer = null;
164 try {
165 if (file.isEmpty()) {
166 retMap.put("code", "0001");
167 retMap.put("msg", "error:文件为空!");
168 } else {
169 ConfigAndConnectionServer configAndConnectionServer = new ConfigAndConnectionServer().invoke(1);
170 StorageClient1 storageClient1 = configAndConnectionServer.getStorageClient1();
171 storageServer = configAndConnectionServer.getStorageServer();
172 trackerServer = configAndConnectionServer.getTrackerServer();
173 String tempFileName = file.getOriginalFilename();
174 //设置元信息
175 NameValuePair[] metaList = new NameValuePair[3];
176 //原始文件名称
177 metaList[0] = new NameValuePair("fileName", tempFileName);
178 //文件后缀
179 byte[] fileBuff = file.getBytes();
180 String fileId = "";
181 //截取后缀
182 String fileExtName = tempFileName.substring(tempFileName.lastIndexOf(".") + 1);
183
184 metaList[1] = new NameValuePair("fileExtName", fileExtName);
185 //文件大小
186 metaList[2] = new NameValuePair("fileLength", String.valueOf(file.getSize()));
187 /**
188 * 4.调用客户端呢的upload_file1的方法开始上传文件
189 */
190 fileId = storageClient1.upload_file1(fileBuff, fileExtName, metaList);
191 retMap = handleResult(retMap, fileId);
192 }
193 } catch (Exception e) {
194 retMap.put("code", "0002");
195 retMap.put("msg", "error:文件上传失败!");
196 }finally {
197 /**
198 * 5.关闭跟踪服务器的连接
199 */
200 colse(storageServer, trackerServer);
201 }
202 return retMap;
203 }
204
205
206 /**
207 * 下载文件
208 *
209 * @param response
210 * @param filepath 数据库存的文件路径
211 * @param downname 下载后的名称
212 * filepath M00/开头的文件路径
213 * group 文件所在的组 如:group0
214 * @throws IOException
215 */
216 public static void download(HttpServletResponse response, String group, String filepath, String downname) {
217 StorageServer storageServer = null;
218 TrackerServer trackerServer = null;
219 try {
220 ConfigAndConnectionServer configAndConnectionServer = new ConfigAndConnectionServer().invoke(0);
221 StorageClient storageClient = configAndConnectionServer.getStorageClient();
222 storageServer = configAndConnectionServer.getStorageServer();
223 trackerServer = configAndConnectionServer.getTrackerServer();
224
225 /**
226 *4.调用客户端的下载download_file的方法
227 */
228 byte[] b = storageClient.download_file(group, filepath);
229 if (b == null) {
230 logger.error("Error1 : file not Found!");
231 response.getWriter().write("Error1 : file not Found!");
232 } else {
233 logger.info("下载文件..");
234 downname = new String(downname.getBytes("utf-8"), "ISO8859-1");
235 response.setHeader("Content-Disposition", "attachment;fileName=" + downname);
236 OutputStream out = response.getOutputStream();
237 out.write(b);
238 out.close();
239 }
240 } catch (Exception e) {
241 e.printStackTrace();
242 try {
243 response.getWriter().write("Error1 : file not Found!");
244 } catch (IOException e1) {
245 e1.printStackTrace();
246 }
247 }finally {
248 /**
249 * 5.关闭跟踪服务器的连接
250 */
251 colse(storageServer, trackerServer);
252 }
253 }
254
255 /**
256 * 删除文件
257 *
258 * @param group 文件分组, filepath 已M00/ 开头的文件路径
259 * @return Map<String,Object> code-返回代码, msg-错误信息
260 */
261 public static Map<String, Object> delete(String group, String filepath) {
262 Map<String, Object> retMap = new HashMap<String, Object>();
263 StorageServer storageServer = null;
264 TrackerServer trackerServer = null;
265 try {
266 ConfigAndConnectionServer configAndConnectionServer = new ConfigAndConnectionServer().invoke(0);
267 StorageClient storageClient = configAndConnectionServer.getStorageClient();
268 storageServer = configAndConnectionServer.getStorageServer();
269 trackerServer = configAndConnectionServer.getTrackerServer();
270 /**
271 * 4.调用客户端的delete_file方法删除文件
272 */
273 int i = storageClient.delete_file(group, filepath);
274 if (i == 0) {
275 retMap.put("code", "0000");
276 retMap.put("msg", "删除成功!");
277 } else {
278 retMap.put("code", "0001");
279 retMap.put("msg", "文件不存在!");
280 }
281 } catch (Exception e) {
282 e.printStackTrace();
283 retMap.put("code", "0002");
284 retMap.put("msg", "删除失败!");
285 } finally {
286 /**
287 * 5.关闭跟踪服务器的连接
288 */
289 colse(storageServer, trackerServer);
290 }
291
292 return retMap;
293
294 }
295
296 /**
297 * 关闭服务器
298 *
299 * @param storageServer
300 * @param trackerServer
301 */
302 private static void colse(StorageServer storageServer, TrackerServer trackerServer) {
303 if (storageServer != null && trackerServer != null) {
304 try {
305 storageServer.close();
306 trackerServer.close();
307 } catch (IOException e) {
308 e.printStackTrace();
309 }
310
311 }
312 }
313
314 /**
315 * 处理上传到文件服务器之后,返回来的结果
316 *
317 * @param retMap
318 * @param fileId
319 * @return
320 */
321 private static Map<String, Object> handleResult(Map<String, Object> retMap, String fileId) {
322 if (!fileId.equals("") && fileId != null) {
323 retMap.put("code", "0000");
324 retMap.put("group", fileId.substring(0, 6));
325 retMap.put("msg", fileId.substring(7, fileId.length()));
326 } else {
327 retMap.put("code", "0003");
328 retMap.put("msg", "error:上传失败!");
329 }
330
331 return retMap;
332 }
333
334 /**
335 * @项目名称:lidong-dubbo
336 * @类名:FastDFSUtil
337 * @类的描述: ConfigAndConnectionServer
338 * @作者:lidong
339 * @创建时间:2017/2/7 上午8:47
340 * @公司:chni
341 * @QQ:1561281670
342 * @邮箱:lidong1665@163.com
343 */
344 private static class ConfigAndConnectionServer {
345 private TrackerServer trackerServer;
346 private StorageServer storageServer;
347 private StorageClient storageClient;
348 private StorageClient1 storageClient1;
349
350
351 public TrackerServer getTrackerServer() {
352 return trackerServer;
353 }
354
355 public StorageServer getStorageServer() {
356 return storageServer;
357 }
358
359 public StorageClient getStorageClient() {
360 return storageClient;
361 }
362
363 public StorageClient1 getStorageClient1() {
364 return storageClient1;
365 }
366
367 public ConfigAndConnectionServer invoke(int flag) throws IOException, MyException {
368 /**
369 * 1.读取fastDFS客户端配置文件
370 */
371 ClassPathResource cpr = new ClassPathResource("fdfs_client.conf");
372 /**
373 * 2.配置文件的初始化信息
374 */
375 ClientGlobal.init(cpr.getClassLoader().getResource("fdfs_client.conf").getPath());
376 TrackerClient tracker = new TrackerClient();
377 /**
378 * 3.建立连接
379 */
380 trackerServer = tracker.getConnection();
381 storageServer = null;
382 /**
383 * 如果flag=0时候,构造StorageClient对象否则构造StorageClient1
384 */
385 if (flag == 0) {
386 storageClient = new StorageClient(trackerServer, storageServer);
387 } else {
388 storageClient1 = new StorageClient1(trackerServer, storageServer);
389 }
390 return this;
391 }
392 }
393}
394
395
396
4.5 SpringMVC 上传文件到Fastdfs文件服务器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24 1@RequestMapping("/upload")
2 public String addUser(@RequestParam("file") CommonsMultipartFile[] files,
3 HttpServletRequest request){
4
5 for(int i = 0;i<files.length;i++){
6 logger.info("fileName-->" + files[i].getOriginalFilename()+" file-size--->"+files[i].getSize());
7 Map<String, Object> retMap = FastDFSUtil.upload(files[i]);
8 String code = (String) retMap.get("code");
9 String group = (String) retMap.get("group");
10 String msg = (String) retMap.get("msg");
11
12 if ("0000".equals(code)){
13 logger.info("文件上传成功");
14 //TODO:将上传文件的路径保存到mysql数据库
15 }else {
16 logger.info("文件上传失败");
17 }
18
19
20 }
21 return "/success";
22 }
23
24
基本上就这么多。大家在学习的过程中如果遇到问题。可以直接在下面评论、吐槽。
代码地址