FastDFS简介
FastDFS为互联网量身定制,充分考虑了冗余备份、负载均衡、线性扩容等机制,并注重高可用、高性能等指标,使用FastDFS很容易搭建一套高性能的文件服务器集群提供文件上传、下载等服务。
FastDFS服务端有两个角色:跟踪器(tracker)
和存储节点(storage)
。
跟踪器主要负责调度工作,在访问上起负载均衡的作用。
存储节点存储文件,完成文件管理的所有功能,同时FastDFS同时对文件的metaData(文件属性列表)进行管理。所谓文件的metaData就是文件的相关属性,以键值对(key value pair)方式表示,如:width=1024,其中的key为width,value为1024。文件meta data是文件属性列表,可以包含多个键值对。
tracker和storage都可以由一台或多台服务器构成。tracker和storage中的服务器均可以随时增加或下线而不影响线上服务。其中tracker中的所有服务器都是对等的,可以根据服务器的压力情况随时增加或减少。
为了支持大容量,storage采用了分卷(或分组)的组织方式。存储系统由一个或多个卷组成,卷与卷之间的文件是相互独立的,所有卷的文件容量累加就是整个存储系统中的文件容量。一个卷可以由一台或多台存储服务器组成,一个卷下的存储服务器中的文件都是相同的,卷中的多台存储服务器起到了冗余备份和负载均衡的作用。
在卷中增加服务器时,同步已有的文件由系统自动完成,同步完成后,系统自动将新增服务器切换到线上提供服务。
当存储空间不足或即将耗尽时,可以动态添加卷。只需要增加一台或多台服务器,并将他们配置为一个新的组,这样就扩大了存储的容量。
FastDFS中的文件标识分为两个部分:组名和文件名。二者缺一不可。
FastDFS原理
存储节点采用了分组(group)的方式。存储系统由一个或多个group组成,group与group之间的文件是相互独立的,所有group的文件容量累加就是整个存储系统中的文件容量。
一个group可以由一台或多台存储服务器组成,一个group下的存储服务器中的文件都是相同的,group中的多台存储服务器起到了冗余备份和负载均衡的作用(一个组的存储容量为该组内存储服务器容量最小的那个,不同组的Storage server之间不会相互通信,同组内的Storage server之间会相互连接进行文件同步)。
工作流程
文件上传
Client会先想Tracker询问存储地址,Tracker查询到存储地址后返回给Client,Client拿着地址直接和对应的Storage通信,将文件上传到storage。
文件下载
同样,Client会向Tracker询问地址,并带上要查询的文件名和组名,tracker查询后会将地址返回给Client,client拿到地址和指定Storage通讯并下载文件。
使用docker安装FastDFS
- 拉取镜像:
docker pull season/fastdfs
- 创建tracker容器:
docker run -it -d --name tracker -v /home/iie4bu/docker/fastDFS/tracker:/fastdfs/tracker/data --net=host season/fastdfs tracker
默认端口是22122
- 创建storage容器:
docker run -itd --name storage -v /home/iie4bu/docker/fastDFS/storage:/fastdfs/storage/data -v /home/iie4bu/docker/fastDFS/store_path:/fastdfs/store_path --net=host -e TRACKER_SERVER:192.168.171.25(本机IP):22122 -e GROUP_NAME=group1 season/fastdfs storage
- 此时两个服务都以启动,进行服务的配置。
进入storage容器,到storage
的配置文件中配置http访问的端口,配置文件在fdfs_conf
目录下的storage.conf
。将文件拷贝出来进行修改:docker cp storage:/fdfs_conf/storage.conf ~/
,修改storage.conf
文件中的tracker_server=你自己的宿主机ip:22122
将修改后的配置文件拷贝到storage的配置目录下:docker cp ~/storage.conf storage:/fdfs_conf/
重启storage容器:docker container restart storage
- 查看tracker容器和storage容器的关联
[root@localhost ~]# docker exec -it storage bash
root@localhost:/# cd fdfs_conf
root@localhost:/fdfs_conf# fdfs_monitor storage.conf
在docker模拟客户端上传文件到storage容器
开启一个客户端:docker run -tid --name fdfs_sh --net=host season/fastdfs sh
将之前的storage.conf
文件拷贝到容器fdfs_sh:/fdfs_conf/
目录下:docker cp ~/storage.conf fdfs_sh:/fdfs_conf/
创建一个txt文件:
[root@localhost 00]# docker exec -it fdfs_sh bash
root@localhost:/# echo hello>a.txt
进入fdfs_conf目录,并将文件上传到storage容器:
root@localhost:/# cd fdfs_conf
root@localhost:/fdfs_conf# fdfs_upload_file storage.conf /a.txt
返回路径:
然后去我们对应的宿主机挂载文件夹下查看:
FastDFS配合使用Nginx
使用delron/fastdfs
镜像,这个镜像中自带Nginx服务。
- 首先拉取镜像:
docker pull delron/fastdfs
- 开启tracker 服务:
docker run -it -d --network=host --name tracker -v ~/docker/fastdfs/tracker/:/var/fdfs delron/fastdfs tracker
。将fastDFS tracker运行目录映射到本机的~/docker/fastdfs/tracker/
目录中。 - 开启storage服务:
docker run -it -d --network=host --name storage -e TRACKER_SERVER=192.168.1.5(本机IP):22122 -v ~/docker/fastdfs/storage/:/var/fdfs delron/fastdfs storage
查看docker服务:
正常启动了两个服务。
- 端口8888是默认的nginx代理端口
- 端口23000是storage服务端口
- 端口22122是tracker服务端口
配置Storage
进入storage容器,到storage的配置文件中配置http访问的端口,配置文件在/etc/fdfs
目录下的storage.conf。
默认的端口是8888,可以不修改
配置Nginx
在/usr/local/nginx
目录下,修改nginx.conf
文件
默认配置如下:(这里可以不用修改)
开启一个客户端容器
docker run -it -d --network=host --name fdfs_sh -e TRACKER_SERVER=192.168.1.5:22122 delron/fastdfs sh
然后上传一个文件到fdfs_sh
容器中:docker cp timg.jpg fdfs_sh:/
然后进入fdfs_sh
容器:docker exec -it fdfs_sh bash
上传文件:fdfs_upload_file /etc/fdfs/client.conf timg.jpg
返回信息如下:
[root@manager /]# fdfs_upload_file /etc/fdfs/client.conf timg.jpg
group1/M00/00/00/wKirGV6yybOAdf9TAABjtCKBG50922.jpg
在浏览器中输入:ip:8088/group1/M00/00/00/wKirGV6yybOAdf9TAABjtCKBG50922.jpg
就可以看到这张图片了。
Springboot代码实现上传与下载
添加依赖包pom.xml内容如下:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.1.4.RELEASE</version>
<scope>import</scope>
<type>pom</type>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.github.tobato</groupId>
<artifactId>fastdfs-client</artifactId>
<version>1.27.2</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.68</version>
</dependency>
</dependencies>
配置文件application.yaml内容如下:
fdfs:
soTimeout: 1500 #socket连接超时时长
connectTimeout: 600 #连接tracker服务器超时时长
reqHost: manager #nginx访问地址
reqPort: 8888 #nginx访问端口
thumbImage: #缩略图生成参数,可选
width: 150
height: 150
trackerList: #TrackerList参数,支持多个,我这里只有一个,如果有多个在下方加- x.x.x.x:port
- manager:22122
spring:
servlet:
multipart:
max-request-size: 500MB
max-file-size: 500MB
server:
port: 8080
添加配置文件FdfsConfiguration.java
:
package cn.ac.iie.config;
import com.github.tobato.fastdfs.FdfsClientConfig;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableMBeanExport;
import org.springframework.context.annotation.Import;
import org.springframework.jmx.support.RegistrationPolicy;
@Configuration
@Import(FdfsClientConfig.class) // 导入FastDFS-Client组件
@EnableMBeanExport(registration = RegistrationPolicy.IGNORE_EXISTING) // 解决jmx重复注册bean的问题
public class FdfsConfiguration {
}
工具类FastDFSClientUtil.java
:
package cn.ac.iie.utils;
import com.github.tobato.fastdfs.domain.fdfs.StorePath;
import com.github.tobato.fastdfs.domain.fdfs.ThumbImageConfig;
import com.github.tobato.fastdfs.domain.proto.storage.DownloadCallback;
import com.github.tobato.fastdfs.service.FastFileStorageClient;
import org.apache.commons.io.FilenameUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.io.InputStream;
@Component
public class FastDFSClientUtil {
@Value("${fdfs.reqHost}")
private String reqHost;
@Value("${fdfs.reqPort}")
private String reqPort;
@Autowired
private FastFileStorageClient storageClient;
@Autowired
private ThumbImageConfig thumbImageConfig; //创建缩略图 , 缩略图访问有问题,暂未解决
public String uploadFile(MultipartFile file) throws IOException {
StorePath storePath = storageClient.uploadFile(file.getInputStream(),file.getSize(), FilenameUtils.getExtension(file.getOriginalFilename()),null);
String path = thumbImageConfig.getThumbImagePath(storePath.getPath()) ;
System.out.println("thumbImage :" + path); // 缩略图访问有问题,暂未解决
System.out.println(storePath);
return getResAccessUrl(storePath);
}
public void delFile(String filePath) {
storageClient.deleteFile(filePath);
}
public InputStream download(String groupName, String path ) {
InputStream ins = storageClient.downloadFile(groupName, path, new DownloadCallback<InputStream>(){
public InputStream recv(InputStream ins) throws IOException {
// 将此ins返回给上面的ins
return ins;
}}) ;
return ins ;
}
/**
* 封装文件完整URL地址
* @param storePath
* @return
*/
private String getResAccessUrl(StorePath storePath) {
System.out.println(storePath.getFullPath());
String fileUrl = "http://" + reqHost + ":" + reqPort + "/" + storePath.getFullPath();
return fileUrl;
}
}
controller:
package cn.ac.iie.controller;
import cn.ac.iie.utils.FastDFSClientUtil;
import com.alibaba.fastjson.JSONObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import org.apache.commons.io.IOUtils;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
@RestController
public class UploadController {
@Autowired
private FastDFSClientUtil dfsClient;
@PostMapping("/upload")
public JSONObject fdfsUpload(@RequestParam("file") MultipartFile file, HttpServletRequest request) {
JSONObject jsonObject = new JSONObject();
try {
String fileUrl = dfsClient.uploadFile(file);
jsonObject.put("url", fileUrl);
} catch (IOException e) {
e.printStackTrace();
}
return jsonObject;
}
/*
* http://localhost:8080/download?filePath=group1/M00/00/00/wKgIZVzZEF2ATP08ABC9j8AnNSs744.jpg
*/
@RequestMapping("/download")
public void download(String filePath , HttpServletRequest request , HttpServletResponse response) throws IOException {
// group1/M00/00/00/wKgIZVzZEF2ATP08ABC9j8AnNSs744.jpg
String[] paths = filePath.split("/");
String groupName = null;
for (String item : paths) {
if (item.indexOf("group") != -1) {
groupName = item;
break ;
}
}
String path = filePath.substring(filePath.indexOf(groupName) + groupName.length() + 1, filePath.length());
InputStream input = dfsClient.download(groupName, path);
//根据文件名获取 MIME 类型
String fileName = paths[paths.length-1] ;
System.out.println("fileName :" + fileName); // wKgIZVzZEF2ATP08ABC9j8AnNSs744.jpg
String contentType = request.getServletContext().getMimeType(fileName);
String contentDisposition = "attachment;filename=" + fileName;
// 设置头
response.setHeader("Content-Type",contentType);
response.setHeader("Content-Disposition",contentDisposition);
// 获取绑定了客户端的流
ServletOutputStream output = response.getOutputStream();
// 把输入流中的数据写入到输出流中
IOUtils.copy(input,output);
input.close();
}
@RequestMapping("/deleteFile")
public String delFile(String filePath , HttpServletRequest request ,HttpServletResponse response) {
try {
dfsClient.delFile(filePath);
} catch(Exception e) {
// 文件不存在报异常 : com.github.tobato.fastdfs.exception.FdfsServerException: 错误码:2,错误信息:找不到节点或文件
// e.printStackTrace();
}
request.setAttribute("msg", "成功删除,'" + filePath);
return "index";
}
}