分布式文件系统FastDfs学习(使用篇)

725 阅读8分钟

一.简介:

今天给大家讲解下FastDfs的使用,主要还是使用java语言如何进行操作,同时也会讲解下FasdDfs环境
的安装,能让大家使用的时候更熟练一些;

一般情况下,对于一个新的技术名称,小编一般都是以3W法则的思路进行思考和解决困惑,也就是what
,why,how,下面就按照这个思路大致简单的给大家介绍下FastDfs:

1.what?(FastDfs是什么?是干什么用的?)

FastDFS是一个开源的高性能,轻量级的分布式文件系统,由淘宝开发平台部资深架构师余庆开发(国内
开源软件),它的主要功能包括:文件存储,文件同步和文件访问(文件上传和文件下载),它可以解
决高容量和负载平衡问题。FastDFS应该满足基于照片共享站点和视频共享站点等文件的网站的要求。

上面文件描述摘自github上fastdfs的项目描述,我们大致可以知道它是个文件系统,关于对该系统存储
单文件的大小讨论,我看网上建议适合文件大小500M以下的存储,具体实际情况小编也没有进行过性能
测试,所以根据上面官方的描述以及网上的建议:FasfDfs是是一个适合存储图片,小文件,小音频的文
件系统。

2.why?(为什么用FastDfs?)

当然,为什么用,主要看是看自己的场景,类似文件存储系统的有很多:GFS、HDFS、Lustre 、
Ceph 、GridFS 、mogileFS、TFS、FastDFS等,各自适用于不同的领域。它们都不是系统级的分布式文
件系统,而是应用级的分布式文件存 储服务。

下面是分布式文件存储选型比较:

而且上面已经提到FastDfs是开源,轻量级,高性能,分布式的。

3.how?(怎么使用FasfDfs呢?)

上面已经对FastDfs有了大概的了解,下面两章会讲解FastDfs的安装(目前FastDfs只支持linux系
统,对windows不支持),以及用java如何进行文件存储和下载。

相关地址:FastDfs的github地址

二.FastDfs环境安装:

环境安装教程(单机部署和分布式部署)

上面链接是跳转到FastDfs的wiki,里面了安装的教程,小编在安装的过程中有一些疑惑,不知道大家
在安装的时候有没有?下面就一块说下。

1.安装的时候需要安装nginx,那nginx和fastDfs是什么关系呢?

前面说fastDfs是一个分布式系统,上传的文件会进行复制备份:
FastDFS通过Tracker服务器,将文件放在Storage服务器存储,但是同组之间的服务器需要复制文件,
有延迟的问题.假设Tracker服务器将文件上传到了A服务器,文件ID已经返回客户端,这时,后台会将
这个文件复制到B服务器,如果复制没有完成,客户端就用这个ID在B服务器取文件,肯定会出现错误。
这个fastdfs-nginx-module可以重定向连接到源服务器取文件,避免客户端由于复制延迟的问题,
出现错误。

三.java操作fasfDfs:

1.配置文件:

如下是我的配置文件配置:

我的配置文件是以fdfs_client.conf配置,官方文档说以.properties进行配置,详细见 fastdfs-client-java官方文档

connect_timeout = 2
network_timeout = 30
charset = UTF-8
http.tracker_http_port = 8080
#fastDfs的权限控制,服务端在http.conf配置
http.anti_steal_token = no
http.secret_key = FastDFS1234567890

tracker_server = 127.0.0.1:22122

connection_pool.enabled = true
connection_pool.max_count_per_entry = 500
connection_pool.max_idle_time = 3600
connection_pool.max_wait_time_in_ms = 1000
所以我的读取配置文件的路径如下图所示:

2.工具类代码:

如下代码包含了读取配置文件,文件的上传,下载,删除以及查询等操作;
package com.ufgov.ar.modules.file.util;

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

import java.io.BufferedOutputStream;
import java.io.IOException;

/**
 * @ProjectName: ar-server
 * @Package: com.ufgov.ar.modules.file.util
 * @ClassName: FastDfsUtil
 * @Author: tianmengwei
 * @Description: FastDFS工具类【实现文件上传、下载、删除、查询】
 * @Date: 2019/12/28 11:04
 */
public class FastDfsClientHelper {

    private static TrackerServer trackerServer;
    private static StorageServer storageServer;

    // 初始化FastDFS Client
    static {
        try {
            ClientGlobal.initByProperties("src/main/resources/application.properties");
            System.out.println("ClientGlobal.configInfo(): " + ClientGlobal.configInfo());
            TrackerClient trackerClient = new TrackerClient(ClientGlobal.g_tracker_group);
            trackerServer = trackerClient.getConnection();
            if (trackerServer == null) {
                throw new IllegalStateException("getConnection return null");
            }
            storageServer = trackerClient.getStoreStorage(trackerServer);
            if (storageServer == null) {
                throw new IllegalStateException("getStoreStorage return null");
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * @author tianmengwei
     * @description 获取StorageClient1对象:
     * 此处不能将该代码放在静态块的原因:
     * 参考该博客;https://mp.weixin.qq.com/s/EFsK3yOtb7maDNqgaHjQow?
     * 在并发场景下,线程一在使用完StorageClient1置为null时,此时线程二在继续使用线程一初始化的对象的话,会报空指针;
     * @date 2020/2/24 11:12
     */
    private static StorageClient1 getStorageClient1() {
        try {
            StorageClient1 storageClient1 = new StorageClient1(trackerServer, storageServer);
            return storageClient1;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * @param fileName : 文件全路径
     * @param extName  : 文件扩展名,不包含(.)
     * @param metas    : 文件扩展信息
     * @author tianmengwei
     * @description 本地上传文件
     * @date 2019/12/28 11:36
     */
    public static String uploadFileByFastDfs(String fileName, String extName, NameValuePair[] metas) {
        String result = null;
        try {
            StorageClient1 storageClient1 = FastDfsClientHelper.getStorageClient1();
            if (storageClient1 == null) {
                return null;
            }
            result = storageClient1.upload_file1(fileName, extName, metas);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return result;
    }

    /**
     * 上传文件,传fileName
     *
     * @param fileName 文件的磁盘路径名称 如:D:/image/aaa.jpg
     * @return null为失败
     */
    public static String uploadFileByFastDfs(String fileName) {
        return uploadFileByFastDfs(fileName, null, null);
    }

    /**
     * @param fileName 文件的磁盘路径名称 如:D:/image/aaa.jpg
     * @param extName  文件的扩展名 如 txt jpg等
     * @return null为失败
     */
    public static String uploadFileByFastDfs(String fileName, String extName) {
        return uploadFileByFastDfs(fileName, extName, null);
    }

    /**
     * @param fileContent : 文件的内容,字节数组
     * @param extName     : 文件扩展名
     * @param metas       : 文件扩展信息
     * @author tianmengwei
     * @description 上传文件
     * @date 2019/12/28 11:33
     */
    public static String uploadFileByFastDfs(byte[] fileContent, String extName, NameValuePair[] metas) {
        String result = null;
        try {
            StorageClient1 storageClient1 = FastDfsClientHelper.getStorageClient1();
            if (storageClient1 == null) {
                return null;
            }
            result = storageClient1.upload_file1(fileContent, extName, metas);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return result;
    }

    /**
     * @param fileContent : 文件的字节数组
     * @author tianmengwei
     * @description 上传文件
     * @date 2019/12/28 11:32
     */
    public static String uploadFileByFastDfs(byte[] fileContent) {
        return uploadFileByFastDfs(fileContent, null, null);
    }

    /**
     * @param fileContent : 文件的字节数组
     * @param extName     : 文件的扩展名 如 txt  jpg png 等
     * @author tianmengwei
     * @description 上传文件
     * @date 2019/12/28 11:32
     */
    public static String uploadFileByFastDfs(byte[] fileContent, String extName) {
        return uploadFileByFastDfs(fileContent, extName, null);
    }

    /**
     * @param path   :  图片路径
     * @param output :  输出流 中包含要输出到磁盘的路径
     * @author tianmengwei
     * @description 文件下载到磁盘 返回结果:-1失败,0成功
     * @date 2019/12/28 11:30
     */
    public static int downloadFileByFastDfs(String path, BufferedOutputStream output) {
        int result = -1;
        try {
            StorageClient1 storageClient1 = FastDfsClientHelper.getStorageClient1();
            if (storageClient1 == null) {
                return -1;
            }
            path = path.replaceAll("\\\\", "/");
            byte[] b = storageClient1.download_file1(path);
            try {
                if (b != null) {
                    output.write(b);
                    result = 0;
                }
                //用户可能取消了下载
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                if (output != null) {
                    try {
                        output.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return result;
    }

    /**
     * @param path : 文件的路径 如group1/M00/00/00/wKgRsVjtwpSAXGwkAAAweEAzRjw471.jpg
     * @author tianmengwei
     * @description path前端会将FastDfs生成的/转换为\,此处FastDfs解析不到信息导致找不到文件路径
     * @date 2019/12/28 11:20
     */
    public static byte[] downLoadBytesByFastDfs(String path) {
        byte[] b = null;
        try {
            StorageClient1 storageClient1 = FastDfsClientHelper.getStorageClient1();
            path = path.replaceAll("\\\\", "/");
            b = storageClient1.download_file1(path);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return b;
    }

    /**
     * @param serverPath : 文件在服务器的路径
     * @param localPath  : 下载的本地路径
     * @author tianmengwei
     * @description 下载文件到本地指定的路径
     * @date 2020/2/20 23:48
     */
    public static void downLoadLocalByFastDfs(String serverPath, String localPath) {
        try {
            StorageClient1 storageClient1 = FastDfsClientHelper.getStorageClient1();
            if (storageClient1 == null) {
                return;
            }
            serverPath = serverPath.replaceAll("\\\\", "/");
            storageClient1.download_file1(serverPath, localPath);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * @param group       : 组名 如:group1
     * @param storagePath : 不带组名的路径名称 如:M00/00/00/wKgRsVjtwpSAXGwkAAAweEAzRjw471.jpg
     * @author tianmengwei
     * @description 返回结果:-1失败,0成功
     * @date 2019/12/28 11:18
     */
    public static Integer deleteFileByFastDfs(String group, String storagePath) {
        int result = -1;
        try {
            StorageClient1 storageClient1 = FastDfsClientHelper.getStorageClient1();
            if (storageClient1 == null) {
                return -1;
            }
            storagePath = storagePath.replaceAll("\\\\", "/");
            result = storageClient1.delete_file(group, storagePath);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return result;
    }

    /**
     * @param storagePath : 文件的全部路径 如:group1/M00/00/00/wKgRsVjtwpSAXGwkAAAweEAzRjw471.jpg
     * @author tianmengwei
     * @description 返回结果:-1失败,0成功
     * @date 2019/12/28 11:12
     */
    public static Integer deleteFileByFastDfs(String storagePath) {
        int result = -1;
        try {
            StorageClient1 storageClient1 = FastDfsClientHelper.getStorageClient1();
            if (storageClient1 == null) {
                return null;
            }
            storagePath = storagePath.replaceAll("\\\\", "/");
            result = storageClient1.delete_file1(storagePath);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return result;
    }

    /**
     * @param fileId : M00/00/00/wKgRsVjtwpSAXGwkAAAweEAzRjw471.jpg
     * @author tianmengwei
     * @description 获取远程服务器文件资源信息
     * @date 2019/12/28 11:08
     */
    public static FileInfo getFileInfoByFastDfs(String fileId) {
        try {
            StorageClient1 storageClient1 = FastDfsClientHelper.getStorageClient1();
            if (storageClient1 == null) {
                return null;
            }
            fileId = fileId.replaceAll("\\\\", "/");
            return storageClient1.get_file_info1(fileId);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * @param fileId : M00/00/00/wKgRsVjtwpSAXGwkAAAweEAzRjw471.jpg
     * @author tianmengwei
     * @description 获取远程服务器文件资源信息
     * @date 2019/12/28 11:08
     */
    public static NameValuePair[] getNameValuePairByFastDfs(String fileId) {
        try {
            StorageClient1 storageClient1 = FastDfsClientHelper.getStorageClient1();
            if (storageClient1 == null) {
                return null;
            }
            fileId = fileId.replaceAll("\\\\", "/");
            return storageClient1.get_metadata1(fileId);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

}


3.web的使用:

如下代码是上传,下载,查询原数据的操作代码:
    @ApiOperation(value = "上传单个普通文件", notes = "上传单个普通文件")
    @RequestMapping(value = {"/uploadNormalFile"}, method = RequestMethod.POST)
    public ArRes uploadNormalFileByFastDfs(@RequestParam(value = "file", required = false) MultipartFile file) throws Exception {
        if (file.isEmpty()) {
            return ArRes.error("上传附件为空");
        }
        String fileType = arBillFileHelper.getFileType(file.getOriginalFilename());
        NameValuePair nameValuePairs [] = new NameValuePair[]{
                    new NameValuePair("fileType", fileType),
                new NameValuePair("fileContent", "11111111")};
        String result = FastDfsClientHelper.uploadFileByFastDfs(file.getBytes(), "doc", nameValuePairs);
        return ArRes.ok().putData(result);
    }
上传成功后会返回文件的fileId,可以根据该id进行下载或者进行元数据的查询;

@ApiOperation(value = "下载文件", notes = "根据fileId传出文件")
@RequestMapping(value = {"/downloadNormalFile"}, method = RequestMethod.GET)
public void downloadNormalFile(HttpServletRequest request, HttpServletResponse response, @RequestParam(value = "fileId") String fileId ) throws IOException {
    try {
        FastDfsClientHelper.downloadFileByFastDfs(fileId,new BufferedOutputStream(response.getOutputStream()));
    } catch (ArBillException e) {
        response.setContentType("text/html; charset=UTF-8");
        response.getWriter().write("文件丢失");
    } catch (Exception e) {
        logger.error("downloadNormalFile failed error:" + e.getMessage(), e);
        response.setContentType("text/html; charset=UTF-8");
        response.getWriter().write("文件下载异常");
    }
}

FastDfs提供了FileInfo实体类,该实体类存储了上传文件的源数据,包含服务器地址,时间等等:

 同时也提供了NameValuePair实体类,可以存储我们想要存储的一些业务信息;

四.结尾:

至此,大家应该对FastDfs大致有了一些了解,并且能简单进行使用了,大家在使用过程中遇到什么问题
欢迎能和小编一起探讨。