分布式文件上传
分别说到 FastDFS 和 MinIO
FastDFS
下载依赖
- libfastcommon 公共 C 函数库
- fastdfs fastdfs 核心项目
- fastdfs-nginx-module nginx 整合 fastdfs
打开 linux 安装C语言开发环境
# centos 7
yum install -y make cmake gcc gcc-c++
# ubuntu
sudo apt install gcc
# ubuntu 默认没有make工具
sudo apt install make
安装 libevent(ubuntu 不需要)
FastDFS 依赖 libevent 库
yum -y install libevent
安装 libfastcommon
上传资源 libfastcommon-master.zip 至服务器 /usr/local/src 目录后并解压。
# 安装 unzip 用于解压
yum install -y unzip
# 解压 libfastcommon 至当前所在目录
unzip libfastcommon-master.zip
编辑和安装
# 进入解压后的 libfastcommon-master 目录
cd libfastcommon-master
# 编译并安装
./make.sh
./make.sh install
libfastcommon 默认安装在 /usr/lib64 和 /usr/include/fastcommon 两个目录中
并且会在 /usr/lib 目录中创建软链接。

安装 FastDFS
上传资源 fastdfs-master.zip 至服务器 /usr/local/src 目录后并解压。
# 解压 fastdfs 至当前所在目录
unzip fastdfs-master.zip
编辑和安装
# 进入解压后的 libfastcommon-master 目录
cd fastdfs-master
# 编译并安装
./make.sh install
安装位置
libfastcommon.so 被安装到了 /usr/lib 目录。 头文件(如 common_define.h 等)被安装到了 /usr/include/fastcommon 目录。
编译过程中,如果报错,致命错误:sf/sf_global.h:没有那个文件或目录)
缺少了 libserverframe 网络框架
FastDSF需要用centos7版本往上才能安装的,ubuntu 也是缺少这个框架
需要先安装网络框架 libserverframe
安装方式同上
安装位置
libserverframe.so 被安装到了 /usr/lib 目录。
头文件(如 sf_types.h 等)被安装到了 /usr/include/sf 及其子目录(/usr/include/sf/idempotency/common 、 /usr/include/sf/idempotency/server 、 /usr/include/sf/idempotency/client )
- fastdfs 默认安装在以下位置:
- /usr/bin:可执行文件
- /etc/fdfs:配置文件
- /etc/init.d:主程序代码
- /usr/include/fastdfs:插件组
tracker 和 storage 启动
tracker 和 storage 分别扮演不同的角色,都有自己的配置文件,分别启动
打开 /etc/fdfs 可以看到 tracker 和 storage 两个配置文件
- client.config:客户端配置文件
- storage.conf:存储器的配置文件
- tracker.conf:跟踪器的配置文件
在 /etc/fdfs下
修改 tracker 配置文件
# 允许访问 tracker 服务器的 IP 地址,为空则表示不受限制
bind_addr =
# tracker 服务监听端口
port = 22122
# tracker 服务器的运行数据和日志的存储父路径(需要提前创建好)
base_path = /fastdfs/tracker
# tracker 服务器 HTTP 协议下暴露的端口
http.server_port = 8080
启动 tracker
/usr/bin/fdfs_trackerd /etc/fdfs/tracker.conf restart
修改 storage 的配置文件
# storage 组名/卷名,默认为 group1
group_name = group1
# 允许访问 storage 服务器的 IP 地址,为空则表示不受限制
bind_addr =
# storage 服务器的运行数据和日志的存储父路径(需要提前创建好)
base_path = /fastdfs/storage/base
# storage 服务器中客户端上传的文件的存储父路径(需要提前创建好)
store_path0 = /fastdfs/storage/store
# storage 服务器 HTTP 协议下暴露的端口
http.server_port = 8888
# tracker 服务器的 IP 和端口
tracker_server = 192.168.10.101:22122
启动 storage
/usr/bin/fdfs_storaged /etc/fdfs/storage.conf restart
client 配置(命令行客户端,程序调用不需要)
- tracker 地址和之前定义一样
# client 客户端的运行数据和日志的存储父路径(需要提前创建好)
base_path = /fastdfs/client
# tracker 服务器的 IP 和端口
tracker_server = 192.168.10.101:22122
到 usr/bin 下执行上传
fdfs_test client配置路径 upload 将要上传的文件路径
整合 nginx
- 将 FastDFS-nginx-module 包传至 /usr/local/ 下
- 解压进去,讲 src 里面的 config 文件的 /usr/local 全部改为 /usr
- 将 mod_fastdfs.conf 拷贝至 /etc/fdfs/ 下
- 打开此文件,配置信息
# 存放文件
base_path=/opt/fastdfs/nginxFdFsLog
# tracker 的地址
tracker_server=192.168.134.222:22122
# 设置文件 URL 是否包含组名
url_have_group_name=true
store_path0=/home/FastDFS/fdfs_storage
# 群组名要和 storage 的一致
group_name=group1
- 安装 nginx 依赖
yum -y install pcre pcre-devel openssl openssl-devel zlib zlib-devel gcc
- 安装 nginx,解压到 uer/local/src 官网地址
- 到 nginx 目录进行配置,最后一行add-module 配置 fastdfs-nginx的src路径
--with-http_image_filter_module \ # 缩略图插件
./configure \
--prefix=/usr/local/nginx \
--pid-path=/var/run/nginx/nginx.pid \
--lock-path=/var/lock/nginx.lock \
--error-log-path=/var/log/nginx/error.log \
--http-log-path=/var/log/nginx/access.log \
--with-http_gzip_static_module \
--with-http_image_filter_module \
--http-client-body-temp-path=/var/temp/nginx/client \
--http-proxy-temp-path=/var/temp/nginx/proxy \
--http-fastcgi-temp-path=/var/temp/nginx/fastcgi \
--http-uwsgi-temp-path=/var/temp/nginx/uwsgi \
--http-scgi-temp-path=/var/temp/nginx/scgi \
--add-module=/usr/local/fastdfs-nginx-module-master/src
- 编译,先make,再使用 make install,之后再 usr/local/nginx
- 添加配置 nginx 服务器的 server,在 usr/local/src/nginx.../config/nginx.config
- 运行,之前已经将 nginx运行安装到了 ,usr/local/nginx中
- 到usr/local/nginx/sbin目录运行
- nginx -t 检查配置是否 ok
- 提示,没有此目录-- /var/temp/nginx/client,创建一下
- 将 /usr/local/fastdfs/config的 http.config 和 mime.types 拷贝到 /etc/fdfs/ 下,http 访问需要
- ./nginx 启动
注意:下面的location 后面的路径必须是 / 并且开头要加,否则访问不到
nginx 配置
# FastDFS 转发配置
server {
listen 8889; # Nginx 将在此端口监听来自客户端的请求
server_name localhost; # 服务器的名称
# FastDFS 文件访问路径
location ~/group[0-9]/ {
ngx_fastdfs_module; # 启用 FastDFS 模块,转发请求到 FastDFS
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}
如果启动报错,原因是nginx的配置文件中, log 日志文件夹不存在,把#号去掉目录创建即可
nginx: [emerg] open() "/var/run/nginx/nginx.pid" failed (2: No such file or directory)
pid /usr/local/nginx/log/nginx.pid;
缩略图配置--不一定要配置,它是一个比较耗费CPU的东西
安装依赖
yum install gd-devel
config 中添加缩略图的配置
location ~group1/M00/(.+)_(\d+)x(\d+)\.(jpg|gif|png){
ngx_fastdfs_module;
set $w $2;
set $h $3;
image_filter resize $w $h;
image_filter_buffer 10M;
rewrite group1/M00(.+)_(\d+)x(\d+)\.(jpg|gif|png)$ group1/M00$1.$4 break;
}
访问
http://192.168.134.222:8888/group1/M00/00/00/wKiG3mTHrN6AGoaaAChK-2gMkSU309_1800x900.jpg
Java 测试
依赖
<dependency>
<groupId>net.oschina.zcx7878</groupId>
<artifactId>fastdfs-client-java</artifactId>
<version>1.27.0.0</version>
</dependency>
上传
@Test
void contextLoads() {
try {
// 加载配置文件
ClientGlobal.initByProperties("fastdfs-client.properties");
// 创建 tracker 客户端
TrackerClient trackerClient = new TrackerClient();
// 获取服务
TrackerServer trackerServer = trackerClient.getConnection();
StorageServer storageServer = null;
// 定义 storage 客户端
StorageClient1 storageClient = new StorageClient1(trackerServer, storageServer);
// 文件元信息--可以加很多
NameValuePair[] metaList = new NameValuePair[1];
metaList[0] = new NameValuePair("fileName", "1.png");
// 执行上传
String file1 = storageClient.upload_file1("D:\\qq\\MTWG@_0$S[{KLGOU[O6~NA5.jpg", "jpg", metaList);
System.out.println(file1); //
// 关闭
trackerServer.close();
} catch (IOException | MyException e) {
throw new RuntimeException(e);
}
}
开放 22122 和 23000 端口
sudo firewall-cmd --zone=public --add-port=22122/tcp --permanent
sudo firewall-cmd --zone=public --add-port=23000/tcp --permanent
# 重启端口
sudo firewall-cmd --reload
上传成功
kun/M00/00/00/wKhQgGS6TtaAdNf3AAEZO5jDrk0154.jpg
配置文件
## fastdfs-client.properties
fastdfs.connect_timeout_in_seconds = 5
fastdfs.network_timeout_in_seconds = 30
fastdfs.charset = UTF-8
fastdfs.http_anti_steal_token = false
fastdfs.http_secret_key = FastDFS1234567890
fastdfs.http_tracker_http_port = 80
fastdfs.tracker_servers = 192.168.80.128:22122
## 是否打开连接池,如果不打开,则每次创建一个新连接
fastdfs.connection_pool.enabled = true
## max_count_per_entry:每个主机的最大连接计数:端口,0不是限制
fastdfs.connection_pool.max_count_per_entry = 500
## 空闲时间超过此时间的连接将被关闭,单位:秒,默认值为3600
fastdfs.connection_pool.max_idle_time = 3600
## 达到最大连接数时的最大等待时间,单位为毫秒,默认值为1000
fastdfs.connection_pool.max_wait_time_in_ms = 1000
查询
try {
// 加载配置文件
ClientGlobal.initByProperties("fastdfs-client.properties");
// 创建 tracker 客户端
TrackerClient trackerClient = new TrackerClient();
// 获取服务
TrackerServer trackerServer = trackerClient.getConnection();
StorageServer storageServer = null;
// 定义 storage 客户端
StorageClient1 storageClient = new StorageClient1(trackerServer, storageServer);
// 查询方式一
FileInfo info = storageClient
.query_file_info("group1", "M00/00/00/wKiG3mS7KciAQCdvACb7kEitkFc114.png");
System.out.println(info);
// 查询方式二,直接查询
FileInfo info1 = storageClient.query_file_info1("group1/M00/00/00/wKiG3mS7KciAQCdvACb7kEitkFc114.png");
System.out.println(info1);
// 查询元信息
NameValuePair[] pairs = storageClient.get_metadata1("group1/M00/00/00/wKiG3mS7KciAQCdvACb7kEitkFc114.png");
System.out.println(Arrays.toString(pairs));
// 关闭
trackerServer.close();
} catch (IOException | MyException e) {
throw new RuntimeException(e);
}
下载
try {
// 加载配置文件
ClientGlobal.initByProperties("fastdfs-client.properties");
// 创建 tracker 客户端
TrackerClient trackerClient = new TrackerClient();
// 获取服务
TrackerServer trackerServer = trackerClient.getConnection();
StorageServer storageServer = null;
// 定义 storage 客户端
StorageClient1 storageClient = new StorageClient1(trackerServer, storageServer);
// 下载
byte[] bytes = storageClient.download_file1("group1/M00/00/00/wKiG3mS7KciAQCdvACb7kEitkFc114.png");
FileOutputStream stream = new FileOutputStream(new File("C:\\Users\\坤\\Pictures\\2.png"));
stream.write(bytes);
stream.close();
// 关闭
trackerServer.close();
} catch (IOException | MyException e) {
throw new RuntimeException(e);
}
概述
分布式文件系统:通过网络将一个一个计算机连接起来,组成文件系统
- 扩容性
- 持续性
主流的分布式文件系统
- NFS:网络文件系统
客户端 + 服务器,在客户端映射 nfs 的磁盘
- GFS:分块文件存储
主从结构,先将文件分块,存储到块服务器中,用户先访问 master 服务器,获取从那些块获取数据
- hdfs
分块存储,先访问 namenode 访问元数据,根据元数据从块中拿数据。
分布式服务提供商
OSS:阿里提供的
七牛云存储
百度云存储
fastdfs
由 C 语言编写的开源的分布式文件系统,由余庆编写并开源
是专用文件系统,适合存储小文件,也不分块,性能高,复杂性低
分为 tracker 和 storage
tracker 管理很多的 storage
用户先访问 tracker ,tracker根据情况返回 storage 存储的图片上传路径
tracker 和 storage 都可以部署集群
storage 向 tracker 上报自己状态
文件上传流程

文件下载流程

http://192.168.80.128/kun/M00/00/00/wKhQgGS6C2CAOP_uAAGADZNFMQg5288901_big
- ip 地址
- 组名:一个 storage
- M00:虚拟路径
- 后面都是文件路径
文件上传前后端
文件类
public class FileSystem {
private String fileId;
private String filePath;
private String fileSize;
private String fileName;
private String fileType;
}
controller
fastdfs-upload:
location: D:/upload
前端上传图片处理
这里需要处理一下跨域
<el-upload
action="/api/fileServer/upload"
list-type="picture-card"
:on-preview="handlePictureCardPreview">
<i class="el-icon-plus"></i>
</el-upload>
前端将图片上传到后端,后端保存到本地,并上传服务器。返回文件对象
@RestController
@RequestMapping("/fileServer")
public class FileServerController {
@Value("${fastdfs-upload.location}")
private String upload_location;
/**
* 将文件存储到本机上,调用 fastdfs 将文件上传到服务器
*/
@PostMapping("/upload")
public FileSystem upload(MultipartFile file) throws IOException {
// 获取原始名称
String filename = file.getOriginalFilename();
// 扩展名
String extention = filename.substring(filename.lastIndexOf("."));
// 存到本机的名字
String fileNameNew = UUID.randomUUID() + extention;
// 存入本机
File file1 = new File(upload_location + fileNameNew);
file.transferTo(file1);
// 获取本机的路径
String newFilepath = file1.getAbsolutePath();
FileSystem system;
try {
// 加载配置文件
ClientGlobal.initByProperties("fastdfs-client.properties");
// 创建 tracker 客户端
TrackerClient trackerClient = new TrackerClient();
// 获取服务
TrackerServer trackerServer = trackerClient.getConnection();
StorageServer storageServer = null;
// 定义 storage 客户端
StorageClient1 storageClient = new StorageClient1(trackerServer, storageServer);
// 文件元信息--可以加很多
NameValuePair[] metaList = new NameValuePair[1];
metaList[0] = new NameValuePair("fileName", filename);
// 执行上传,扩展名为null,是因为解决重复添加扩展名。
String fileFdfs = storageClient.upload_file1(newFilepath, null, metaList);
system = new FileSystem(fileFdfs, fileFdfs, file.getSize(), fileNameNew, file.getContentType());
System.out.println(fileFdfs);
// 关闭
trackerServer.close();
} catch (IOException | MyException e) {
throw new RuntimeException(e);
}
return system;
}
}
base64 编码转 MultipartFile 对象,转 base64
实现
@Test
public void getTreeStructure() {
// 图片路径
String imageBase64 = encodeImageToBase64("D:\\qq\\MTWG@_0$S[{KLGOU[O6~NA5.jpg");
String dataUri = "data:image/jpg;base64"; // 替换为实际的 DataURI
// 创建 Base64ToMultipartFile 对象
MultipartFile multipartFile = new Base64ToMultipartFile(imageBase64, dataUri);
try {
// 转为 file对象 输出到文件
multipartFile.transferTo(new File("D:\\qq\\222.jpg"));
} catch (IOException e) {
e.printStackTrace();
}
// 使用 MultipartFile 对象,获取原始名
String originalFilename = multipartFile.getOriginalFilename();
// 获取上传文件的内容类型(MIME 类型)
String contentType = multipartFile.getContentType();
// 判断上传文件是否为空
boolean isEmpty = multipartFile.isEmpty();
// 可以使用 fileBytes 进行文件上传等操作,转字节
byte[] fileBytes;
try {
fileBytes = multipartFile.getBytes();
// 可以使用 fileBytes 进行文件上传等操作
} catch (IOException e) {
e.printStackTrace();
}
}
//将图片文件转化为字节数组字符串,并对其进行Base64编码处理
public static String encodeImageToBase64(String imagePath) {
File file = new File(imagePath);
try (FileInputStream imageInFile = new FileInputStream(file)) {
byte[] imageData = new byte[(int) file.length()];
imageInFile.read(imageData);
return Base64.getEncoder().encodeToString(imageData);
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
实现类
public class Base64ToMultipartFile implements MultipartFile {
private final byte[] fileContent;
private final String originalFilename;
private final String contentType;
/**
* 构造函数,将 Base64 编码的图片数据和 DataURI 传入,创建 MultipartFile 对象
*
* @param base64Data Base64 编码的图片数据
* @param dataUri DataURI 格式,例如 "data:image/jpg;base64"
*/
public Base64ToMultipartFile(String base64Data, String dataUri) {
this.fileContent = Base64.getDecoder().decode(base64Data);
this.originalFilename = "file_" + System.currentTimeMillis(); // 使用当前时间戳生成文件名
this.contentType = dataUri.split(";")[0].split(":")[1]; // 获取 DataURI 中的内容类型
}
@Override
public String getName() {
return "param_" + System.currentTimeMillis(); // 使用当前时间戳生成参数名
}
@Override
public String getOriginalFilename() {
return originalFilename; // 返回生成的原始文件名
}
@Override
public String getContentType() {
return contentType; // 返回内容类型(MIME 类型)
}
@Override
public boolean isEmpty() {
return fileContent == null || fileContent.length == 0; // 判断文件内容是否为空
}
@Override
public long getSize() {
return fileContent.length; // 返回文件内容的长度
}
@Override
public byte[] getBytes() throws IOException {
return fileContent; // 返回文件内容的字节数组
}
@Override
public InputStream getInputStream() throws IOException {
return new ByteArrayInputStream(fileContent); // 返回文件内容的输入流
}
@Override
public void transferTo(File dest) throws IOException, IllegalStateException {
try (FileOutputStream fos = new FileOutputStream(dest)) {
fos.write(fileContent); // 将文件内容写入目标文件
}
}
}
最近需要用到fastdfs,再来更新一下使用,简单理一下思路,交给Spirng管理,然后...再然后,就可以使用了。
SpringBoot 整合 fastdfs
配置文件
## fastdfs-client.properties
fastdfs.connect_timeout_in_seconds = 5
fastdfs.network_timeout_in_seconds = 30
fastdfs.charset = UTF-8
fastdfs.http_anti_steal_token = false
fastdfs.http_secret_key = FastDFS1234567890
fastdfs.http_tracker_http_port = 80
fastdfs.tracker_servers = 123.24.94.225:22122
## ????????????????????????
fastdfs.connection_pool.enabled = true
## max_count_per_entry:???????????????0????
fastdfs.connection_pool.max_count_per_entry = 500
## ??????????????????????????3600
fastdfs.connection_pool.max_idle_time = 3600
## ??????????????????????????1000
fastdfs.connection_pool.max_wait_time_in_ms = 1000
第一步初始化
@Slf4j
@Configuration
public class FastDfsConfig {
@Bean
public StorageClient1 fastDFSClient() {
try {
// 加载配置文件
ClientGlobal.initByProperties("fastdfs-client.properties");
// 创建 tracker 客户端
TrackerClient trackerClient = new TrackerClient();
TrackerServer trackerServer = trackerClient.getTrackerServer();
// 定义 storage 客户端
return new StorageClient1(trackerServer, null);
} catch (IOException | MyException e) {
log.error("fastdfs初始化失败:{}", e.getMessage());
return null;
}
}
}
文件上传
@Slf4j
@Service
public class FastDfsService {
private final StorageClient1 storageClient1;
/**
* 文件上传
*/
public String upload(MultipartFile file) {
try {
// 获取文件扩展名
String fileSuffix = FileUtils.getFileSuffix(file.getOriginalFilename());
// 执行上传
return storageClient1.upload_file1(file.getBytes(), fileSuffix, null);
} catch (IOException | MyException | ArrayIndexOutOfBoundsException e) {
log.error("fastdfs上传文件失败:{}", e.getMessage());
return null;
}
}
}
文件删除
/**
* 文件删除
*
* @param groupName 组名 group1
* @param fileName 文件名 M00/00/00/ezle4Wa4XqeAG652ACb7kEitkFc133.png
*/
public boolean delete(String groupName, String fileName) {
try {
return storageClient1.delete_file(groupName, fileName) == 0;
} catch (IOException | MyException e) {
log.error("fastdfs删除文件失败:{}", e.getMessage());
return false;
}
}