分布式文件存储-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 > <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.confhttp.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;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); ClientGlobal.init(filePath); TrackerClient trackerClient = new TrackerClient (); TrackerServer trackerServer = trackerClient.getConnection(); StorageServer storageServer = null ; StorageClient storageClient = new StorageClient (trackerServer, storageServer); String[] strings = storageClient.upload_file(uploadFilePath, "gif" , null ); 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 public static InputStream downFile (String groupName, String remoteFileName) { try { String filePath = new ClassPathResource ("fdfs_client.conf" ).getFile().getAbsolutePath(); System.out.println(filePath); ClientGlobal.init(filePath); TrackerClient trackerClient = new TrackerClient (); TrackerServer trackerServer = trackerClient.getConnection(); 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
创建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;@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 ("文件不能为空" ); } 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; } private String fdfsUpload (MultipartFile multipartFile) { initFdfsConfig(); TrackerClient trackerClient = new TrackerClient (); try { TrackerServer trackerServer = trackerClient.getConnection(); StorageServer storeStorage = trackerClient.getStoreStorage(trackerServer); StorageClient1 storageClient1 = new StorageClient1 (trackerServer, storeStorage); String originalFilename = multipartFile.getOriginalFilename(); String extName = originalFilename.substring(originalFilename.lastIndexOf("." ) + 1 ); String fileId = storageClient1.upload_file1(multipartFile.getBytes(), extName, null ); return fileId; } catch (Exception e) { System.out.println(e); return null ; } } 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 @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 <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 @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: - 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;@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) { String contentType = file.getContentType(); if (!prop.getAllowTypes().contains(contentType)) { throw new RuntimeException ("文件类型不支持" ); } 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(), "." ); 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()); } } }