乐趣区

FastDFS-Docker化部署-以及-Java-SpringMVC实践

简介

FastDFS 是一个轻量级分布式文件系统。可以对文件进行管理,功能包括:文件存储、文件同步、文件访问(文件上传、文件下载)等,而且可以集群部署,有高可用保障。相应的竞品有 Ceph、TFS 等。相比而言 FastDFS 对硬件的要求比较低,所以适合中小型公司。

概念

FastDFS 服务端由两个重要部分组成:跟踪器(Tracker)和存储节点(Storage)。

Tracker 主要做调度工作,在访问上起负载均衡的作用。Tracker 可以做集群部署,各个节点之间是平等的,客户端请求时采用轮询机制,某个 Tracker 不能提供服务时就换另一个。Storage 启动后会连接到 Tracker Server 告知自己的 Group 信息,形成映射关联,并采用心跳机制保持状态。
Storage 存储节点负责文件的存储,Storage 可以集群部署。

Storage 集群有以下特点:

  • 以组(Group)为单位(也有称呼为卷 Volume 的),集群的总容量为所有组的集合。
  • 一个卷(组)内 storage server 之间相互通信,文件进行同步,保证卷内 storage 完全一致,所以一个卷的容量以最小的服务器为准。不同的卷之间相互不通信。
  • 当某个卷的压力较大时可以添加 storage server(纵向扩展),如果系统容量不够可以添加卷(横向扩展)。

上传流程

此章节根据资料整理,可能随着版本有所改变,这里只介绍大致的,以便了解整个运作流程。如果需要深入研究,建议还是以官方文档为标准。

一,客户端请求会打到负载均衡层,到 tracker server 时,由于每个 server 之间是对等的关系,所以可以任意选择一个 tracker server。

二,到 storage 层:tracker server 接收到 upload file 请求时,会为该请求分配一个可以存储该文件的 group。

分配 group 规则:

  • Round robin 轮询
  • Specified group 指定一个 group
  • Load balance 剩余存储空间多的 group 优先

三,确定 group 后,tracker 会在 group 内选择一个 storage server 给客户端。

在 group 内选择 storage server 时规则:

  • Round robin 轮询
  • First server ordered by ip 按 ip 排序
  • First server ordered by priority,按优先级排序(优先级在 storage 上配置)

四,选择 storage path:当分配好 storage server 后,客户端向 storage 发送写文件请求,storage 将会为文件分配一个数据存储目录,支持规则如下:

  • round robin 轮询
  • 剩余存储空间最多的优先

五,生成 File id:选定存储目录之后,storage 会为文件生成一个 File id。规则如下:

 由 storage server ip、文件创建时间、文件大小,文件 crc32 和一个随机数拼接而成,然后将这个二进制串进程 base64 编码,转换为可打印的字符串。

六,选择两级目录:每个存储目录下有两级 256 * 256 的子目录,storage 会按文件 Field 进行两次 hash,路由到其中的一个目录,然后将文件以 file id 为文件名存储到该子目录下。

一个文件路径最终由如下组成:组名 / 磁盘 / 目录 / 文件名

七,客户端 upload file 成功后,会拿到一个 storage 生成的文件名,接下来客户端根据这个文件名即可访问到该文件。

下载流程

下载流程如下:

一,选择 tracker server:和 upload file 一样,在 download file 时随机选择 tracker server。

二,选择 group:tracker 发送 download 请求给某个 tracker,必须带上文件名信息,tracker 从文件名中解析出 group、大小、创建时间等信息,根据 group 信息获取对于的 group。

三,选择 storage server:从 group 中选择一个 storage 用来服务读请求。由于 group 内的文件同步时在后台异步进行的,所以有可能出现在读到的时候,文件还没有同步到某些 storage server 上,为了尽量避免反问道这样的 storage,tracker 按照一定的规则选择 group 内可读的 storage。

文件 HTTP 预览服务

Storage 还可以结合 nginx 的 fastdfs-nginx-module 提供 http 服务,以实现图片等预览功能。

这个部分这里不做介绍,后续可能单独写篇文章,因为我发现对 fastDFS 集群提供 http 服务还是挺复杂,包括我下面找的 docker 镜像都不完善,主要是规划的问题,包括衍生的服务,缓存,以及对图片的处理(nginx+lua)这些,后续打算研究下,重新开源个 docker 构建镜像。

实战

安装、部署规划

FastDFS 安装方法网上有很多教程,这里不多讲,我建议使用 docker 来运行 FastDFS,可以自己根据安装步骤构建自己的镜像。然后在需要的机器直接运行,后续扩容也方便,再启动一个 storage 容器就可以了。

详细版安装推荐篇文章:https://segmentfault.com/a/11…

Docker 集群搭建

我这里从 github 上找的一个别人构建好的镜像,可以直接使用。地址:https://github.com/luhuiguo/f…

使用方法也很简单


# 启动一个 tracker 服务器
docker run -dti --network=host --name tracker -v /var/fdfs/tracker:/var/fdfs luhuiguo/fastdfs tracker

# 启动 storage0
docker run -dti --network=host --name storage0 -e TRACKER_SERVER=10.1.5.85:22122 -v /var/fdfs/storage0:/var/fdfs luhuiguo/fastdfs storage

# 再启动一个 storage1
docker run -dti --network=host --name storage1 -e TRACKER_SERVER=10.1.5.85:22122 -v /var/fdfs/storage1:/var/fdfs luhuiguo/fastdfs storage

# 启动一个新组的 storage
docker run -dti --network=host --name storage2 -e TRACKER_SERVER=10.1.5.85:22122 -e GROUP_NAME=group2 -e PORT=22222 -v /var/fdfs/storage2:/var/fdfs luhuiguo/fastdfs storage

部署注意点

1,原 github 地址上的 usage 介绍,启动 storage0 和 storage1 有一个参数错误 (多一个 -e),以我上面发的命令为准。
2,这里的 TRACKER_SERVER 注意改为你自己的,同一个网段内网 ip。

3,实际上这里 docker 容器之间还是同一个物理主机上部署的 (根据 network 而言),虽然后续可以通过加硬盘,然后新建 storage 绑定到新加硬盘 mount 上,但是如果是大公司的生产环境还是推荐建立一个 overlay 网络,具体见:https://www.cnblogs.com/bigbe…,这样可以直接扩物理机集群了。另外这里也提供 docker-compose 方式启动服务,实际也不推荐使用,因为 tracker 和 storage server 以后必然是分开的,所以还是推荐单个 docker 容器保持灵活性。这里高级点可以用 k8s 进行自动扩容(后续打算重新开源个镜像)。

Java 实践

导入需要包

这里使用官方的客户端包:https://github.com/happyfish1…

# 下载源码
git clone https://github.com/happyfish100/fastdfs-client-java.git

cd fastdfs-client-java

# 打 jar 包
mvn clean install

# 输出目录
cd target

# 导入到本地仓库 注意这里 version 根据实际生成的来
mvn install:install-file -DgroupId=org.csource -DartifactId=fastdfs-client-java -Dversion=1.27-SNAPSHOT -Dpackaging=jar -Dfile=fastdfs-client-java-1.27-SNAPSHOT.jar

在 pom.xml 中引入依赖

    <dependency>
        <groupId>org.csource</groupId>
        <artifactId>fastdfs-client-java</artifactId>
        <version>1.27-SNAPSHOT</version>
    </dependency>

    <dependency>
      <groupId>commons-fileupload</groupId>
      <artifactId>commons-fileupload</artifactId>
      <version>1.3.1</version>
    </dependency>

    <dependency>
      <groupId>commons-io</groupId>
      <artifactId>commons-io</artifactId>
      <version>2.2</version>
    </dependency>

    <dependency>
      <groupId>org.apache.commons</groupId>
      <artifactId>commons-lang3</artifactId>
      <version>3.1</version>
    </dependency>

添加 Client 配置

在 resource 目录下,添加 conf/fdfs_client.conf 配置文件

connect_timeout = 2
network_timeout = 30
charset = UTF-8
http.tracker_http_port = 80
http.anti_steal_token = no
http.secret_key = FastDFS1234567890

tracker_server = 192.168.1.163:22122

测试时实际上只需关注 tracker_server,并且改为你自己的 tracker server

添加文件上传 bean

applicationContext.xml 配置中添加文件上传 bean

    <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
        <property name="maxUploadSize" value="62914560" />
        <property name="defaultEncoding" value="UTF-8" />
    </bean>

建一个 Client 封装

建一个简单的 client 封装(勿作生产使用)
FastDFSClient.java

package com.rootrl.fastDFSDemo.utiles;

import org.apache.commons.lang3.StringUtils;
import org.csource.common.NameValuePair;
import org.csource.fastdfs.*;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;

public class FastDFSClient {

    private static StorageClient1 storageClient1 = null;

    static {
        try {
            // 获取配置文件
            String classPath = new File(FastDFSClient.class.getResource("/").getFile()).getCanonicalPath();
            String CONF_FILENAME = classPath + File.separator + "conf" + File.separator + "fdfs_client.conf";
            ClientGlobal.init(CONF_FILENAME);
            // 获取触发器
            TrackerClient trackerClient = new TrackerClient(ClientGlobal.g_tracker_group);
            TrackerServer trackerServer = trackerClient.getConnection();
            // 获取存储服务器
            StorageServer storageServer = trackerClient.getStoreStorage(trackerServer);
            storageClient1 = new StorageClient1(trackerServer, storageServer);
        } catch (Exception e) {System.out.println(e);
        }
    }

    /**
     * 上传文件
     * @param fis      文件输入流
     * @param fileName 文件名称
     * @return
     */
    public static String uploadFile(InputStream fis, String fileName) {
        try {NameValuePair[] meta_list = null;

            // 将输入流写入 file_buff 数组
            byte[] file_buff = null;
            if (fis != null) {int len = fis.available();
                file_buff = new byte[len];
                fis.read(file_buff);
            }

            String fileid = storageClient1.upload_file1(file_buff, getFileExt(fileName), meta_list);
            return fileid;
        } catch (Exception ex) {return null;} finally {if (fis != null) {
                try {fis.close();
                } catch (IOException e) {System.out.println(e);
                }
            }
        }
    }


    /**
     * 获取文件后缀
     * @param fileName
     * @return
     */
    private static String getFileExt(String fileName) {if (StringUtils.isBlank(fileName) || !fileName.contains(".")) {return "";} else {return fileName.substring(fileName.lastIndexOf(".") + 1);
        }
    }
}

建立控制器

然后建立一个 File 控制器,做测试用
FileController.java

package com.rootrl.fastDFSDemo.controller;

import com.rootrl.fastDFSDemo.utiles.FastDFSClient;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;

@Controller
@RequestMapping("fastdfs")
public class FileController {@RequestMapping(value = "upload")
    @ResponseBody
    public String uploadFileSample(@RequestParam MultipartFile file){

        try {String fileId = FastDFSClient.uploadFile(file.getInputStream(), file.getOriginalFilename());

            return fileId;

        } catch (Exception e) {System.out.println(e.getMessage());
            return "error";
        }
    }

}

然后使用 postman 客户端测试,url 为:http://localhost:8080/fastdfs/upload.do(依据自己实际情况变更)

注意 postman 使用 post 请求,然后切换到 body/form-data 标签项,添加一个 Key 为 file,类型为 file,然后 value 就可以上传文件了。成功会返回文件 id,类似:group1/M00/00/00/wKgBo1zjxnOAT-k1AAAoMlb3hzU996.png

参考

https://blog.csdn.net/yxflove…

退出移动版