一文带你读懂FastDFS使用

3,262 阅读7分钟

头图来自:www.zcool.com.cn/u/14943699

FastDFS

一、FastDFS基本了解

1、FastDFS概念

​ FastDFS是一个开源的轻量级分布式文件系统,它对文件进行管理,功能包括:文件存储、文件同步、文件访问(文件上传、文件下载)等,解决了大容量存储和**负载均衡**的问题。特别适合以文件为载体的在线服务,如相册网站、视频网站等等。

​ FastDFS为互联网量身定制,充分考虑了冗余备份、负载均衡、在线扩容等机制,并注重高可用高性能等指标,使用FastDFS很容易搭建一套高性能的文件服务器集群提供文件上传、下载等服务。

2、FastDFS的作用

​ 适合用来存储用户图片、视频、文档等文件。 ​ 出于简洁考虑,FastDFS没有对文件做分块存储,因此不太适合分布式计算场景

3、FastDFS的优缺点

优点:

  1. 适合中小文件存储(建议范围:4KB<file_size<500MB
  2. 主备Tracker服务,增强系统的可用性
  3. 系统不需要支持POSIX,这样的话就降低了系统的复杂度,使得处理的速度会更高
  4. 支持主从文件,支持自定义扩展名
  5. 支持在线扩容机制,增强了系统的可扩展性
  6. 实现了软RAID,增强了系统的并发处理能力和数据容错恢复能力

缺点:

  1. 直接按文件存储,可直接读取文件内容,缺乏文件安全性
  2. 通过API下载,存在单点的性能瓶颈
  3. 不支持断点续传,对大文件将是噩梦
  4. 同步机制不支持文件正确性校验,降低了系统的可用性
  5. 不支持POSIX通用接口访问,通用性比较的低
  6. 对跨公网的文件同步,存在着比较大的延迟,需要应用做相应的容错策略

4、相关概念

FastDFS服务端有三个角色:跟踪服务器(tracker server)、存储服务器(storage server)和客户端(client)。

tracker server:跟踪服务器,主要做调度工作,起负载均衡的作用。Tracker是FastDFS的协调者,负责管理所有的storage server和group,每个storage在启动后会连接Tracker,告知自己所属的group等信息,并保持周期性的心跳,tracker根据storage的心跳信息,建立group==>[storage server list]的映射表。

storage server:存储服务器(又称:存储节点或数据服务器),文件和文件属性(meta data)都保存到存储服务器上。Storage server直接利用OS的文件系统调用管理文件。

client:客户端,作为业务请求的发起方,通过专有接口,使用TCP/IP协议与跟踪器服务器存储节点进行数据交互。FastDFS向使用者提供基本文件访问接口,比如upload、download、append、delete等,以客户端库的方式提供给用户使用。

二、FastDFS原理

1、FastDFS系统结构图

  • FastDFS分为Tracker、Storage,其中Storage负责存储文件,Tracker负责存储文件所在地址,主要作用是负载均衡和资源调度。

  • Tracker、Storage都可以实现集群部署,Tracker的每个节点地位平等,而Storage可以分为多个组,每个组之间保存的文件是不同的,组内部分为多个成员,每个成员保存的内容是一样,组成员地位一致,没有主从概念。

  • 使用FastDFS存储文件优点:可以应对互联网的海量文件存储,一旦文件较多,可以随时横向扩展,且集群的实现也使系统不存在单点故障问题,用户不会因为服务器宕机而无法访问文件资源。

2、FastDFS工作流程

文件上传

• 1. client询问tracker上传到的storage,不需要附加参数;

• 2. tracker返回一台可用的storage;

• 3. client直接和storage通讯完成文件上传。

文件下载

image-20200711081914456

  1. client询问tracker下载文件的storage,参数为文件标识(组名和文件名);

  2. tracker返回一台可用的storage;

  3. client直接和storage通讯完成文件下载。

三、Linux下部署FastDFS

1、准备工作

安装gcc(编译时需要)

yum install -y gcc gcc-c++

安装libevent(运行)

yum -y install libevent

创建文件夹并上传文件

mkdir -p /fileservice/fast

cd /fileservice/fast

注意:需要将相关文件上传值该文件夹中。

img

2、安装libfastcommon

# 解压文件
tar -zxvf libfastcommon-1.0.35.tar.gz
# 进入目录
cd libfastcommon-1.0.35
# 编译
./make.sh
# 安装
./make.sh install

使用make指令

image-20200708115344216

安装完成后

image-20200708115609983

目录结构

image-20200708115934372

3、安装fastdfs

安装相关依赖

yum install perl pcre pcre-devel zlib zlib-devel openssl openssl-devel -y
安装fastdfs
# 解压fastdfs
tar zxvf fastdfs-5.11.tar.gz
# 进入目录
cd fastdfs-5.11
# 执行编译
./make.sh
# 安装
./make.sh install

安装成功之后

image-20200708141037022

查看tracker和storage的可执行脚本
ll /etc/init.d/ | grep fdfs

image-20200708141228169

准备配置文件

默认在/etc/fdfs/下面

cd /etc/fdfs/

复制配置文件

cp client.conf.sample client.conf
cp storage.conf.sample storage.conf
cp storage_ids.conf.sample storage_ids.conf
cp tracker.conf.sample tracker.conf

创建修改tracker默认的存放数据和日志的需要的目录

mkdir -p /home/fastdfs/tracker

创建修改storage默认的存放数据和日志的需要的目录

mkdir -p /home/fastdfs/storage

配置和启动tracker

配置
# 切换到/etc/fdfs/下
cd /etc/fdfs/
# 修改tracker.conf
vim tracker.conf

修改默认的base_path为新创建的文件,即/home/fastdfs/tracker

文件tracker.conf的修改内容

# 修改默认的base_path,注意:根文件夹必须存在,子文件夹会自动创建
base_path=/home/fastdfs/tracker

image-20200708143358349

启动tracker
service fdfs_trackerd  start

image-20200708142706897

注意:服务启动成功后,会在base_path指定的目录下生成datalogs

image-20200708143732474

配置和启动storage

配置
# 切换目录
cd /etc/fdfs/
# 修改storage.conf 
vim storage.conf

文件tracker.conf的配置

# 配置组名(可选)
group_name=group1 

# 配置base_path,注意:根文件夹必须存在,子文件夹会自动创建
base_path=/home/fastdfs/storage

#store存放文件的位置(store_path), 
store_path0=/home/fastdfs/storage
# 注意:如果挂载多个磁盘,如下配置
# store_path1=.....
# store_path2=......
#。。。

#配置tracker服务器:IP
tracker_server=192.168.10.100:22122
#如果有多个则配置多个tracker
#tracker_server= xxx.xxx.xxx.xxx:22122
#。。。

image-20200708150127834

image-20200708150254896

image-20200708150435787

启动storage
service fdfs_storaged start

启动完成后进入 /home/fastdfs/storage/data 目录下,显示目录如下:

cd /home/fastdfs/storage/data/

image-20200708151408153

4、使用FastDFS自带工具测试

配置
# 切换目录到
cd /etc/fdfs/
# 修改client.conf
vim client.conf

修改基本路径和tracker_server如下

# storage的根目录
base_path=/home/fastdfs/storage

# tracker的ip和地址
tracker_server=192.168.10.100:22122

# 注意:若tracker有多个,可以配置多个
# tracker_server= ...
# tracker_server= ...

image-20200708151917417

上传一张图片到Linux的根目录/root/

image-20200708152827632

进行测试
/usr/bin/fdfs_upload_file /etc/fdfs/client.conf /root/image-20200708110242109.jpg

image-20200708153026498

如此,表示FastDFS搭建成功。

上图中文件的地址:http://192.168.10.100:80/group1/M00/00/00/wKgKZF8FdfiAIvwVAAETxoCDaLI533.jpg,对应storage服务器上的 /home/fastdfs/storage/data/00/00/wKgKZF8FdfiAIvwVAAETxoCDaLI533.jpg文件

image-20200708153538201

注意:此时该图片无法使用http访问下载,可以使用内置Web Server,也可以和其他Web Server配合使用

5、FastDFS为什么要结合Nginx?

通过FastDFS的HTTP服务器来提供HTTP服务。但是FastDFS的HTTP服务无法提供负载均衡等高性能的服务,所以FastDFS的开发者—淘宝的架构师余庆为我们提供了Nginx上使用的FastDFS模块(FastDFS的Nginx模块)。详情

6、安装fastdfs-nginx-module

配置
# 解压 fastdfs-nginx-module
tar -zxvf  fastdfs-nginx-module-1.20.tar.gz
# 切换目录
cd fastdfs-nginx-module-1.20/src
# 修改config
vim config

修改内容

ngx_module_incs="/usr/include/fastdfs /usr/include/fastcommon/"
CORE_INCS="$CORE_INCS /usr/include/fastdfs /usr/include/fastcommon/"

image-20200708161328126

拷贝配置mod_fastdfs.conf并修改

# 将fastdfs-nginx-module/src下的mod_fastdfs.conf拷贝至/etc/fdfs/下
cp mod_fastdfs.conf /etc/fdfs/
# 修改 /etc/fdfs/mod_fastdfs.conf 的内容
vim /etc/fdfs/mod_fastdfs.conf

修改内容如下:

# 配置跟踪服务器tracker的ip和端口
tracker_server=192.168.10.100:22122 
#url中包含group名称
url_have_group_name=true
#指定文件存储路径(为配置的store路径)
store_path0=/home/fdfs_storage

image-20200708162138972

image-20200708162214356

image-20200708162306467

拷贝配置文件

cp http.conf mime.types /etc/fdfs/

7、nginx的安装

配置

cd /fileservice/fast/
# 解压nginx
tar -zxvf nginx-1.15.2.tar.gz 
# 进入nginx解压的目录下
cd nginx-1.15.2/
# 加入模块命令配置
./configure --prefix=/opt/nginx --sbin-path=/usr/bin/nginx --add-module=/fileservice/fast/fastdfs-nginx-module-1.20/src
# 编译并安装
make && make install
# 修改nginx配置
cd /opt/nginx/conf
vim nginx.conf

修改nginx配置的内容

 server {
        listen       80;
        server_name  localhost;

        #charset koi8-r;

        #access_log  logs/host.access.log  main;

        location / {
           # root   html;
           # index  index.html index.htm;
           ngx_fastdfs_module;
        }

image-20200708163457406

启动ngnix

cd /usr/bin/
# 启动
./nginx

四、Docker搭建FastDFS

1、拉取镜像并启动

# 启动docker
systemctl start docker

# -v ${HOME}/fastdfs:/var/local/fdfs是指:将${HOME}/fastdfs这个目录挂载到容器里的/var/local/fdfs这个目录里。
#所以上传的文件将被持久化到${HOME}/fastdfs/storage/data里
#IP 后面是自己的服务器公网ip或者虚拟机ip,
#-e WEB_PORT=80 指定nginx默认端口
docker run -d --restart=always --privileged=true --net=host --name=fastdfs -e IP=192.168.10.100 -e WEB_PORT=80 -v ${HOME}/fastdfs:/var/local/fdfs registry.cn-beijing.aliyuncs.com/tianzuo/fastdfs

2、测试上传

# 进入容器
docker exec -it fastdfs /bin/bash
# 创建文件
echo "Hello FastDFS!">index.html
# 测试文件上传
fdfs_test /etc/fdfs/client.conf upload index.html

3、测试访问

五、Java实现文件上传

1、创建Maven项目

2、引入依赖

<!-- fastdfs客户端jar -->
<dependency>
    <groupId>net.oschina.zcx7878</groupId>
    <artifactId>fastdfs-client-java</artifactId>
    <version>1.27.0.0</version>
</dependency>
<!-- spring-core -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-core</artifactId>
    <version>4.3.25.RELEASE</version>
</dependency>

3、创建fdfs_client.conf配置文件

# 连接超时时间
connect_timeout=30
# 网络超时时间
network_timeout=60
# tracker根目录
base_path=/home/fastdfs/tracker
#改为自己服务器的ip
tracker_server=192.168.10.100:22122

log_level=info
use_connection_pool = false
connection_pool_max_idle_time = 3600
load_fdfs_parameters_from_tracker=false
use_storage_id = false
storage_ids_filename = storage_ids.conf
http.tracker_server_port=80

4、编写测试类

public static void main(String[] args) throws Exception {

    // 文件的路径
    String testFilePath = "C:/Users/hxxiapgy/Desktop/fastdfs架构.png";

    // 获取fdfs_client.conf的
    String filePath  = new ClassPathResource("fdfs_client.conf").getFile().getAbsolutePath();

    // 1.加载配置文件
    ClientGlobal.init(filePath);
    // 2.创建一个 TrackerClient 对象
    TrackerClient trackerClient = new TrackerClient();
    // 3.使用 TrackerClient 对象创建连接,获得一个 TrackerServer 对象。
    TrackerServer trackerServer = trackerClient.getConnection();
    // 4.声明 StorageServer
    StorageServer storageServer = null;
    // 5.创建一个 StorageClient 对象,需要两个参数 TrackerServer 对象、StorageServer 的引用
    StorageClient storageClient = new StorageClient(trackerServer, storageServer);
    // 6.通过storangeClient上传文件,返回组名和文件名
    String[] strings = storageClient.upload_file(testFilePath, "png", null);

    // 打印组名与文件名
    for (String string : strings) {
        System.out.println(string);
    }

    System.out.println("文件上传成功!");
}

image-20200708214405417

六、SpringBoot上传文件

1、方式一:

创建项目
引入依赖
 <!--   fastdfs依赖     -->
<dependency>
    <groupId>net.oschina.zcx7878</groupId>
    <artifactId>fastdfs-client-java</artifactId>
    <version>1.27.0.0</version>
</dependency>
<!--    lang3        -->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
</dependency>
修改配置文件
fastdfs:
  connect_timeout_in_seconds: 120
  network_timeout_in_seconds: 120
  charset: UTF-8
  tracker_servers: 192.168.10.100:22122
编写UploadService
/**
 * @description: 文件上传
 * @author: Hxxiapgy
 * @date: 2020/7/9 20:02
 */
@Service
public class UploadService {

    @Value("${fastdfs.connect_timeout_in_seconds}")
    private Integer connectTimeout;

    @Value("${fastdfs.network_timeout_in_seconds}")
    private Integer networkTimeout;

    @Value("${fastdfs.charset}")
    private String charset;

    @Value("${fastdfs.tracker_servers}")
    private String trackerServers;



    public Map<String,Object> upload(MultipartFile multipartFile){

        // 判断文件是否存在
        if(multipartFile == null){
            throw new RuntimeException("文件不能为空!");
        }

        // 将文件上传到fastdfs
        String fileId = fdfsUpload(multipartFile);

        // 判断fileId是否为空
        if (fileId == null){
            throw  new RuntimeException("文件上传失败!");
        }

        Map<String,Object> map = new HashMap<>();
        map.put("code", 1);
        map.put("msg", "文件上传成功");
        map.put("fileId", fileId);

        return map;
    }

    /**
    * 功能描述:
    * @param
    * @return {@link String}
    * @author hxxiapgy
    * @data 2020/7/9 20:08
    */
    private String fdfsUpload(MultipartFile multipartFile){

        // 1.初始化FastDFS的环境
        initFdfsConfid();

        // 1.创建trackerClient对象
        TrackerClient trackerClient = new TrackerClient();

        try {

            // 2.获取trackerService
            TrackerServer trackerServer = trackerClient.getConnection();
            // 3.声明StorageService
            StorageServer storageServer = null;
            // 4.获取StorageClient
            StorageClient1 storageClient1 = new StorageClient1(trackerServer,storageServer);
            // 5.获取文件后缀
            String originalFilename = multipartFile.getOriginalFilename();
            // 文件异常
            if (StringUtils.isBlank(originalFilename)) {
                throw  new RuntimeException("文件读取异常!");
            }
            String extName = originalFilename.substring(originalFilename.lastIndexOf(".") + 1);

            // 6.通过StorageService上传文件,返回组名和文件名
            String fileId = storageClient1.upload_file1(multipartFile.getBytes(), extName , null);
            return fileId;

        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }


    }

    /**
    * 功能描述: 加载fastDFS环境
    * @param
    * @return
    * @author hxxiapgy
    * @data 2020/7/9 20:35
    */
    private void initFdfsConfid() {

        try {
            // 设置连接超时时间
            ClientGlobal.setG_connect_timeout(connectTimeout);
            // 设置网络超时时间
            ClientGlobal.setG_network_timeout(networkTimeout);
            // 设置字符编码
            ClientGlobal.setG_charset(charset);
            // 设置trackerService
            ClientGlobal.initByTrackers(trackerServers);
        } catch (Exception e) {
            e.printStackTrace();
        }

    }


}
编写UploadController
/**
 * @description: 文件上传
 * @author: Hxxiapgy
 * @date: 2020/7/9 20:40
 */
@Controller
public class UploadController {

    @Resource
    private UploadService uploadService;

    @PostMapping("/upload")
    @ResponseBody
    public Map<String,Object> upload(MultipartFile multipartFile){

        Map<String, Object> map = uploadService.upload(multipartFile);
        return map;
    }

    @RequestMapping("/index")
    public String index(){
        return "index.html";
    }

}

编写测试入口index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>测试文件上传</title>
</head>
<body>

    <h3>文件上传</h3>
    <hr/>
    <form action="/upload" method="post"  enctype="multipart/form-data">
        <input type="file" name="multipartFile"/>
        <input type="submit" value="上传"/>
    </form>
</body>
</html>
测试

方式二

创建项目
引入依赖
<!--   fastdfs client     -->
<dependency>
    <groupId>com.github.tobato</groupId>
    <artifactId>fastdfs-client</artifactId>
    <version>1.26.7</version>
</dependency>
修改配置文件
fdfs:
  so-timeout: 2500       # 读取时间
  connect-timeout: 600   # 连接超时时间
  thumb-image:           # 缩略图
    width: 100
    height: 100
  tracker-list:          # tracker服务配置地址列表
    - 192.168.10.100:22122
upload:
  base-url: http://192.168.10.100/
  allow-types:
    - image/jpeg
    - image/png
    - image/bmp
    - image/gif
编写fastdfs的属性类
/**
 * @description: 配置fastdfs的属性类
 * @author: Hxxiapgy
 * @date: 2020/7/9 21:58
 */
@ConfigurationProperties(prefix = "upload")
@Data
public class UploadProperties {

    private String baseUrl;

    private List<String> allowTypes;

    public String getBaseUrl() {
        return baseUrl;
    }

    public void setBaseUrl(String baseUrl) {
        this.baseUrl = baseUrl;
    }

    public List<String> getAllowTypes() {
        return allowTypes;
    }

    public void setAllowTypes(List<String> allowTypes) {
        this.allowTypes = allowTypes;
    }
}
编写UploadService
/**
 * @description: 文件上传
 * @author: Hxxiapgy
 * @date: 2020/7/9 22:01
 */
@Service
@EnableConfigurationProperties(UploadProperties.class)
public class UploadService {

    private Log log= LogFactory.getLog(UploadService.class);

    @Resource
    private FastFileStorageClient storageClient;

    @Resource
    private UploadProperties uploadProperties;

    /**
    * 功能描述:     文件上传
    * @param multipartFile  要上传的文件
    * @return {@link String}    返回文件路径
    * @author hxxiapgy
    * @data 2020/7/9 22:05
    */
    public String upload(MultipartFile multipartFile){

        // 1.校验上传文件是否为空
        if (multipartFile == null){
            log.error("文件不存在");
            throw new RuntimeException("文件为空!");
        }

        // 1.校验文件类型
        //获取上传文件的类型
        String contentType = multipartFile.getContentType();
        if (!uploadProperties.getAllowTypes().contains(contentType)){
            log.info("文件类型不支持!");
            throw new RuntimeException("文件类型不支持!");
        }

        try {
            // 2.校验文件内容
            // 读取文件
            BufferedImage bufferedImage = ImageIO.read(multipartFile.getInputStream());
            if (bufferedImage == null || bufferedImage.getWidth() == 0 || bufferedImage.getHeight() == 0){
                log.error("上传文件有问题");
                throw new RuntimeException("上传文件有问题!");
            }
        } catch (IOException e) {
            log.error("检验文件内容失败...{}",e);
            throw new RuntimeException("检验文件内容失败...{}" + e.getMessage());
        }

        // 3.获取扩展名
        String extension = StringUtils.substringAfterLast(multipartFile.getOriginalFilename(),".");

        try {
            // 4.上传文件
            StorePath storePath  = storageClient.uploadFile(multipartFile.getInputStream(), multipartFile.getSize(), extension, null);
            // 5.返回路径
            return uploadProperties.getBaseUrl() + storePath.getFullPath();
        } catch (Exception e) {
            log.error("文件上传失败!...{}",e);
            throw new RuntimeException("文件上传失败...{}" + e.getMessage());
        }

    }

}
编写UploadController
/**
 * @description: 文件上传
 * @author: Hxxiapgy
 * @date: 2020/7/9 22:25
 */
@Controller
public class UploadController {

    @Resource
    private UploadService uploadService;

    /**
    * 功能描述:  文件上传
    * @param multipartFile 要删除的文件
    * @return {@link Map< String, Object>}
    * @author hxxiapgy
    * @data 2020/7/9 22:27
    */
    @PostMapping("/upload")
    @ResponseBody
    public Map<String,Object> upload(MultipartFile multipartFile){
        String filePath = uploadService.upload(multipartFile);

        Map<String,Object> map = new HashMap<>();
        map.put("code","200");
        map.put("msg", "文件上传成功");
        map.put("filePath", filePath);

        return map;
    }

}
编写测试入口

同方法一。

测试