FastDFS简介 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],用户上传流程见下图[1]
FastDFS上传流程 流程图
相关概念 客户端上传文件后存储服务器将文件 ID 返回给客户端,此文件 ID 用于以后访问该文件 的索引信息。文件索引信息包括:组名,虚拟磁盘路径,数据两级目录,文件名。
组名 :文件上传后所在的 storage 组名称,在文件上传成功后有storage 服务器返回,需 要客户端自行保存。
虚拟磁盘路径 :storage 配置的虚拟路径,与磁盘选项store_path*对应。如果配置了
store_path0 则是 M00,如果配置了 store_path1 则是 M01,以此类推。
数据两级目录 :storage 服务器在每个虚拟磁盘路径下创建的两级目录,用于存储数据 文件。
文件名 :与文件上传时不同。是由存储服务器根据特定信息生成,文件名包含:源存储
服务器 IP 地址、文件创建时间戳、文件大小、随机数和文件拓展名等信息。
访问地址说明 文件上传完毕后,会返回一个地址,如下
group1是组名
M是只ian过了Storage组中某个节点的主文件夹地址,比如/etc/fdfs/data
02/44是通过算法算出来的2个子目录
wKg…sh是文件的名字
最后通过 http://地址:端口/group1/M00/02/44/xxxxx.jpg就可以访问上传完毕的资源
FastDFS在Docker中的安装 拉取镜像 1 $ docker pull morunchang/fastdfs
运行Tracker
复制以下代码直接运行即可
1 $ docker run -d --name tracker --net=host morunchang/fastdfs sh tracker.sh
–net=host 是网络模式
运行Storage
复制一下代码,修改IP和组名
1 $ docker run -d --name storage --net=host -e TRACKER_IP=192.168.162.130:22122 -e GROUP_NAME=group1 morunchang/fastdfs sh storage.sh
–net=host 是网络模式
192.168.162.130 : 是虚拟机的IP
group1是当前组的名字,如果想新增storage服务器,再次运行该命令,注意更换组名
修改容器随docker启动而启动 1 $ docker update --restart=always tracker
1 $ docker update --restart=always storage
查看或修改nginx配置(非必须步骤)
storage和tracker安装完毕后都nginx服务器,访问地址http://虚拟机IP:8080,配置文件所在目录为 /etc/nginx。默认配置可以改,也可以不改
进入容器 1 $ docker exec -it storage /bin/bash
打开nginx配置文件 1 $ vi /etc/nginx/conf/nginx.conf
SpringBoot项目构建文件存储服务 引入FastDFS的依赖 1 2 3 4 5 <dependency > <groupId > net.oschina.zcx7878</groupId > <artifactId > fastdfs-client-java</artifactId > <version > 1.27.0.0</version > </dependency >
在resources目录里引入配置文件
fdfs_client.conf
1 2 3 4 5 6 7 8 9 10 connect_timeout = 60 还不能发送或接收数据,则本次网络通信失败 network_timeout = 60 charset = UTF-8 http.tracker_http_port = 8080 tracker_server = 192.168.162.130:22122
application.yml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 spring: servlet: multipart: max-file-size: 10MB max-request-size: 10MB application: name: file server: port: 18082 eureka: client: service-url: defaultZone: http://127.0.0.1:7001/eureka instance: prefer-ip-address: true feign: hystrix: enabled: true
max-file-size是单个文件大小,max-request-size是设置总上传的数据大小
如果不是微服务项目eureka和feign配置可以省略
创建springboot启动程序
因为当前项目不需要并且如果没有引入数据库,所以要配置exclude = {DataSourceAutoConfiguration.class},否则无法启动
1 2 3 4 5 6 7 @SpringBootApplication (exclude = {DataSourceAutoConfiguration.class }) @EnableEurekaClient public class FileApplication { public static void main (String[] args) { SpringApplication.run(FileApplication.class ,args ) ; } }
文件信息封装 文件上传一般都有文件的名字、文件的内容、文件的扩展名、文件的md5值、文件的作者等相关属性,我们可以创建一个对象封装这些属性,代码如下
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 import lombok.AllArgsConstructor;import lombok.Data;import lombok.NoArgsConstructor;import java.io.Serializable;@Data @AllArgsConstructor @NoArgsConstructor public class FastDFSFile implements Serializable { private String name; private byte [] content; private String ext; private String md5; private String author; public FastDFSFile (String name, byte [] content, String ext) { this .name = name; this .content = content; this .ext = ext; } }
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 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 package com.changgou.file.utils;import com.changgou.file.FastDFSFile;import org.csource.common.NameValuePair;import org.csource.fastdfs.*;import org.slf4j.LoggerFactory;import org.springframework.core.io.ClassPathResource;import java.io.ByteArrayInputStream;import java.io.IOException;import java.io.InputStream;public class FastDFSClient { private static org.slf4j.Logger logger = LoggerFactory.getLogger(FastDFSClient.class ) ; static { try { String filePath = new ClassPathResource("fdfs_client.conf" ).getFile().getAbsolutePath(); ClientGlobal.init(filePath); } catch (Exception e) { logger.error("FastDFS Client Init Fail!" ,e); } } public static String[] upload(FastDFSFile file) { NameValuePair[] meta_list = new NameValuePair[1 ]; meta_list[0 ] = new NameValuePair("author" , file.getAuthor()); String[] uploadResults = null ; StorageClient storageClient=null ; try { storageClient = getTrackerClient(); uploadResults = storageClient.upload_file(file.getContent(), file.getExt(), meta_list); } catch (Exception e) { logger.error("Exception when uploadind the file:" + file.getName(), e); } if (uploadResults == null && storageClient!=null ) { logger.error("upload file fail, error code:" + storageClient.getErrorCode()); } String groupName = uploadResults[0 ]; String remoteFileName = uploadResults[1 ]; return uploadResults; } public static FileInfo getFile (String groupName, String remoteFileName) { try { StorageClient storageClient = getTrackerClient(); return storageClient.get_file_info(groupName, remoteFileName); } catch (Exception e) { logger.error("Exception: Get File from Fast DFS failed" , e); } return null ; } public static InputStream downFile (String groupName, String remoteFileName) { try { StorageClient storageClient = getTrackerClient(); 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 ; } public static void deleteFile (String groupName, String remoteFileName) throws Exception { StorageClient storageClient = getTrackerClient(); int i = storageClient.delete_file(groupName, remoteFileName); } public static StorageServer[] getStoreStorages(String groupName) throws IOException { TrackerClient trackerClient = new TrackerClient(); TrackerServer trackerServer = trackerClient.getConnection(); return trackerClient.getStoreStorages(trackerServer, groupName); } public static ServerInfo[] getFetchStorages(String groupName, String remoteFileName) throws IOException { TrackerClient trackerClient = new TrackerClient(); TrackerServer trackerServer = trackerClient.getConnection(); return trackerClient.getFetchStorages(trackerServer, groupName, remoteFileName); } public static String getTrackerUrl () throws IOException { return "http://" +getTrackerServer().getInetSocketAddress().getHostString()+":" +ClientGlobal.getG_tracker_http_port()+"/" ; } private static StorageClient getTrackerClient () throws IOException { TrackerServer trackerServer = getTrackerServer(); StorageClient storageClient = new StorageClient(trackerServer, null ); return storageClient; } private static TrackerServer getTrackerServer () throws IOException { TrackerClient trackerClient = new TrackerClient(); TrackerServer trackerServer = trackerClient.getConnection(); return trackerServer; } }
在控制器中编写上传的功能 1 2 3 4 5 6 7 8 9 10 11 @RestController @RequestMapping ("/file" )@CrossOrigin public class FileController { @PostMapping ("/upload" ) public Result upload (@RequestParam(value = "file" ) MultipartFile file)throws Exception { FastDFSFile fastDFSFile = new FastDFSFile(file.getName(),file.getBytes(), StringUtils.getFilenameExtension(file.getOriginalFilename())); String[] groupAndPath = FastDFSClient.upload(fastDFSFile); return new Result(true , StatusCode.OK,"文件上传成功" ,FastDFSUtils.getTrackerUrl()+groupAndPath[0 ]+"/" +groupAndPath[1 ]); } }
测试文件上传
data中的地址值就是上传后文件的访问地址,直接访问即可
思考:文件上传到哪了? 分析地址信息:http://192.168.162.130:8080/group1/M00/00/00/wKiigl5g4RmAQQBnAAAq8ZibPHI365.jpg
文件在group1服务器
登录对应的storage服务器,执行以下命令查看storage的配置文件
1 $ cat /etc/fdfs/storage.conf
根据以下步骤定位文件路径
一个group服务器上可以有多个路径,可以写多个store_path_数字,这里store_path_0对应的就是M00,依次类推M11对应的就是store_path_11