FastDFS是一个轻量级分布式文件系统。能够对文件进行管理,功能包括:文件存储、文件同步、文件访问(文件上传、文件下载)等,并且能够集群部署,有高可用保障。相应的竞品有Ceph、TFS等。相比而言FastDFS对硬件的要求比较低,因此适合中小型公司。html
FastDFS服务端由两个重要部分组成:跟踪器(Tracker)和存储节点(Storage)。java
Tracker主要作调度工做,在访问上起负载均衡的做用。Tracker能够作集群部署,各个节点之间是平等的,客户端请求时采用轮询机制,某个Tracker不能提供服务时就换另外一个。Storage启动后会链接到Tracker Server告知本身的Group信息,造成映射关联,并采用心跳机制保持状态。
Storage存储节点负责文件的存储,Storage能够集群部署。nginx
Storage集群有如下特色:git
此章节根据资料整理,可能随着版本有所改变,这里只介绍大体的,以便了解整个运做流程。若是须要深刻研究,建议仍是以官方文档为标准。github
一,客户端请求会打到负载均衡层,到tracker server时,因为每一个server之间是对等的关系,因此能够任意选择一个tracker server。web
二,到storage层:tracker server接收到upload file请求时,会为该请求分配一个能够存储该文件的group。spring
分配group规则:docker
三,肯定group后,tracker会在group内选择一个storage server给客户端。apache
在group内选择storage server时规则:segmentfault
四,选择storage path:当分配好storage server后,客户端向storage发送写文件请求,storage将会为文件分配一个数据存储目录,支持规则以下:
五,生成File id:选定存储目录以后,storage会为文件生成一个File id。规则以下:
由storage server ip、文件建立时间、文件大小,文件crc32和一个随机数拼接而成,而后将这个二进制串进程base64编码,转换为可打印的字符串。
六,选择两级目录:每一个存储目录下有两级256 * 256的子目录,storage会按文件Field进行两次hash,路由到其中的一个目录,而后将文件以file id为文件名存储到该子目录下。
一个文件路径最终由以下组成:组名/磁盘/目录/文件名
七,客户端upload file成功后,会拿到一个storage生成的文件名,接下来客户端根据这个文件名便可访问到该文件。
下载流程以下:
一,选择tracker server:和upload file同样,在download file时随机选择tracker server。
二,选择group:tracker发送download请求给某个tracker,必须带上文件名信息,tracker从文件名中解析出group、大小、建立时间等信息,根据group信息获取对于的group。
三,选择storage server:从group中选择一个storage用来服务读请求。因为group内的文件同步时在后台异步进行的,因此有可能出如今读到的时候,文件尚未同步到某些storage server上,为了尽可能避免反问道这样的storage,tracker按照必定的规则选择group内可读的storage。
Storage还能够结合nginx的fastdfs-nginx-module提供http服务,以实现图片等预览功能。
这个部分这里不作介绍,后续可能单独写篇文章,由于我发现对fastDFS集群提供http服务仍是挺复杂,包括我下面找的docker镜像都不完善,主要是规划的问题,包括衍生的服务,缓存,以及对图片的处理(nginx+lua)这些,后续打算研究下,从新开源个docker构建镜像。
FastDFS安装方法网上有不少教程,这里很少讲,我建议使用docker来运行FastDFS,能够本身根据安装步骤构建本身的镜像。而后在须要的机器直接运行,后续扩容也方便,再启动一个storage容器就能够了。
详细版安装推荐篇文章:https://segmentfault.com/a/11...
我这里从github上找的一个别人构建好的镜像,能够直接使用。地址:https://github.com/luhuiguo/f...
使用方法也很简单
# 启动一个tracker服务器 docker run -dti --network=host --name tracker -v /var/fdfs/tracker:/var/fdfs luhuiguo/fastdfs tracker # 启动storage0 docker run -dti --network=host --name storage0 -e TRACKER_SERVER=10.1.5.85:22122 -v /var/fdfs/storage0:/var/fdfs luhuiguo/fastdfs storage # 再启动一个storage1 docker run -dti --network=host --name storage1 -e TRACKER_SERVER=10.1.5.85:22122 -v /var/fdfs/storage1:/var/fdfs luhuiguo/fastdfs storage # 启动一个新组的storage docker run -dti --network=host --name storage2 -e TRACKER_SERVER=10.1.5.85:22122 -e GROUP_NAME=group2 -e PORT=22222 -v /var/fdfs/storage2:/var/fdfs luhuiguo/fastdfs storage
1,原github地址上的usage介绍,启动storage0和storage1有一个参数错误(多一个-e),以我上面发的命令为准。
2,这里的TRACKER_SERVER注意改成你本身的,同一个网段内网ip。
3,实际上这里docker容器之间仍是同一个物理主机上部署的(根据network而言),虽而后续能够经过加硬盘,而后新建storage绑定到新加硬盘mount上,可是若是是大公司的生产环境仍是推荐创建一个overlay网络,具体见:https://www.cnblogs.com/bigbe...,这样能够直接扩物理机集群了。另外这里也提供docker-compose方式启动服务,实际也不推荐使用,由于tracker和storage server之后必然是分开的,因此仍是推荐单个docker容器保持灵活性。这里高级点能够用k8s进行自动扩容(后续打算从新开源个镜像)。
这里使用官方的客户端包:https://github.com/happyfish1...
# 下载源码 git clone https://github.com/happyfish100/fastdfs-client-java.git cd fastdfs-client-java # 打jar包 mvn clean install # 输出目录 cd target # 导入到本地仓库 注意这里version根据实际生成的来 mvn install:install-file -DgroupId=org.csource -DartifactId=fastdfs-client-java -Dversion=1.27-SNAPSHOT -Dpackaging=jar -Dfile=fastdfs-client-java-1.27-SNAPSHOT.jar
<dependency> <groupId>org.csource</groupId> <artifactId>fastdfs-client-java</artifactId> <version>1.27-SNAPSHOT</version> </dependency> <dependency> <groupId>commons-fileupload</groupId> <artifactId>commons-fileupload</artifactId> <version>1.3.1</version> </dependency> <dependency> <groupId>commons-io</groupId> <artifactId>commons-io</artifactId> <version>2.2</version> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <version>3.1</version> </dependency>
在resource目录下,添加conf/fdfs_client.conf配置文件
connect_timeout = 2 network_timeout = 30 charset = UTF-8 http.tracker_http_port = 80 http.anti_steal_token = no http.secret_key = FastDFS1234567890 tracker_server = 192.168.1.163:22122
测试时实际上只需关注tracker_server,而且改成你本身的tracker server
applicationContext.xml配置中添加文件上传bean
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"> <property name="maxUploadSize" value="62914560" /> <property name="defaultEncoding" value="UTF-8" /> </bean>
建一个简单的client封装(勿做生产使用)
FastDFSClient.java
package com.rootrl.fastDFSDemo.utiles; import org.apache.commons.lang3.StringUtils; import org.csource.common.NameValuePair; import org.csource.fastdfs.*; import java.io.File; import java.io.IOException; import java.io.InputStream; public class FastDFSClient { private static StorageClient1 storageClient1 = null; static { try { // 获取配置文件 String classPath = new File(FastDFSClient.class.getResource("/").getFile()).getCanonicalPath(); String CONF_FILENAME = classPath + File.separator + "conf" + File.separator + "fdfs_client.conf"; ClientGlobal.init(CONF_FILENAME); // 获取触发器 TrackerClient trackerClient = new TrackerClient(ClientGlobal.g_tracker_group); TrackerServer trackerServer = trackerClient.getConnection(); // 获取存储服务器 StorageServer storageServer = trackerClient.getStoreStorage(trackerServer); storageClient1 = new StorageClient1(trackerServer, storageServer); } catch (Exception e) { System.out.println(e); } } /** * 上传文件 * @param fis 文件输入流 * @param fileName 文件名称 * @return */ public static String uploadFile(InputStream fis, String fileName) { try { NameValuePair[] meta_list = null; //将输入流写入file_buff数组 byte[] file_buff = null; if (fis != null) { int len = fis.available(); file_buff = new byte[len]; fis.read(file_buff); } String fileid = storageClient1.upload_file1(file_buff, getFileExt(fileName), meta_list); return fileid; } catch (Exception ex) { return null; } finally { if (fis != null) { try { fis.close(); } catch (IOException e) { System.out.println(e); } } } } /** * 获取文件后缀 * @param fileName * @return */ private static String getFileExt(String fileName) { if (StringUtils.isBlank(fileName) || !fileName.contains(".")) { return ""; } else { return fileName.substring(fileName.lastIndexOf(".") + 1); } } }
而后创建一个File控制器,作测试用
FileController.java
package com.rootrl.fastDFSDemo.controller; import com.rootrl.fastDFSDemo.utiles.FastDFSClient; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.multipart.MultipartFile; @Controller @RequestMapping("fastdfs") public class FileController { @RequestMapping(value = "upload") @ResponseBody public String uploadFileSample(@RequestParam MultipartFile file){ try { String fileId = FastDFSClient.uploadFile(file.getInputStream(), file.getOriginalFilename()); return fileId; } catch (Exception e) { System.out.println(e.getMessage()); return "error"; } } }
而后使用postman客户端测试,url为:http://localhost:8080/fastdfs/upload.do(依据本身实际状况变动)
注意postman使用post请求,而后切换到body/form-data标签项,添加一个Key为file,类型为file,而后value就能够上传文件了。成功会返回文件id,相似:group1/M00/00/00/wKgBo1zjxnOAT-k1AAAoMlb3hzU996.png