1、FastDFS简介

为什么

    1. 在分布式集群环境下,文件上传至节点A,这时通过负载均衡算法,访问到节点B,则不能访问到文件,这时会出现有时能访问有时不能访问的问题
    1. 同时要考虑为文件做冗余备份、负载均衡、线性扩容等功能,这些都是单节点文件上传所不具备的

      FastDFS体系结构

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

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

FastDFS 架构包括 Tracker server 和 Storage server。客户端请求 Tracker server 进行文件上传、下载,通过Tracker server 调度最终由 Storage server 完成文件上传和下载。

Tracker server 作用是负载均衡和调度,通过 Tracker server 在文件上传时可以根据一些策略找到Storage server提供文件上传服务。可以将 tracker 称为追踪服务器或调度服务器。Storage server 作用是文件存储,客户端上传的文件最终存储在 Storage 服务器上,Storageserver 没有实现自己的文件系统而是利用操作系统的文件系统来管理文件。可以将storage称为存储服务器。
第2节 FastDFS - 图1
上传流程
image.png
客户端上传文件后存储服务器将文件 ID 返回给客户端,此文件 ID 用于以后访问该文件的索引信息。文件索引信息包括:组名,虚拟磁盘路径,数据两级目录,文件名。
image.png

  • 组名:文件上传后所在的 storage 组名称,在文件上传成功后有storage 服务器返回,需要客户端自行保存。
  • 虚拟磁盘路径:storage 配置的虚拟路径,与磁盘选项store_path*对应。如果配置了store_path0 则是 M00,如果配置了 store_path1 则是 M01,以此类推。
  • 数据两级目录:storage 服务器在每个虚拟磁盘路径下创建的两级目录,用于存储数据文件。
  • 文件名:与文件上传时不同。是由存储服务器根据特定信息生成,文件名包含:源存储服务器 IP 地址、文件创建时间戳、文件大小、随机数和文件拓展名等信息。

    2、FastDFS安装

    我们使用Docker安装FastDFS
    拉取镜像
    1. docker pull morunchang/fastdfs
    image.png
    运行tracker
    1. docker run -d --name tracker --net=host morunchang/fastdfs sh tracker.sh
    image.png
    运行storage ```bash docker run -d —name storage —net=host -e TRACKER_IP=:22122 -e GROUP_NAME= morunchang/fastdfs sh storage.sh

docker run -d —name storage —net=host -e TRACKER_IP=192.168.80.133:22122 -e GROUP_NAME=group1 morunchang/fastdfs sh storage.sh

  1. ![image.png](https://cdn.nlark.com/yuque/0/2020/png/2210628/1604724270802-191ceeb0-2490-465d-b966-e4511270283b.png#align=left&display=inline&height=71&margin=%5Bobject%20Object%5D&name=image.png&originHeight=141&originWidth=1371&size=25000&status=done&style=none&width=685.5)
  2. - 使用的网络模式是–net=hosthost模式可以不用映射容器端口宿主机, <your tracker server address>替换为你机器的Ip即可
  3. - <group name>是组名,即storage的组
  4. - 如果想要增加新的storage服务器,再次运行该命令,注意更换新组名
  5. 修改nginx的配置<br />进入storage的容器内部,修改nginx.conf
  6. ```bash
  7. docker exec -it storage /bin/bash

image.png
进入后

vi /etc/nginx/conf/nginx.conf

我们可以先cd /etc/nginx/conf/ 看一下:
image.png
添加以下内容(已经存在,不用改)

location ~ /M00 {
  root /data/fast_data/data;
  ngx_fastdfs_module;
}

image.png
禁止缓存:

add_header Cache-Control no-store;

注意还是在,这个M00中添加:
image.png
退出容器

exit

重启storage容器

docker restart storage

image.png
查看tracker.conf和storage.conf配置文件

docker exec -it storage /bin/bash
cd /etc/fdfs
vim tracker.conf
vim storage.conf

image.png
image.png
image.png
image.png

3、文件上传微服务

搭建项目

创建文件上传微服务upload-service,通过fastdfs-client组件实现文件上传和删除的功能
(1)在spring cloud项目中创建子工程,修改pom.xml,引入依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>springCloud-bill-manager-6</artifactId>
        <groupId>com.zh</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>upload-service</artifactId>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <!-- FastDFS依赖 -->
        <dependency>
            <groupId>com.github.tobato</groupId>
            <artifactId>fastdfs-client</artifactId>
            <version>1.26.7</version>
        </dependency>
    </dependencies>


</project>

(2)application.yml

server:
  port: 9004
logging:
  #file: demo.log
  pattern:
    console: "%d - %msg%n"
  level:
    org.springframework.web: debug
    com.zh: debug

spring:
  application:
    name: upload-service
  servlet:
    multipart:
      enabled: true
      max-file-size: 10MB #单个文件上传大小
      max-request-size: 20MB #总文件上传大小

fdfs:
  # 链接超时 这里设置大点,先测试行不行在调整,时间短容易超时
  connect-timeout: 600
  # 读取时间 这里设置大点,先测试行不行在调整,时间短容易超时
  so-timeout: 600
  # 生成缩略图参数
  thumb-image:
    width: 150
    height: 150
  tracker-list: 192.168.80.133:22122

eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:10086/eureka
  instance:
    #更倾向于使用ip地址,而不是主机名
    prefer-ip-address: true
    #ip地址
    ip-address: 127.0.0.1
    #续约间隔,默认30秒
    lease-renewal-interval-in-seconds: 5
    #服务的实效时间, 默认90秒
    lease-expiration-duration-in-seconds: 5

(3)启动类UploadApplication

package com.zh.upload;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

@SpringBootApplication
@EnableDiscoveryClient
public class UploadApplication {
    public static void main(String[] args) {
        SpringApplication.run(UploadApplication.class, args);
    }
}

(4) FastDfs配置类DfsConfig

package com.zh.upload.config;

import com.github.tobato.fastdfs.FdfsClientConfig;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;

@Configuration
@Import(FdfsClientConfig.class)
public class DfsConfig {
}

工具类FileDfsUtil

调用fastdfs-client工具方法实现文件上传和删除

package com.zh.upload.config;

import com.github.tobato.fastdfs.domain.fdfs.StorePath;
import com.github.tobato.fastdfs.service.FastFileStorageClient;
import org.apache.commons.io.FilenameUtils;
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;

@Component
public class FileDfsUtil {
    private static final Logger LOGGER = LoggerFactory.getLogger(FileDfsUtil.class);
    @Resource
    private FastFileStorageClient storageClient;

    /**
     * 上传文件
     */
    public String upload(MultipartFile multipartFile) throws Exception {
        String originalFilename = FilenameUtils.getExtension(multipartFile.getOriginalFilename());
        StorePath storePath = storageClient.uploadImageAndCrtThumbImage(
                multipartFile.getInputStream(), multipartFile.getSize(), originalFilename, null);
        return storePath.getFullPath(); // "/group1/M00/00/00/wKjcZF8ekIWAEyMAAABzzes71pI891.jpg"
    }

    /**
     * 删除文件
     */
    public void deleteFile(String fileUrl) {
        if (StringUtils.isEmpty(fileUrl)) {
            LOGGER.info("fileUrl == >>文件路径为空...");
            return;
        }
        try {
            StorePath storePath = StorePath.parseFromUrl(fileUrl);
            storageClient.deleteFile(storePath.getGroup(), storePath.getPath());
        } catch (Exception e) {
            LOGGER.info(e.getMessage());
        }
    }
}

FileController

创建文件上传和删除功能的controller,实现文件删除

package com.zh.upload.controller;

import com.zh.upload.config.FileDfsUtil;
import org.springframework.http.ResponseEntity;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;

/**
 * @author zlz1314
 */
@RestController
public class FileController {
    @Resource
    private FileDfsUtil fileDfsUtil;

    /**
     * http://192.168.80.133:8080/group1/M00/00/00/wKhQhV-mOuqAEzbLAAYTEeRHeZU275.jpg
     */
    @RequestMapping(value = "/uploadFile", headers = "content-type=multipart/form-data", method = RequestMethod.POST)
    public ResponseEntity<String> uploadFile(@RequestParam("file") MultipartFile file) {
        String result;
        try {
            String path = fileDfsUtil.upload(file);
            if (!StringUtils.isEmpty(path)) {
                result = path;
            } else {
                result = "上传失败";
            }
        } catch (Exception e) {
            e.printStackTrace();
            result = "服务异常";
        }
        return ResponseEntity.ok(result);
    }

    /**
     * param: filePathName = "group1/M00/00/00/wKhQhV-mOuqAEzbLAAYTEeRHeZU275.jpg" ;
     * 文件删除
     */
    @RequestMapping(value = "/deleteByPath", method = RequestMethod.GET)
    public ResponseEntity<String> deleteByPath(String filePathName) {
        fileDfsUtil.deleteFile(filePathName);
        return ResponseEntity.ok("SUCCESS");
    }
}

postman测试

上传测试

报错:
com.github.tobato.fastdfs.exception.FdfsConnectException: 无法获取服务端连接资源:can’t create connection to/192.168.80.133:22122
简单粗暴的方式:把防火墙关了:

查看防火墙状态
#  Stopped firewalld - dynamic firewall daemon. 我是 centos7:说明当前防火墙已经是关闭状态了
systemctl status firewalld

暂时关闭防火墙
systemctl stop firewalld

永久关闭防火墙
systemctl disable firewalld

重启防火墙
systemctl enable firewalld

然后报错:
无法获取服务端连接资源:找不到可用的tracker /192.168.80.133:22122
这个可能是虚拟机那边的问题,使用docker exec -it storage /bin/bash,然后之后退出查看一下docker ps,
反正就是得exec一下才行,就奇怪
image.png

然后又报错:
java.net.SocketTimeoutException: Read timed out
。。。
com.github.tobato.fastdfs.exception.FdfsIOException: 客户端连接服务端出现了io异常:socket io exception occured while receive content
起始就是最上边说的那个超时了,这是因为配置文件中时间设置的短了:
image.png
把它都调大,比如我这里都改成600,毕竟测试嘛
image.png
然后就成功了:注意看响应时间,足足1373ms,,,

image.png然后在浏览器中访问:
image.png
在容器中查看:
image.png

删除测试
image.png
浏览器查看:
image.png虚拟机查看:
大图已经删掉了,小图还有。
image.png