FastDFS 实用笔记

  • 一、什么是 FastDFS?
  • 1.1 传统方式存放文件
  • 1.2 分布式系统存放文件
  • 1.3 FastDFS 介绍
  • 二、FastDFS 简介
  • 2.1 相关概念解释
  • 2.2 上传交互过程
  • 2.3 下载交互过程
  • 2.4 FastDFS 中相关关键字解释
  • 2.5 FastDFS 同步机制
  • 三、安装 FastDFS
  • 3.1 Linux 下安装 FastDFS
  • 3.2 Docker 安装 FastDFS
  • 四、SpringBoot 整合 FastDFS
  • 4.1 环境准备
  • 4.2 编写 SpringBoot 程序
  • 4.3 运行结果

Author:Gorit

Date:2021年1月16日

Refer:B站、腾讯课堂

2021年发表博文: 5/50

一、什么是 FastDFS?

1.1 传统方式存放文件

单系统、单应用

缺点:

  1. 文件全部存放在一个计算机中,如果这台计算机宕机。会导致整个服务不可用。文件上传和下载不能使用
  2. 如果计算机磁盘损坏,那么会丢失所有的文件,而这台计算机的磁盘空间非常有限,存放上限后导致无法存储文件

FastDFS实用笔记 (Docker 搭建环境 + 整合 SpringBoot)_分布式

1.2 分布式系统存放文件

优点:

  1. 解决了传统方式单点故障问题,如果一个节点出现问题,还有其他节点,可以用来读取 和 写入。可以提供数据备份,避免了因磁盘损坏导致的文件 丢失
  2. 动态扩容机制,无限增加文件存放的空间上限

FastDFS实用笔记 (Docker 搭建环境 + 整合 SpringBoot)_linux_02

1.3 FastDFS 介绍

FastDFS 是一个开源轻量级分布式文件系统,为互联网应用量身定做。采用 c 语言开发。是一个软件/软件服务器,它对文件进行管理,但是所管理的文件通常不在一个节点上。功能如下:

  1. 文件存储
  2. 文件同步
  3. 文件访问(文件上传、文件下载)

解决了大容量存储 和 负载均衡的问题,特别适合中小文件(4KB < file_size < 500MB)。如相册网站,视频网站等

FastDFS 充分考虑了冗余备份,线程扩容机制,并注重高可用。高性能等指标。使用 FastDFS 很容易搭建一套高性能的而文件服务器集群提供文件上传。下载服务

二、FastDFS 简介

2.1 相关概念解释

FastDFS 文件系统由两大部分构成,一个是**客户端,**一个是服务端。

客户端是指我们的应用程序,比如我们的 Java 程序 去连接 FastDFS、操作 FastDFS。Java 程序就是一个客户端,FastDFS 提供专有 API 访问,目前提供了 C、Java 和 PHP 的几种变成语言 API。用来访问 FastDFS 文件系统

FastDFS 服务端有两个角色,代表两个服务:

  1. 跟踪器(tracker):做调度工作,在访问上起负载均衡的作用。
  2. 存储节点(storage):存储文件,完成文件管理的所有功能。

为了支持大容量,存储节点(服务器)采用了分卷(或分组)。存储系统由一个或多个卷组成,卷与卷之间的文件是相互独立的。所有卷的文件容量累加就是整个存储系统中的文件容量。一个卷可以由一个或多个存储服务器组成。一个卷下的存储服务器中的文件都是相同的。卷中多台存储服务器起到了冗余备份 和 负载均衡

FastDFS 文件标识分为两个部分:

  1. 卷名
  2. 文件名

2.2 上传交互过程

  1. Client 会向 Tracker 询问存储地址,
  2. Tracker 查询到存储地址(一台可用的 Storage)返回给 Client
  3. Client 拿着地址直接和对应的 Storage 通讯,将文件上传至 Storage

2.3 下载交互过程

  1. Client 会向 Tracker 询问地址,并带上要查询的 文件名 和 组名
  2. Tracker 查询后会将地址(可用的 Storage)返回给 Client
  3. Client 拿着地址 和 指定 Storage 通讯并下载文件

2.4 FastDFS 中相关关键字解释

  • TrackerServer:跟踪服务器,主要做调度工作,在访问上起负载均衡的作用。记录 storage server 状态,是连接 client 和 Storage server 的枢纽。
  • Storage Server:存储服务器,文件和 meta data 都保存到存储服务器上
  • group:组,也称为卷,同组内服务器上的文件是完全相同的
  • 文件标识:包括两部分:组名 和 文件名(包含路径
  • meta data:文件相关属性,键值对(Key Value Pair)方式,如 width=1024,height=784

2.5 FastDFS 同步机制

  1. 同一组内 storage server 之间对等的,文件上传,删除等操作,可以再任意一台 storage server 运行
  2. 文件同步只在同组内的 storage server 之间进行的,采用 push 方式,及源服务器同步给目标服务器
  3. 源头数据才需要同步,备份数据不需要再次同步,否则就构成环路了

PS:上述第二条规则有个例外,就是新增加一台 storage server 时,由已有的一台 storage server 将已有的所有数据(包括源头数据 和 备份数据)同步给新增服务器

三、安装 FastDFS

3.1 Linux 下安装 FastDFS

安装 fastDFS 需要分别安装 fastdfs-nginx-module,fastdfs,nginx,libfastcommon

先安装依赖环境,再安装 FastDFS

安装参考文章

安装参考文章

3.2 Docker 安装 FastDFS

如果学了 Docker ,我们使用 Docker 来安装就会简单很多

参考文章:

下载

docker pull delron/fastdfs

查看镜像

docker images

构建 tracker 服务(跟踪服务器,实现任务调度的作用)

docker run -d --network=host --name tracker -v /root/tracker:/var/root delron/fastdfs tracker

构建 storage 容器(存储服务器,提供容量和备份服务),,这里storage容器需要依赖tracker服务,传入你的tracker服务的ip地址,端口默认是22122,ip地址也就是你宿主机的ip

docker run -d --network=host --name storage -e TRACKER_SERVER=139.196.43.98:22122 -v /root/storage:/var/root -e GROUP_NAME=group1 delron/fastdfs storage

docker ps

[root@VM-16-5-centos storage]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
e36862cc1e63 delron/fastdfs "/usr/bin/start1.sh …" 9 seconds ago Up 8 seconds storage
d3dfdfea80b7 delron/fastdfs "/usr/bin/start1.sh …" 2 minutes ago Up 2 minutes tracker

进入正在工作中的 storage 容器

docker exec -it storage /bin/bash

四、SpringBoot 整合 FastDFS

4.1 环境准备

  1. 一台云服务器,用来提供 FastDFS 的 tracker 服务 和 storage 服务。提供服务的地址是 IP:8888
  2. SpringBoot 版本 2.3.4

PS:保证云服务器的 8888 端口(Nginx 对外提供的端口),22122 端口,23000 端口都能相互访问

4.2 编写 SpringBoot 程序

pom.xml 编写,主要依赖配置如下

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- fastDFS 客户端 -->
<dependency>
<groupId>com.github.tobato</groupId>
<artifactId>fastdfs-client</artifactId>
<version>1.26.5</version>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>

application.yml 配置

spring:
application:
name: fastdfs-demo
servlet:
multipart:
max-file-size: 100MB # 最大支持文件大小
max-request-size: 100MB # 最大请求大小
enabled: true
fdfs:
so-timeout: 1500 # socket 连接时长
connect-timeout: 600 # 连接 tracker 服务器超时时长
# 这两个是你服务器的 IP 地址,注意 23000 端口也要打开,阿里云服务器记得配置安全组。tracker 要和 stroage 服务进行交流
tracker-list: 服务器公网IP:22122
web-server-url: 服务器公网IP:8888
pool:
jmx-enabled: false
# 生成缩略图
thumb-image:
height: 500
width: 500

server:
port: 80

Java 配置类,配置 FastDFS Client

package com.example.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableMBeanExport;
import org.springframework.jmx.support.RegistrationPolicy;

/**
* @Classname FastdfsConfiguration
* @Description TODO
* @Date 2021/1/23 22:27
* @Created by CodingGorit
* @Version 1.0
*/
@Configuration // 导入 FastDFS-Client 组件
@EnableMBeanExport(registration = RegistrationPolicy.IGNORE_EXISTING) // 解决 Jmx重复注册bean的问题
public class FastdfsConfiguration {
}

配置静态文件访问

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
* @Classname WebMvcConfig
* @Description TODO
* @Date 2021/1/23 22:55
* @Created by CodingGorit
* @Version 1.0
*/
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry
.addResourceHandler("/static/**")
.addResourceLocations("classpath:/static/");
}
}

前端页面编写 upload.html

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>文件上传测试</title>
</head>
<body>
<!-- 文件上传 -->
<form action="/upload" method="post" enctype="multipart/form-data">
<input type="file" name="file">
<input type="submit" value="提交">
</form>

<!-- 文件删除 -->
<form action="/delete" method="get">
<input type="text" name="file">
<input type="submit" value="提交">
</form>
</body>
</html>

fastDFS 工具类

package com.example.util;

import com.github.tobato.fastdfs.domain.fdfs.StorePath;
import com.github.tobato.fastdfs.service.FastFileStorageClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;


/**
* @Classname FastdfsUtil
* @Description TODO
* @Date 2021/1/23 22:43
* @Created by CodingGorit
* @Version 1.0
*/
@Component
public class FastdfsUtil {

private static final Logger log = LoggerFactory.getLogger(FastdfsUtil.class);

@Resource
private FastFileStorageClient storageClient ;

/**
* 上传文件
*/
public String upload(MultipartFile multipartFile) throws Exception{
String originalFilename = multipartFile.getOriginalFilename().
substring(multipartFile.getOriginalFilename().
lastIndexOf(".") + 1);
StorePath storePath = this.storageClient.uploadImageAndCrtThumbImage(
multipartFile.getInputStream(),
multipartFile.getSize(),originalFilename , null);
return storePath.getFullPath() ;
}
/**
* 删除文件
*/
public String deleteFile(String fileUrl) {
if (StringUtils.isEmpty(fileUrl)) {
log.info("fileUrl == >>文件路径为空...");
return "文件路径不能为空";
}
try {
StorePath storePath = StorePath.parseFromUrl(fileUrl);
storageClient.deleteFile(storePath.getGroup(), storePath.getPath());
} catch (Exception e) {
log.error(e.getMessage());
}
return "删除成功";
}

/**
* 下载文件
*/
}

控制器类

package com.example.controller;

import com.example.util.FastdfsUtil;
import org.springframework.lang.Nullable;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;

/**
* @Classname FileController
* @Description TODO
* @Date 2021/1/23 22:45
* @Created by CodingGorit
* @Version 1.0
*/
@RestController
public class FileController {

@Resource
private FastdfsUtil fastdfsUtil;

/**
* 文件上传
*/
@RequestMapping(value = "/upload",headers="content-type=multipart/form-data", method = RequestMethod.POST)
public Object uploadFile (@RequestParam("file") MultipartFile file){
String result ;
try{
String path = fastdfsUtil.upload(file) ;
if (!StringUtils.isEmpty(path)){
result = path ;
} else {
result = "上传失败" ;
}
} catch (Exception e){
e.printStackTrace() ;
result = "服务异常" ;
}
return result;
}

/**
* 文件删除
*/
@RequestMapping(value = "/delete", method = RequestMethod.GET)
public Object deleteByPath (@RequestParam("file") String file){
if (StringUtils.isEmpty(file)) {
return "路径不能为空";
}
String res = fastdfsUtil.deleteFile(file);
// group1/M00/00/00/rBEQBWAMJLOAHtzOAC-ojGdpZlE261.png 必须得删除这个路径
return res;
}
}

4.3 运行结果

  1. 进入前端上传页面
  2. 输入 IP 地址访问:http://localhost/upload.html
  3. 上传一张图片

FastDFS实用笔记 (Docker 搭建环境 + 整合 SpringBoot)_分布式_03

  1. 返回的数据

FastDFS实用笔记 (Docker 搭建环境 + 整合 SpringBoot)_spring_04

访问 IP:8888/上面返回的内容,然后就可以看到效果了FastDFS实用笔记 (Docker 搭建环境 + 整合 SpringBoot)_spring_05

删除 图片

FastDFS实用笔记 (Docker 搭建环境 + 整合 SpringBoot)_docker_06

  1. 再次访问

FastDFS实用笔记 (Docker 搭建环境 + 整合 SpringBoot)_spring_07