场景:天猫、淘宝等购物网站,大量的图片和视频,文件太多,如何存储呢? 用户访问量大又如何保证下载速度?分布式文件系统就是解决这些问题的。

什么是文件系统

思考:文件数据如何存取?
image.png

分布式文件系统

一台电脑存储量有限,并且并发吞吐量也有限,如何提高性能呢?
集群:同一个业务部署在多台服务器上,提高系统的可用性
分布式:不同的业务模块部署在不同的服务器上或者同一个业务模块拆分多个子业务,部署不同的服务器上,解决高并发的问题
例如:
小饭馆,只有一个厨师,后来客人越来越多,一个厨师忙不过来,只能再请一个厨师,两个厨师都能炒菜,也就是两个厨师的作用是一样的,那么这两个厨师的关系就是“集群”。
为了让厨师专心炒菜,把菜炒到极致,又请了配菜师负责切菜、备料等工作,厨师和配菜师的关系就是“分布式”
一个配菜师忙不过来,要提供两份食材给两个厨师,又请了一个配菜师,两个配菜师就是“集群”。

目前主流的分布式文件系统:

  • HDFS : Hadoop 分布式文件系统

    • 高容错性系统,适合部署到廉价的机器上
    • 能提供高吞吐量的数据访问,非常适合大规模数据应用
    • HDFS采用主从结构,一个HDFS是由一个name节点和N个data节点组成
    • name节点存储元数据,一个文件分割成N份,存储在不同的data节点上
  • GFS (Google File System)

    • 可扩展的分布式文件系统,用于大型的分布式,对大量数据进行访问的应用
    • 运行于廉价的普通硬件上,可提供容错功能
    • 它可以给大量的用户提供总体性能较高的服务
    • GFS采用主从结构,一个GFS集群由一个master和大量的chunkserver(分块服务器)组成
    • 一个文件被分割若干块,分散存储到多个分块Server中
  • FastDFS

    • 由淘宝资深架构师余庆编写并开源
    • 转为互联网量身定制,充分考虑了冗余备份、负载均衡、线性扩容等机制,并注重高可用、高性能等指标,使用FastDFS很容易搭建一套高性能的文件服务器集群提供文件上传、下载等服务
    • HDFS、GFS等都是通用的文件系统,他们的优点是开发体验好、但是系统的复杂度较高、性能也一般;
    • 相比之下,专用的分布式系统体验差,但是复杂度低,性能也高,尤其fastDFS特别适合图片,小视频等小文件,因为fastDFS对文件是不分割的,所以没有文件合并的开销
    • 网络通信用socket,速度快

工作原理

  • fastDFS包含Tracker Server和Storage Server;
  • 客户端请求Tracker Server进行文件的上传与下载;
  • Tracker Server调度Storage Server最终完成上传与下载

image.png

  • Tracker : 作用负载均衡和调度,它管理着存储服务(Storage Server) 可以理解为:“大管家、追踪者、调度员”。Tracker Server可以集群,实现高可用,策略为“轮询”。
  • Storage :作用是文件存储,客户端上传的文件最终存储到Storage服务器上;Storage集群采用分组的方式,同组内的每台服务器是平等关系,数据同步,目的是实现数据备份,从而高可用,而不同组的服务器之间是不通信的;同组内的每台服务器的存储量不一致的情况下,会选取容量最小的那个,所以同组内的服务器之间软硬件最好保持一致;Storage Server会连接集群中的所有Tracker Server 定时向他们汇报自己的状态,例如:剩余空间、文件同步情况,文件上传下载次数等等信息。

上传和下载原理

image.png
image.png
客户端文件上传后,storage会将文件id返回给客户端
group1/M00/002/11/ajxasS234234242ASDSD.sh

  • 组名:文件上传后,在storage组的名称,文件上传成功后,由Storage返回,客户端需要自行保存
  • 虚拟磁盘的路径:
    • Storage配置的虚拟机路径在:storage_path对应
    • storage_path0对应着M00
    • storage_path1对应着M01
  • 数据两级目录:

Storage在虚拟磁盘下,自行创建的目录

  • 文件名:

与上传时不同,是用storage根据特定信息生成的,里面包含:strong的ip,创建时间戳、大小、后缀名等信息

FastDFS 安装与使用

fastDFS 只能安装在Linux系统上。

  • Linux必须安装gcc环境yum install -y gcc gcc-c++
  • 安装libevent 运行时环境

yum -y install libevent

  • 安装listfastcommon 由fastDFS官方提供的基础库
    • 首先安装解压zip包的软件yum install -y unzip
    • 将如下资源,解压到/opt/目录下libfastcommon.zip unzip libfastcommon.zip
    • 然后进入libfastcommon-master 目录下,执行make命令./make.sh 如果没有权限设置执行权限chmod 777 make.sh
    • 执行./make.sh install 安装
    • 将/usr/lib64/libfastcommon.so 文件拷贝到/usr/lib下,以防万一

image.png

  • 安装Tracker

将如下文件上传到/opt/目录下,进行解压安装
FastDFS_v5.05.tar.gz
执行如下命令:

  1. cd FastDFS/
  2. ./make.sh
  3. ./make.sh install

安装成功之后,将安装目录下的conf/目录,拷贝到/etc/fdfs/

cp conf/* /etc/fdfs/

配置

配置Tracker,在/etc/fdfs/目录下,编辑vi tracker.conf

# 修改基础目录,需要自行创建fastdfs目录
base_path=/home/fastdfs

进入home目录,创建fastdfs目录

  cd /home/
 mkdir fastdfs

配置storage,编辑vi storage.conf 同样修改基础目录以及storage_path0

#配置组名
group_name=group1
#端口
port=23000
#向tracker心跳间隔(秒)
heart_beat_interval=30
#storage基础目录
#目录不存在,需要自行创建
base_path=/home/fastdfs
#store存放文件的位置(store_path)
#可以理解一个磁盘一个path,多个磁盘,多个store_path
#fdfs_storage目录不存在,需要自行创建
#mkdir/home/fastdfs/fdfs_storage
store_path0=/home/fastdfs/fdfs_storage
#如果有多个挂载磁盘则定义多个store_path,如下
#store_path1=.....(M01)
#store_path2=.....(M02)
#配置tracker服务器:IP
tracker_server=10.1.220.247:22122 # 改成虚拟机的IP地址
#如果有多个则配置多个tracker
#trackerserver=10.1.220.x:22122

启动服务

  • 启动tracker服务

    /usr/bin/fdfs_trackerd /etc/fdfs/tracker.conf restart
    
  • 启动storage服务

    /usr/bin/fdfs_storaged /etc/fdfs/storage.conf restart
    

    输入命令netstat -ntlp查看启动的服务端口
    image.png
    注意:防火墙开放端口,或者直接关闭防火墙

文件上传实现

创建maven项目,引入fastdfs依赖

    <dependencies>
        <!-- fastdfs 的Java客户端 -->
        <dependency>
            <groupId>net.oschina.zcx7878</groupId>
            <artifactId>fastdfs-client-java</artifactId>
            <version>1.27.0.0</version>
        </dependency>
        <dependency>
            <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
            <version>1.3.2</version>
        </dependency>
    </dependencies>

配置文件:config/fastdfs-client.properties 名字最好不要动

##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=172.16.150.130:22122

编写客户端测试上传:

import org.csource.common.MyException;
import org.csource.common.NameValuePair;
import org.csource.fastdfs.*;

import java.io.IOException;

/**
 * 文件上传
 */
public class TestUpload {

    public static void main(String[] args) {
        try {
            //加载配置文件
            ClientGlobal.initByProperties("config/fastdfs-client.properties");
            //创建Tracker客户端
            TrackerClient trackerClient = new TrackerClient();
            //通过Tracker客户端获取Tracker服务并返回
            TrackerServer trackerServer = trackerClient.getConnection();
            //声明Storage服务
            StorageServer storageServer = null;
            //定义Storage客户端
            StorageClient1 storageClient1 = new StorageClient1(trackerServer, storageServer);
            //定义文件元信息
            NameValuePair[] list = new NameValuePair[1];
            list[0] = new NameValuePair("fileName", "avatar.png");
            //上传文件
            String fileId = storageClient1.upload_file1("/Users/prim/Downloads/avatar.png", "png", list);
            System.out.println(fileId);//group1/M00/00/00/rBCWgmAX7DiAV1vDAAH6S0vtgAQ068.png
            //group1:一台服务器,就是一个组
            //M00:对应的目录/home/fastdfs/fdfs_storage/data 在之前配置时进行设置的
            //00/00:两级数据目录
            //关闭Tracker
            trackerServer.close();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (MyException e) {
            e.printStackTrace();
        }
    }
}

图片在服务器的位置:
image.png

文件查询

import org.csource.common.MyException;
import org.csource.fastdfs.*;

import java.io.IOException;

/**
 * 文件查询
 */
public class TestQuery {
    public static void main(String[] args) {
        try {
            //加载配置文件
            ClientGlobal.initByProperties("config/fastdfs-client.properties");
            //创建Tracker客户端
            TrackerClient trackerClient = new TrackerClient();
            //通过Tracker客户端获取Tracker服务并返回
            TrackerServer trackerServer = trackerClient.getConnection();
            //声明Storage服务
            StorageServer storageServer = null;
            //定义Storage客户端
            StorageClient1 storageClient1 = new StorageClient1(trackerServer, storageServer);

            FileInfo fileInfo = storageClient1.query_file_info1("group1/M00/00/00/rBCWgmAX7DiAV1vDAAH6S0vtgAQ068.png");

            if (fileInfo != null) {
                System.out.println(fileInfo);
            } else {
                System.out.println("查无此文件");
            }
            // 返回信息如下:
            // source_ip_addr = 172.16.150.130, 
            // file_size = 129611, 
            // create_timestamp = 2021-02-01 19:55:36, 
            // crc32 = 1273856004
            trackerServer.close();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (MyException e) {
            e.printStackTrace();
        }
    }
}

文件下载

import org.csource.common.MyException;
import org.csource.fastdfs.*;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;

public class TestDownload {
    public static void main(String[] args) {
        try {
            //加载配置文件
            ClientGlobal.initByProperties("config/fastdfs-client.properties");
            //创建Tracker客户端
            TrackerClient trackerClient = new TrackerClient();
            //通过Tracker客户端获取Tracker服务并返回
            TrackerServer trackerServer = trackerClient.getConnection();
            //声明Storage服务
            StorageServer storageServer = null;
            //定义Storage客户端
            StorageClient1 storageClient1 = new StorageClient1(trackerServer, storageServer);

            byte[] bytes = storageClient1.download_file1("group1/M00/00/00/rBCWgmAX7DiAV1vDAAH6S0vtgAQ068.png");
            //文件的id
            //通过IO流将字节数组转换成一个文件
            FileOutputStream outputStream = new FileOutputStream(new File("/Users/prim/xxx.png"));
            outputStream.write(bytes);
            outputStream.close();
            trackerServer.close();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (MyException e) {
            e.printStackTrace();
        }
    }
}

ok,在对应的目录找到了对应的图片,图片下载完毕
image.png

搭建图片服务器

Nignx模块安装Storage

  1. 上传如下文件,上传到服务器到/opt/目录下 解压文件

fastdfs-nginx-module_v1.16.tar.gz

  1. 进入到解压的目录中/opt/fastdfs-nginx-module/src

编辑配置文件:vi config/usr/local/xxx 改为/usr/xxx

ngx_addon_name=ngx_http_fastdfs_module
HTTP_MODULES="$HTTP_MODULES ngx_http_fastdfs_module"
NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/ngx_http_fastdfs_module.c"
CORE_INCS="$CORE_INCS /usr/include/fastdfs /usr/include/fastcommon/"
CORE_LIBS="$CORE_LIBS -L/usr/lib -lfastcommon -lfdfsclient"
CFLAGS="$CFLAGS -D_FILE_OFFSET_BITS=64 -DFDFS_OUTPUT_CHUNK_SIZE='256*1024' -DFDFS_MOD_CONF_FILENAME='\"/etc/fdfs/mod_fastdfs.conf\"'"
  1. fastdfs-nginx-module/src 下的mod_fastdfs.conf 拷贝至/etc/fdfs

    cp mod_fastdfs.conf  /etc/fdfs/
    

    image.png

  2. 修改etc/fdfs/mod_fastdfs.conf ```shell vi mod_fastdfs.conf

修改的配置如下:

the base path to store log files

base_path=/home/fastdfs tracker_server=172.16.150.130:22122

n个Tracker配置n行即可

url_have_group_name = true store_path0=/home/fastdfs/fdfs_storage


6. 将`libfdfsclient.so` 拷贝至`/usr/lib/` 目录下
```shell
 cp /usr/lib64/libfdfsclient.so /usr/lib
  1. 创建nginx/client目录

    mkdir -p /var/temp/nginx/client
    

    Nginx安装Tracker

  2. 安装Nginx:

Nginx 快速入门
查询如下依赖库是否安装:

yum install pcre
yum install pcre-devel
yum install zlib
yum install zlib-devel
yum install openssl
yum install openssl-devel
  1. 进入到nginx目录,安装注意要添加fastdfs-nginx-module,如果之前安装过nginxrm -rf Makefile ,然后执行如下命令: :::tips 注意:如果你之前安装Nginx没有安装fastdfs-nginx-module,则在如下的配置文件,启动nginx会包错误:unknown directive ngx_fastdfs_module in nginx.conf88 :::

    ./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 \
    --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=/opt/fastdfs-nginx-module/src
    
  2. 拷贝配置文件

    [root@localhost conf]# cd /opt/FastDFS/conf/
    [root@localhost conf]# cp http.conf mime.types /etc/fdfs/
    cp: overwrite ‘/etc/fdfs/http.conf’? yes
    cp: overwrite ‘/etc/fdfs/mime.types’? yes
    
  3. 修改nginx配置文件

    pid        /usr/local/nginx/logs/nginx.pid;
    
    server {
     listen       80;
     server_name  172.16.150.130;
     #charset koi8-r;
     #access_log  logs/host.access.log  main;
     location /group1/M00 {
         root   /home/fastdfs/fdfs_storage/data;
         ngx_fastdfs_module;
    }
    }
    
  4. 启动Nginx

    [root@localhost sbin]# pkill -9 nginx
    [root@localhost sbin]# /usr/local/nginx/sbin/nginx -c /usr/local/nginx/conf/nginx.conf
    ngx_http_fastdfs_set pid=89078
    
  5. 测试访问,FastDFS中存储的图片:ip地址后面拼接的就是存储图片返回的fileID

http://172.16.150.130/group1/M00/00/00/rBCWgmAX7DiAV1vDAAH6S0vtgAQ068.png
image.png

Java端实现

依赖包引入

 <packaging>war</packaging>
    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.11</version>
            <scope>test</scope>
        </dependency>
        <!--因为有jsp页面,所以引用servlet依赖-->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>servlet-api</artifactId>
            <scope>provided</scope>
            <version>2.5</version>
        </dependency>
        <!--页面提交过来的请求,使用springmvc来处理-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>5.2.7.RELEASE</version>
        </dependency>
        <!--java连接fastDFS的客户端工具-->
        <dependency>
            <groupId>net.oschina.zcx7878</groupId>
            <artifactId>fastdfs-client-java</artifactId>
            <version>1.27.0.0</version>
        </dependency>
        <!--图片上传到FastDFS需要用的到IO工具-->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-io</artifactId>
            <version>1.3.2</version>
        </dependency>
        <!--图片保存到web服务器需要用到的IO工具-->
        <dependency>
            <groupId>commons-fileupload</groupId>
            <artifactId>commons-fileupload</artifactId>
            <version>1.3.1</version>
        </dependency>
        <!--用来转换java对象和json字符串,注意,2.7以上版本必须搭配spring5.0以上-->
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.9.8</version>
        </dependency>
    </dependencies>
  1. 前端页面部分

    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    <html>
    <head>
     <title>上传图片</title>
    </head>
    <body>
    <%--上传图片,文件与文字相比较起来输入内容较大必须使用post提交--%>
    <%--上传文件和普通文本有区别,action接收参数也会区别对待,所以声明带文件提交的表单为多部件表单--%>
    <form action="upload" method="post" enctype="multipart/form-data">
     <input type="file" name="fname">
     <br>
     <button>提交</button>
    </form>
    </body>
    </html>
    
  2. spring-mvc配置

    <?xml version="1.0" encoding="UTF-8" ?>
    <beans xmlns="http://www.springframework.org/schema/beans"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:context="http://www.springframework.org/schema/context"
        xmlns:mvc="http://www.springframework.org/schema/mvc"
        xsi:schemaLocation="
         http://www.springframework.org/schema/beans
         http://www.springframework.org/schema/beans/spring-beans.xsd
         http://www.springframework.org/schema/context
         http://www.springframework.org/schema/context/spring-context.xsd
         http://www.springframework.org/schema/mvc
         http://www.springframework.org/schema/mvc/spring-mvc.xsd">
     <!-- 扫描注解 -->
     <context:component-scan base-package="controller"/>
     <!--    扫描控制器中的注解:@Response-->
     <mvc:annotation-driven/>
    
     <!-- 上传文件的解析器-规定上传文件的大小限制 -->
     <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
         <!-- 限制文件最大2G -->
         <property name="maxUploadSize" value="2048000000"/>
     </bean>
    </beans>
    

    web.xml配置

    <?xml version="1.0" encoding="UTF-8"?>
    <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
          version="4.0">
     <servlet>
         <servlet-name>dispatcherServlet</servlet-name>
         <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
         <init-param>
             <param-name>contextConfigLocation</param-name>
             <param-value>classpath:spring/spring-mvc.xml</param-value>
         </init-param>
         <load-on-startup>2</load-on-startup>
     </servlet>
     <servlet-mapping>
         <servlet-name>dispatcherServlet</servlet-name>
         <url-pattern>/</url-pattern>
     </servlet-mapping>
    </web-app>
    
  3. 定义文件实体类

    public class FileSystem implements Serializable {
     private String fileId;
     private String filePath;
     private String fileName;
    }
    
  4. 连接fastDFS的配置文件config/fastdfs-client.properties

    ##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=172.16.150.130:22122
    
  5. 编写控制器类

    /**
    * @program: ImageServer
    * @Description: 处理上传文件控制器
    * @author: sufulu
    * @version: 1.0.0
    * @create: 2021-02-01 21:40
    * @PackageName: controller
    * @ClassName: FilController.java
    **/
    @Controller
    public class FilController {
    
     /**
      * @param request MultipartHttpServletRequest是HttpServletRequest的强化版本 不仅可以装文本信息还可以装图片文件信息
      * @return
      * @throws Exception 上传流程:
      *                   1. 先把文件保存到web服务器上
      *                   2. 再从web服务上将文件上传到fastDFS上
      */
     @RequestMapping("upload")
     @ResponseBody
     public FileSystem upload(MultipartHttpServletRequest request) throws Exception {
         FileSystem fileSystem = new FileSystem();
    
         //1. 先把文件保存到web服务器上
         //从页面请求中获取,拿上传的文件对象
         MultipartFile file = request.getFile("fname");
         //从文件对象中获取文件的原始名称
         String oldFilename = file.getOriginalFilename();
         //获取后缀名 通过字符串截取获得
         String suffixName = oldFilename.substring(oldFilename.lastIndexOf(".") + 1);
         //重新生成文件名,避免文件同名而覆盖
         String newFileName = UUID.randomUUID().toString() + "." + suffixName;
         //创建web服务器保存文件的目录
         File webSaveFile = new File("/Users/prim/Downloads/" + newFileName);
         //将路径转换成文件
         file.transferTo(webSaveFile);
         //获取服务器的绝对路径
         String webPath = webSaveFile.getAbsolutePath();
    
         //2. 再从web服务上将文件上传到fastDFS上
         ClientGlobal.initByProperties("config/fastdfs-client.properties");
         TrackerClient trackerClient = new TrackerClient();
         TrackerServer trackerServer = trackerClient.getConnection();
         StorageServer storageServer = null;
         StorageClient1 client1 = new StorageClient1(trackerServer, storageServer);
         NameValuePair[] nameValuePairs = new NameValuePair[1];
         nameValuePairs[0] = new NameValuePair("fileName", oldFilename);
         String fileId = client1.upload_file1(webPath, suffixName, nameValuePairs);
         trackerServer.close();
    
         //3. 封装返回的数据对象
         fileSystem.setFileId(fileId);
         fileSystem.setFileName(oldFilename);
         fileSystem.setFilePath(fileId);//已经上传到fastDFS上通过fileId来访问图片
         return fileSystem;
     }
    }
    

    测试上传图片:
    image.png
    拿到fileId,然后通过之前设置的nginx配置访问:http://172.16.150.130/group1/M00/00/00/rBCWgmAYDFyAMx5RAAH6S0vtgAQ271.png FastDFS中的图片