一. 前言
最近有很多小伙伴问壹哥,大规模的文件存储该怎么做? 这个感觉很难实现呢。其实这个需求,并没有大家想的那么难以实现。今天壹哥就带着各位,用10分钟的时间来搞定这个需求。不信?那你就跟着壹哥一步步来,10分钟绝对带你学会。
二. 浅析FastDFS分布式文件系统
FastDFS是一个开源的轻量级分布式文件系统,它特别适合海量的文件存储,具有文件上传、下载、文件同步和删除等功能。还可以存储各种类型文件,例如文本文件、声音文件、视频文件、图片文件等,可以用来做相册网站,还可以在电商网站中保存商品图片,在贴吧网站保存文章中的插图等。
FastDFS****特点:
支持高并发存储,对高并发文件存储有负载均衡的功能;
同时支持存储容量的水平扩展,可以无限扩展增大存储容量;
支持容灾功能,因为内部有冗余备份功能,防止文件丢失;
高可用,管理端和存储端都有心跳检测功能。
不要惊讶,它就是这么强大****!!!
三. FastDFS执行流程和原理
接下来壹哥就给大家解读FastDFS流程图中展示的执行流程和原理****:
FastDFS分为客户端client(我们的项目),tracker管理端(管理存储端服务器的,不存储文件),storage存储端(存储文件) 共三部分。
首先管理端tracker不存储具体的文件,它用来管理storage存储端集群服务器。tracker可以是一主多从,备机每隔一段时间ping主机,主机返回pong命令,具有心跳检测机制。如果主机宕机,不返回pong命令,那么备机会替代主机工作。请求进入靠tracker管理端分配,具体将文件存储到哪一个storage存储端服务器,具有负载均衡功能。
storage存储端服务器,是两台为一组,一台主机和一台备机。存储时向主机从存储,然后主机将文件同步到备机保存一份,冗余存储防止文件丢失。主机和备机之间也有心跳检测机制,防止主机宕机,高可用。存储端服务器可以水平无限扩展,这样理论上fastDFS分布式文件系统可以存储的文件大小是无限大。
客户端如果想要存储,发送请求到tracker管理端,管理端会返回给客户端一个存储端服务器的ip和端口,然后客户端向具体的存储端服务器中存储,存储后返回一个存储后的路径,并且文件会被自动重命名,防止文件重名。
四. FastDFS安装部署
壹哥在这里使用Docker容器化来搭建FastDFS。
4.1****拉取镜像
docker pull morunchang/fastdfs
4.2****运行tracker
创建FastDFS管理端tracker容器。
docker run -d --name tracker --net=host morunchang/fastdfs sh tracker.sh
4.3****运行storage
创建FastDFS存储端storage容器。
# 命令语法
docker run -d --name storage --net=host -e TRACKER_IP=<your tracker server address>:22122 -e GROUP_NAME=<group name> morunchang/fastdfs sh storage.sh
# 创建storage容器例子:
docker run -d --name storage --net=host -e TRACKER_IP=192.168.200.128:22122 -e GROUP_NAME=group1 morunchang/fastdfs sh storage.sh
使用的网络模式是–net=host;
其中<your tracker server address> 位置替换为你机器的ip地址即可;
其中<group name> 是组名,即storage的组,例如: group1、group2、group3等;
如果想要增加新的storage服务器,再次运行该命令,注意更换新组名即可。
*4.4***修改nginx的配置 **
进入storage的容器内部,修改nginx.conf。
# 进入到storage容器内部 docker exec -it storage /bin/bash
进入到容器内部后。
#1. 通过命令来查询Nginx的安装位置:
root@iZ8vb6w2xyjemtqcxtmaj4Z:/# whereis nginx
#显示如下:
nginx: /etc/nginx
#2. 查看当前Nginx的进程
root@iZ8vb6w2xyjemtqcxtmaj4Z:/# ps aux | grep nginx
#显示如下:
root 16 0.0 0.0 32480 1480 ? Ss 13:18 0:00 nginx: master process /etc/nginx/sbin/nginx
nobody 100 0.0 0.0 33036 2116 ? S 14:15 0:00 nginx: worker process
root 118 0.0 0.0 11272 728 pts/1 S+ 14:54 0:00 grep --color=auto nginx
在storage存储端容器的nginx中添加以下内容:
#3. 修改Nginx的配置文件
vi /etc/nginx/conf/nginx.conf
#4. 修改Nginx配置内容
server {
listen 80;
server_name localhost;
location ~ /M00 {
# storage 实际存储图片的位置
root /data/fast_data/data;
ngx_fastdfs_module;
}
}
#5. 进入到Nginx sbin目录从新加载Nginx配置文件
cd /etc/nginx/sbin
#6. 重新加载配置文件, 让nginx配置生效
./nginx -s reload
修改后:
storage存储的位置/data/fast_data/data。
4.5设置开机启动容器
docker update --restart=always tracker docker update --restart=always storage
五. 代码实现
5.1****创建文件管理微服务
创建文件管理微服务fastdfsDemo,该工程主要用于实现文件上传以及文件删除等功能。创建微服务时,项目为Maven项目,不要选择骨架。
5.2****修改pom.xml引入依赖
<!-- 继承Spring boot工程 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.5.RELEASE</version>
</parent>
<properties>
<!-- 项目源码及编译输出的编码 -->
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<!-- 项目编译JDK版本 -->
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<!-- 依赖包版本管理 -->
<spring.boot.version>2.1.5.RELEASE</spring.boot.version>
<fastdfs.client.version>1.27.0.0</fastdfs.client.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>${spring.boot.version}</version>
</dependency>
<dependency>
<groupId>net.oschina.zcx7878</groupId>
<artifactId>fastdfs-client-java</artifactId>
<version>${fastdfs.client.version}</version>
</dependency>
</dependencies>
5.3****创建fasfDFS的配置文件
在resources文件夹下创建fasfDFS的配置文件fdfs_client.conf。
connect_timeout = 60
network_timeout = 60
charset = UTF-8
http.tracker_http_port = 80
tracker_server = 192.168.200.128:22122
connect_timeout:连接超时时间,单位为秒;
network_timeout:通信超时时间,单位为秒。发送或接收数据时。假设在超时时间后还不能发送或接收数据,则本次网络通信失败;
charset: 字符集;
http.tracker_http_port :.tracker的http端口;
tracker_server:tracker服务器IP和端口设置。
5.4****创建微服务配置文件
在resources文件夹下创建application.yml。
spring:
servlet:
multipart:
max-file-size: 10MB
max-request-size: 10MB
application:
name: fastdfs-demo
server:
port: 9001
max-file-size是单个文件大小,max-request-size是设置总上传的数据大小。
**创建启动类 **
创建com.qianfeng包,创建启动类FastDFSApplication。
package com.qianfeng;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* 项目启动类
* @Author 千锋壹哥
*/
@SpringBootApplication
public class FastDFSApplication {
public static void main(String[] args) {
SpringApplication.run(FastDFSApplication.class, args);
}
}
**5.5 文件上传 **
5.5.1 文件信息封装
文件上传一般都有文件的名字、文件的内容、文件的扩展名、文件的md5值、文件的作者等相关属性,我们可以创建一个对象封装这些属性。我们先创建com.qianfeng.pojo.FastDFSFile文件,代码如下:
package com.qianfeng.pojo;
/**
* 自定义封装, 文件实体类
* @Author 千锋壹哥
*/
public class FastDFSFile {
//文件名字
private String name;
//文件内容
private byte[] content;
//文件扩展名
private String ext;
//文件MD5摘要值
private String md5;
//文件创建作者
private String author;
public FastDFSFile(String name, byte[] content, String ext, String height, String width, String author) {
super();
this.name = name;
this.content = content;
this.ext = ext;
this.author = author;
}
public FastDFSFile(String name, byte[] content, String ext) {
super();
this.name = name;
this.content = content;
this.ext = ext;
}
// getter and setter ...
}
5.5.2 文件操作
创建FastDFSClient类,存放在com.qianfeng.util下,在该类中实现FastDFS信息获取以及文件的相关操作,代码如下:
package com.qianfeng.util;
import com.qianfeng.pojo.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;
/**
* 上传下载等文件操作工具类
* @Author 千锋壹哥
*/
public class FastDFSClient {
private static org.slf4j.Logger logger = LoggerFactory.getLogger(FastDFSClient.class);
/***
* 初始化加载FastDFS的TrackerServer配置
*/
static {
try {
String filePath = new ClassPathResource("fdfs_client.conf").getFile().getAbsolutePath();
ClientGlobal.init(filePath);
} catch (Exception e) {
logger.error("FastDFS Client Init Fail!",e);
}
}
/***
* 文件上传
* @param file
* @return
*/
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客户端对象
storageClient = getTrackerClient();
/***
* 文件上传
* 1)文件字节数组
* 2)文件扩展名
* 3)文件作者
*/
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;
}
/***
* 获取文件信息
* @param groupName:组名
* @param remoteFileName:文件存储完整名
* @return
*/
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;
}
/***
* 文件下载
* @param groupName
* @param remoteFileName
* @return
*/
public static InputStream downFile(String groupName, String remoteFileName) {
try {
//创建StorageClient
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;
}
/***
* 文件删除
* @param groupName
* @param remoteFileName
* @throws Exception
*/
public static void deleteFile(String groupName, String remoteFileName)
throws Exception {
//创建StorageClient
StorageClient storageClient = getTrackerClient();
//删除文件
int i = storageClient.delete_file(groupName, remoteFileName);
}
/***
* 获取Storage组
* @param groupName
* @return
* @throws IOException
*/
public static StorageServer[] getStoreStorages(String groupName)
throws IOException {
//创建TrackerClient
TrackerClient trackerClient = new TrackerClient();
//获取TrackerServer
TrackerServer trackerServer = trackerClient.getConnection();
//获取Storage组
return trackerClient.getStoreStorages(trackerServer, groupName);
}
/***
* 获取Storage信息,IP和端口
* @param groupName
* @param remoteFileName
* @return
* @throws IOException
*/
public static ServerInfo[] getFetchStorages(String groupName,
String remoteFileName) throws IOException {
TrackerClient trackerClient = new TrackerClient();
TrackerServer trackerServer = trackerClient.getConnection();
return trackerClient.getFetchStorages(trackerServer, groupName, remoteFileName);
}
/***
* 获取Tracker服务地址
* @return
* @throws IOException
*/
public static String getTrackerUrl() throws IOException {
return "http://"+getTrackerServer().getInetSocketAddress().getHostString()+":"+ ClientGlobal.getG_tracker_http_port()+"/";
}
/***
* 获取Storage客户端
* @return
* @throws IOException
*/
private static StorageClient getTrackerClient() throws IOException {
TrackerServer trackerServer = getTrackerServer();
StorageClient storageClient = new StorageClient(trackerServer, null);
return storageClient;
}
/***
* 获取Tracker
* @return
* @throws IOException
*/
private static TrackerServer getTrackerServer() throws IOException {
TrackerClient trackerClient = new TrackerClient();
TrackerServer trackerServer = trackerClient.getConnection();
return trackerServer;
}
}
5.5.3 文件上传
创建一个FileController,在该控制器中实现文件上传操作,代码如下:
package com.qianfeng.controller;
import com.qianfeng.pojo.FastDFSFile;
import com.qianfeng.util.FastDFSClient;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
/**
* 文件操作controller接口
* @Author 千锋壹哥
*/
@RestController
@CrossOrigin
@RequestMapping("/file")
public class FileController {
/**
* 上传接口
* @param file 接收文件参数, 参数名必须叫做file
* @Author 千锋壹哥
*/
@PostMapping("/upload")
public String upload(@RequestParam("file") MultipartFile file) {
String path ="";
try {
path=saveFile(file);
System.out.println(path);
} catch (Exception e) {
e.printStackTrace();
}
return path;
}
/**
* 上传文件到FastDFS分布式文件系统
* @param multipartFile
* @Author 千锋壹哥
*/
public String saveFile(MultipartFile multipartFile) throws IOException {
//1. 获取文件名
String fileName = multipartFile.getOriginalFilename();
//2. 获取文件内容
byte[] content = multipartFile.getBytes();
//3. 获取文件扩展名
String ext = "";
if (fileName != null && !"".equals(fileName)) {
ext = fileName.substring(fileName.lastIndexOf("."));
}
//4. 创建文件实体类对象
FastDFSFile fastDFSFile = new FastDFSFile(fileName, content, ext);
//5. 上传
String[] uploadResults = FastDFSClient.upload(fastDFSFile);
//6. 拼接上传后的文件的完整路径和名字, uploadResults[0]为组名, uploadResults[1]为文件名称和路径
String path = FastDFSClient.getTrackerUrl() + uploadResults[0] + "/" + uploadResults[1];
//7. 返回
return path;
}
}
5.4 Postman测试文件上传
测试步骤:
选择post请求方式,输入请求地址 http://localhost:9001/file/upload
填写Headers
Key:Content-Type Value:multipart/form-data
- 填写body。选择form-data 然后选择文件file 点击添加文件,最后发送即可。
postman填写信息填写如下步骤所示。
注意Headers请求头中内容:
注意body请求体中内容:
上传结果后,就可以通过浏览器访问显示如下效果,这个小姐姐你想要吗?
六. 结语
至此,各位小伙伴是否都已经跟着壹哥学会了FastDFS的使用了呢?
现在挺多企业都是购买阿里云或者华为云等云服务器,然后在这些云服务器上再手动部署FastDFS,这样比直接购买使用阿里的OSS对象存储服务更节省经费,这也就是FastDFS的魅力所在。
版权归原作者 一一哥Sun 所有, 如有侵权,请联系我们删除。