一、FastDFS 概述
1.1 什么是 FastDFS
FastDFS 是一个开源的轻量级分布式文件系统,它用于对文件进行管理,功能包括:文件存储、文件同步、文件访问(上传和下载)等,解决了大容量存储和负载均衡的问题。适合以文件为载体的在线服务,如相册网站、视频网站等。
由于 FastDFS 充分考虑了冗余备份、负载均衡、线性扩容等机制,并注重高可用、高性能等指标,使用 FastDFS 能很容易地搭建一套高性能的文件服务器集群提供文件的上传、下载等服务。
1.2 FastDFS 工作流程
1.2.1 组件
Tracker:负责文件管理、负载均衡操作,即控制中心(注册中心)
Storage:负责文件操作,文件上传、下载、修改、删除等
1.2.2 工作流程
二、FastDFS 安装
拉取镜像
# 启动 docker
service docker start# 拉取 fastdfs 镜像
docker pull morunchang/fastdfs
删掉已有的 tracker 镜像和 storage 镜像
# 显示所有容器(包括未运行)
docker ps -a# 停止 tracker 容器 和 storage 容器
docker stop tracker
docker stop storage# 删除对应容器
docker rm tracker
docker rm storage
重新生成容器
# 生成 tracker 容器
docker run -d --name tracker --net=host morunchang/fastdfs sh tracker.sh# 生成 storage 容器
docker run -d --name storage --net=host -e TRACKER_IP=192.168.47.142:22122 -e GROUP_NAME=group1 morunchang/fastdfs sh storage.sh[-d]:后台运行
[-name]:容器名称
[--net=host]:表示使用的网络模式是net模式
[TRACKER_IP]:表示 tracker 的ip
[GROUP_NAME]:组名
生成成功
三、修改 Nginx 配置
在安装 tracker 和 storage 时,已经自动安装好了 nginx,故无需再安装 nginx
先进入nginx配置文件目录
# 运行 tracker
docker exec -it storage /bin/bash# 进入nginx
cd /etc/nginx/conf
查看配置文件
vim nginx.conf
存在默认配置:
添加禁止缓存
location ~ /M00 {# 配置为不要缓存add_header Cache-control no-store;root /data/fast_data/data;ngx_fastdfs_module;
}
或其他相关配置,可访问:
Nginx下关于缓存控制字段cache-control的配置说明 - 运维小结
四、SpringBoot 项目中使用 FastDFS
4.1 代码构建
4.1.1 加入依赖
<dependencies><dependency><groupId>org.csource</groupId><artifactId>fastdfs-client-java</artifactId><version>1.27-SNAPSHOT</version></dependency>
</dependencies>
4.1.2 创建配置文件
/resources/fdfs_client.conf
connect_timeout = 60 # 连接超时时间
network_timeout = 60 # 网络超时时间
charset = UTF‐8 #字符集编码# tracker 的 Http 请求端口
http.tracker_http_port = 8080# tracker 的 TCP 通信端口
tracker_server = 192.168.47.142:22122
创建核心配置文件:/resources/application.yml
spring:application:name: fileservlet:multipart:max‐file‐size: 10MBmax‐request‐size: 10MB
server:port: 18082
eureka:client:service‐url:defaultZone: http://127.0.0.1:7001/eurekainstance:prefer‐ip‐address: true
feign:hystrix:enabled: true
4.1.3 编写启动类
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;@SpringBootApplication(exclude = DataSourceAutoConfiguration.class) // 排除掉数据库自动加载
@EnableEurekaClient
public class FileApplication {public static void main(String[] args) {SpringApplication.run(FileApplication.class);}
}
4.1.4 编写 util 文件
import lombok.extern.slf4j.Slf4j;
import org.csource.common.MyException;
import org.csource.common.NameValuePair;
import org.csource.fastdfs.*;
import org.springframework.core.io.ClassPathResource;import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.text.SimpleDateFormat;
import java.util.Date;/*** 实现 FastDFS 文件管理*/
@Slf4j
public class FastDFSUtil {/*** 加载 Tracker 连接信息*/static {try {// 查找 classpath 下的文件路径String filename = new ClassPathResource("fdfs_client.conf").getPath();// 加载 Tracker 链接信息ClientGlobal.init(filename);} catch (IOException e) {e.printStackTrace();} catch (MyException e) {e.printStackTrace();}}/**** @return* @throws Exception*/public static TrackerServer getTrackerServer() throws Exception {// 创建一个 Tracker 访问的客户端对象 TrackerClientTrackerClient trackerClient = new TrackerClient();// 通过 TrackerClient 访问 TrackerServer 服务,获取连接信息TrackerServer trackerServer = trackerClient.getConnection();return trackerServer;}public static StorageClient getStorageClient(TrackerServer trackerServer) {return new StorageClient(trackerServer, null);}/*** 文件上传* @param fastDFSFile 上传的文件信息封装* @return*/public static String[] upload(FastDFSFile fastDFSFile) throws Exception{// 通过 TrackerClient 访问 TrackerServer 服务,获取连接信息TrackerServer trackerServer = getTrackerServer();// 通过 TrackerServer 的连接信息可以获取 Storage 的连接信息,创建 StorageClient 对象存储 Storage 的连接信息StorageClient storageClient = getStorageClient(trackerServer);///*** 通过 StorageClient 访问 Storage,实现文件上传,并获取文件上传后的存储信息* 1. 上传文件的字节数组* 2. 文件的扩展名* 3. 附加参数,如地址、时间等*/// 附加参数NameValuePair[] meta_list = new NameValuePair[3];meta_list[0] = new NameValuePair("地址", "北京");Date date = new Date();SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");meta_list[1] = new NameValuePair("时间", sdf.format(date));meta_list[2] = new NameValuePair("作者", fastDFSFile.getAuthor());String[] uploads = storageClient.upload_file(fastDFSFile.getContent(), fastDFSFile.getExt(), meta_list);return uploads;}public static FileInfo getFileInfo(String groupName, String remoteFileName) throws Exception {// 通过 TrackerClient 访问 TrackerServer 服务,获取连接信息TrackerServer trackerServer = getTrackerServer();StorageClient storageClient = getStorageClient(trackerServer);return storageClient.get_file_info(groupName, remoteFileName);}/*** 文件下载* @param groupName* @param remoteFileName* @return* @throws Exception*/public static InputStream downFile(String groupName, String remoteFileName) throws Exception {// 通过 TrackerClient 访问 TrackerServer 服务,获取连接信息TrackerServer trackerServer = getTrackerServer();StorageClient storageClient = getStorageClient(trackerServer);byte[] buffer = storageClient.download_file(groupName, remoteFileName);return new ByteArrayInputStream(buffer);}/*** 删除文件* @param groupName* @param remoteFileName* @return* @throws Exception*/public static void deleteFile(String groupName, String remoteFileName) throws Exception {// 通过 TrackerClient 访问 TrackerServer 服务,获取连接信息TrackerServer trackerServer = getTrackerServer();StorageClient storageClient = getStorageClient(trackerServer);storageClient.delete_file(groupName, remoteFileName);}/*** 获取 storage 的信息* @return* @throws Exception*/public static StorageServer getStorage() throws Exception {// 创建一个 TrackerClient 对象,通过 TrackerClient 对象访问 TrackerServerTrackerClient trackerClient = new TrackerClient();TrackerServer trackerServer = trackerClient.getConnection();StorageClient storageClient = getStorageClient(trackerServer);return trackerClient.getStoreStorage(trackerServer);}/*** 获取 storage 数组的信息* @param groupName* @param remoteFileName* @return* @throws Exception*/public static ServerInfo[] getServerInfo(String groupName, String remoteFileName) throws Exception {// 创建一个 TrackerClient 对象,通过 TrackerClient 对象访问 TrackerServerTrackerClient trackerClient = new TrackerClient();TrackerServer trackerServer = trackerClient.getConnection();return trackerClient.getFetchStorages(trackerServer, groupName, remoteFileName);}public static String getTrackerInfo() throws Exception {TrackerServer trackerServer = getTrackerServer();String ip = trackerServer.getInetSocketAddress().getHostString();int tracker_http_port = ClientGlobal.getG_tracker_http_port();return "http://" + ip + ":" + tracker_http_port;}public static void main(String[] args) throws Exception {
// // 文件上传
// FileInfo fileInfo = getFileInfo("group1", "M00/00/00/wKgvjl7NvxmAJIzHAAGfCM0GYss770.jpg");
// if (fileInfo == null) {
// log.info("文件不存在");
// return ;
// }
// log.info("ip = " + fileInfo.getSourceIpAddr());
// log.info("size = " + fileInfo.getFileSize());// // 文件下载
// InputStream in = downFile("group1", "M00/00/00/wKgvjl7NvxmAJIzHAAGfCM0GYss770.jpg");
// // 将文件写入本地磁盘
// FileOutputStream os = new FileOutputStream("E:/study/project/backupFile/1.jpg");
// // 定义一个缓冲区
// byte[] buffer = new byte[1024];
// while (in.read(buffer) != -1) {
// os.write(buffer);
// }
// os.flush();
// os.close();
// in.close();// // 删除文件
// deleteFile("group1", "M00/00/00/wKgvjl7NwZqAYOkMAAGfCM0GYss913.jpg");
// log.info("删除成功");
// // 获取 storage 信息
// StorageServer storageServer = getStorage();
// log.info("pathIndex = " + storageServer.getStorePathIndex());
// log.info("ip = " + storageServer.getInetSocketAddress().getAddress());
// log.info("host = " + storageServer.getInetSocketAddress().getHostName());
// log.info("port = " + storageServer.getInetSocketAddress().getPort());// // 获取 storage 组的IP和端口信息
// ServerInfo[] groups = getServerInfo("group1", "/M00/00/00/wKgvjl7Nz_yAVmr9AAGfCM0GYss556.jpg");
// for (ServerInfo server : groups) {
// log.info("addr = " + server.getIpAddr());
// log.info("port = " + server.getPort());
// }log.info(getTrackerInfo());}
}
/*** 封装文件上传信息*/
@Data
public class FastDFSFile implements Serializable {private static final long serialVersionUID = 1L;// 文件名private String name;// 文件内容private byte[] content;// 文件扩展名private String ext;// 文件MD5摘要值private String md5;// 文件创建作者private String author;public FastDFSFile(String name, byte[] content, String ext) {this.name = name;this.content = content;this.ext = ext;}public FastDFSFile(String name, byte[] content, String ext, String md5, String author) {this.name = name;this.content = content;this.ext = ext;this.md5 = md5;this.author = author;}
}
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;@RestController
@RequestMapping("/upload")
@CrossOrigin
public class FileUploadController {@PostMappingpublic Result upload(@RequestParam(value = "file")MultipartFile file) throws Exception {FastDFSFile fastDFSFile = new FastDFSFile(file.getOriginalFilename(),file.getBytes(),StringUtils.getFilenameExtension(file.getOriginalFilename()));// 调用 FastDFSUtil 工具类将文件传入到 FastDFS 中String[] uploads = FastDFSUtil.upload(fastDFSFile);String url = FastDFSUtil.getTrackerInfo() + "/" + uploads[0] + "/" + uploads[1];return new Result(true, StatusCode.OK, "上传成功", url);}
}
4.2 文件操作
4.2.1 文件上传
由于文件项目的端口为 18082,故需要使用 POST 请求 18082 端口
或是代码上传:
public static void main(String[] args) throws Exception {// 文件上传FileInfo fileInfo = getFileInfo("group1", "M00/00/00/wKgvjl7NvxmAJIzHAAGfCM0GYss770.jpg");if (fileInfo == null) {log.info("文件不存在");return ;}log.info("ip = " + fileInfo.getSourceIpAddr());log.info("size = " + fileInfo.getFileSize());
}
4.2.2 文件访问
【storage地址】:【nginx端口】/【组名】/M00/【地址】例如:
http://192.168.47.142:8080/group1/M00/00/00/wKgvjl7NvxmAJIzHAAGfCM0GYss770.jpg
4.2.3 文件下载
public static void main(String[] args) throws Exception {// 文件下载InputStream in = downFile("group1", "M00/00/00/wKgvjl7NvxmAJIzHAAGfCM0GYss770.jpg");// 将文件写入本地磁盘FileOutputStream os = new FileOutputStream("E:/study/project/backupFile/1.jpg");// 定义一个缓冲区byte[] buffer = new byte[1024];while (in.read(buffer) != -1) {os.write(buffer);}os.flush();os.close();in.close();
}
4.2.4 文件删除
public static void main(String[] args) throws Exception {deleteFile("group1", "M00/00/00/wKgvjl7NwZqAYOkMAAGfCM0GYss913.jpg");log.info("删除成功");
}