1 分布式文件系统

  • 分布式文件系统(Distributed File System):文件系统管理的物理存储资源不一定直接连接在本地节点上,而是通过计算机网络与节点相连。
  • 传统文件管理系统管理的文件存储在本机。
  • 分布式文件管理系统管理的文件存储在很多台机器上,这些机器通过网络连接,被同一管理。无论是上传或者访问文件,都需要通过管理中心来访问。

2 FastDFS

  • FastDFS是由淘宝的余庆用C语言开发的一个轻量级、高性能的开源分布式文件系统。
  • 适合有大容量存储需求的应用或系统。
  • 功能:
    • 文件存储。
    • 文件同步。
    • 文件访问(上传、下载)。
    • 存取负载均衡。
    • 在线扩容。
  • 同类的分布式文件系统有谷歌的GFS、HDFS(Hadoop)、TFS(淘宝)。
  • 网站:happyfish100 - Overview

3 FastDFS架构

1、架构图

图片说明

  • Tracker Server:跟踪服务器,主要负责调度Storage节点与Client通信。在访问上起负载均衡、记录Storage节点的运行状态等作用,是连接Client和Storage节点的枢纽。
  • Storage Server:存储服务器,保存文件和文件的元数据(meta data)。每个storage server会启动一个单独的线程主动向Tracker cluster中每个Tracker server保存其状态信息,包括磁盘使用情况、文件同步情况以及文件上传下载次数统计等信息。
  • Group:文件组,多台Storage server的集群。上传一个文件到同组内的一台机器上后,FastDFS会将该文件即时同步到同组内的其他所有机器上,起到备份的作用。不同组的服务器,保存的数据不同,而且项目独立,不进行通信。
  • Tracker Cluster:跟踪服务器的集群,由一组跟踪服务器(Tracker Server)组成。
  • Storage Cluster:存储集群,由多个Group组成。

2、上传、下载流程

  • 上传:

图片说明

  • 上传步骤:
    1. Client通过Tracker查找可用的Storage。
    2. Tracker向Client返回一台可用的Storage的IP地址、端口号。
    3. Client直接通过该IP地址、端口号与其中一台Storage建立连接并进行文件上传。
    4. 上传完成,Storage返回一个文件ID。
  • 下载:

图片说明

  • 下载步骤:

    1. Client通过Tracker查找要下载文件所在的Storage。
    2. Tracker向Client返回包含该文件的Storage的IP地址、端口号。
    3. Client直接通过该IP地址、端口号与Storage建立连接。
    4. 下载指定文件。

    4 使用FastDFS客户端

    1、依赖

    <dependency>
        <groupId>com.github.tobato</groupId>
        <artifactId>fastdfs-client</artifactId>
        <version>${fastDFS.client.version}</version>
    </dependency>

    2、配置

    fdfs:
      so-timeout: 1501  # 超时时间
      connect-timeout: 601  # 连接超时时间
      thumb-image:  # 缩略图
        width: 60
        height: 60
      tracker-list:   # tracker地址
        - 192.168.56.101:22122

    3、配置类

    package com.leyou.upload.config;
    
    import com.github.tobato.fastdfs.FdfsClientConfig;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.annotation.EnableMBeanExport;
    import org.springframework.context.annotation.Import;
    import org.springframework.jmx.support.RegistrationPolicy;
    
    @Configuration
    @Import(FdfsClientConfig.class)
    @EnableMBeanExport(registration = RegistrationPolicy.IGNORE_EXISTING)// 解决jmx重复注册Bean的问题
    public class FastClientImporter {
    }

    4、Service

    package com.leyou.upload.service;
    
    import com.github.tobato.fastdfs.domain.StorePath;
    import com.github.tobato.fastdfs.service.FastFileStorageClient;
    import org.apache.commons.lang.StringUtils;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    import org.springframework.web.multipart.MultipartFile;
    
    import javax.imageio.ImageIO;
    import java.awt.image.BufferedImage;
    import java.io.File;
    import java.util.Arrays;
    import java.util.List;
    
    @Service
    public class UploadService {
        @Autowired
        private FastFileStorageClient storageClient;
    
        private static final List<String> CONTENT_TYPES = Arrays.asList("image/gif", "image/jpeg");
        private static final Logger LOGGER = LoggerFactory.getLogger(UploadService.class);
    
        public String uploadImage(MultipartFile file) {
            String originalFilename = file.getOriginalFilename();
            // 1、校验文件类型
            String contentType = file.getContentType();
            if (!CONTENT_TYPES.contains(contentType)) {
                LOGGER.info("文件类型不合法:{}", originalFilename);
                return null;
            }
            try {
                // 2、校验文件内容
                BufferedImage bufferedImage = ImageIO.read(file.getInputStream());
                if (bufferedImage == null) {
                    LOGGER.info("文件内容不合法:{}", originalFilename);
                    return null;
                }
                // 3、保存到服务器
                //file.transferTo(new File("E:\\image\\" + originalFilename));
                String ext = StringUtils.substringAfterLast(originalFilename, ".");
                StorePath storePath = this.storageClient.uploadFile(file.getInputStream(), file.getSize(), ext, null);
                // 4、返回url
                //return "http://image.leyou.com/" + originalFilename;
                return "http://image.leyou.com/" + storePath.getFullPath();
            } catch (Exception e) {
                LOGGER.info("服务器内部错误:{}", originalFilename);
                e.printStackTrace();
            }
            return null;
        }
    }