【手把手】一篇讲清楚FastDFS的安装及使用

news2024/9/28 1:06:24

分布式存储发展历程

前段时间618活动火热进行,正是购物的好时机。当我们访问这些电商网站的时候,每一个商品都会有各式各样的图片展示介绍,这些图片一张两张可以随便丢在服务器的某个文件夹中,可是电商网站如此大体量的图片,得分门别类的进行管理。再比如我们平时浏览的各大视频网站的视频,还有我们现在正在浏览的CSDN上的各类文章,都需要在服务器上分门别类的管理好。

在文件管理早期的时候,由于文件本身的数量和占用空间都比较小,往往在一台服务器上既有程序在运行,也有文件在存储。随着互联网的不断发展,现在的程序越做越大,图片的内容和数量也越来越多,不断的侵蚀服务器有限的资源,进而影响到程序本身运行的稳定性。

于是,越来越多的系统在最初设计,或者升级改造的时候,选择将文件的存储单独拎出来放在一台专用的文件服务器上。和程序服务器相互独立,相互不受影响。文件服务器的功能也相对比较单一,只需要对文件进行管理即可。

随着图片的数量越来越多,单个图片自身占用的空间越来越大,单台服务器对于图片的承载压力也越来越大,就需要拓宽服务器的数量,通过更多的图片服务器存储文件。这样虽然解决了文件的存储问题,但是却产生了一个新的问题:怎么从这么多的机器中快速的找寻到需要的那张图片?于是便引申出了分布式存储的概念,在众多的机器之中,如何讲文件放进去,如何讲文件取出来。

常见的分布式存储框架

分布式框架

简介

FastDFS

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

HDFS

Hadoop组件中的分布式存储框架,高度容错性的系统,适合部署在廉价的机器上。HDFS能提供高吞吐量的数据访问,非常适合大规模数据集上的应用。

MinIO

Apache下的产品,最适合存储非结构化的数据。比如:照片,视频,日志文件,备份和容器等

阿里云对象存储

OSS(Object Storage Service)是一款海量、安全、低成本、高可靠的云存储服务,提供99.9999999999%(12个9)的数据持久性,99.995%的数据可用性。多种存储类型供选择,全面优化存储成本。

什么是FastDFS

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

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

角色

描述

Tracker Server

跟踪器,主要做调度工作,在访问上起负载均衡的作用。记录storage server的状态,是连接Client和Storage server的枢纽

Storage Server

存储服务器,文件和metadata都保存到存储服务器上

group

组,也可称为卷。同组内服务器上的文件是完全相同的

文件标识

包括两部分:组名和文件名(包含路径)

meta-data

文件相关属性,键值对(Key Value Pair)方式:width=1024,heigth=768

FastDFS服务端有两个角色:跟踪器(tracker)和存储服务器(storage)。跟踪器主要做调度工作,在访问上起负载均衡的作用。存储服务器存储文件,完成文件管理的所有功能:就是这样的存储、同步和提供存取接口,FastDFS同时对文件的metadata进行管理。所谓文件的metadata就是文件的相关属性,以键值对(key value)方式表示,如:width=1024,其中的key为width,value为1024。文件metadata是文件属性列表,可以包含多个键值对。 

很多人可能不太理解为什么要设计tracker这个角色,当存储服务器由很多个物理机器组成时。客户端要上传/下载文件的时候,不知道文件上传/下载到哪个具体的机器。于是客户端会先请求tracker,tracker会返回具体的group和group中的具体主机信息。客户端拿着这个具体的主机信息去请求相应的主机进行文件的上传/下载。

跟踪器和存储节点都可以由一台或多台服务器构成。跟踪器和存储节点中的服务器均可以随时增加或下线而不会影响线上服务。其中跟踪器中的所有服务器都是对等的,可以根据服务器的压力情况随时增加或减少。 

为了支持大容量,存储节点(服务器)采用了分组(或分卷)的组织方式:group。存储系统由一个或多个group组成,group与group之间的文件是相互独立的,所有group的文件容量累加就是整个存储系统中的文件容量。一个group可以由一台或多台存储服务器组成,一个group下的存储服务器中的文件都是相同的,group中的多台存储服务器起到了冗余备份和负载均衡的作用。

在group中增加服务器时,同步已有的文件由系统自动完成,同步完成后,系统自动将新增服务器切换到线上提供服务。当存储空间不足或即将耗尽时,可以动态添加group。只需要增加一台或多台服务器,并将它们配置为一个新的卷,这样就扩大了存储系统的容量。FastDFS中的文件标识分为两个部分:组名和文件名,二者缺一不可。

FastDFS的安装与部署

FastDFS开源的地址在GitHub上:https://github.com/happyfish100

源码安装要将对应的源码下载后编译运行安装,当然图省事的话可以直接使用docker快速安装。先看使用docker的情况下如何快速安装,毕竟很多人应该对源码编译安装没啥兴趣。

docker安装

首先,查询docker中关于FastDFS有哪些可用的镜像:docker search fastdfs

选择需要的镜像,拉取到本地:docker pull delron/fastdfs

镜像文件并不大,本身只有400+M 

通过Docker命令来创建Tracker服务: 
docker run -d --name tracker --network=host -v /mydata/fastdfs/tracker:/var/fdfs delron/fastdfs tracker

tracker服务默认的端口为22122,-v 实现了docker中的容器和本地目录之间的挂载。所以在执行docker命令前应手动先将本地的 /mydata/fastdfs/tracker 目录创建好。

Tracker服务创建成功之后,继续通过docker的命令创建Storage服务:
docker run -d --name storage --network=host -e TRACKER_SERVER=192.168.32.128:22122 -v /mydata/fastdfs/storage:/var/fdfs -e GROUP_NAME=group1 delron/fastdfs storage

在执行上面命令的时候要注意对应的修改下,其中TRACKER_SERVER中的IP要修改为你的Tracker服务所在的服务IP地址。并且同样的,先手动将/mydata/fastdfs/storage目录创建好。

默认情况下在Storage服务中是已经预装了Nginx服务的,相关的端口为8888;Storage自身的端口默认是23000。当然如果你发现这些相关的端口被占用了,或者想要对应的修改端口信息也可以,需要进入容器中查看下相关的配置文件信息。

进入Storage服务的容器中:docker exec -it d4926e8325e3 bash

在容器中的 /etc/fdfs/ 目录下找到storage.conf配置文件

可以看到在配置文件的最后一行指定了http的端口:

除此之外,还得再来到容器中的 /usr/local/nginx/conf 目录下,将 nginx.conf 配置文件中的8888端口一并更改:

Tracker和Storage服务安装完成之后,在服务器的Storage本地目录中放几张用于测试上传的图片。因为之前在启动的时候,已经将本地目录和docker目录进行了一个挂载,所以当进入到docker中的Storage服务中,依然可以看到本地目录中的测试图片:

使用服务原生的/usr/bin/fdfs_upload_file命令进行文件上传:
/usr/bin/fdfs_upload_file /etc/fdfs/client.conf face2face1.jpg

上传成功之后,返回已上传成功的图片信息。这张图片存于group1组中,/M00/00/00/目录下,重命名为wKgggGSD2feAXsv4AER92Sd_dzk072.jpg。然后拿着这串图片地址,使用浏览器访问:http://192.168.32.128:8888/group1/M00/00/00/wKgggGSD2feAXsv4AER92Sd_dzk072.jpg

可以成功拿到这张上传的图片则说明FastDFS服务安装成功。因为Storage服务中已经安装了Nginx服务,所以访问图片的端口就是Nginx监听的8888端口,经过Nginx的代理拿到上传的那张图片。

源码安装

docker的安装比较简单,基本上该有的配置都帮你配置好了,傻瓜式安装无脑省事。当然有些人并不满足于使用docker进行管理,总想走一遍复杂的源码安装一窥究竟,那么接下来我们就来走一遍使用GitHub上的源码应该如何进行安装。

在正式的安装前,先预备两台虚拟机分别用于Tracker服务和Storage服务的安装,这次我们将两个服务分别独立部署在两个独立的虚拟机上,更加真是模拟线上的真实环境:
192.168.126.142 作为 Storage
192.168.126.143 作为 Tracker、Client

虚拟机的防火墙记得关闭:
systemctl stop firewalld.service
systemctl disable firewalld.service

提前将需要依赖的环境全部下载安装好:
FastDFS采用C语言开发,所以必不可少的 gcc 环境:
yum -y install gcc-c++

FastDFS依赖 libevent 库,这玩意儿也不能少:
yum -y install libevent

访问图片时必不可少的Nginx依赖:
yum -y install pcre-devel
yum -y install openssl openssl-devel

依赖安装完成之后,从GitHub上下载FastDFS必要的安装包:
wget https://github.com/happyfish100/libfastcommon/archive/refs/tags/V1.0.66.tar.gz
wget https://github.com/happyfish100/libserverframe/archive/refs/tags/V1.1.25.tar.gz
wget https://github.com/happyfish100/fastdfs/archive/refs/tags/V6.9.4.tar.gz
wget https://github.com/happyfish100/fastdfs-nginx-module/archive/refs/tags/V1.23.tar.gz

libfastcommon

FastDFS分离出的公用函数库

libserverframe

FastDFS分离出的网络框架

fastdfs

FastDFS核心本体

fastdfs-nginx-module

FastDFS和nginx的关联模块

以及下载Nginx安装包:
wget https://nginx.org/download/nginx-1.24.0.tar.gz

以上这些安装包在Tracker服务和Storage服务上都安排一份,压缩包准备完成之后,创建数据存储目录:mkdir -p /data/fast_dfs

首先安装Tracker服务,在Tracker服务器上,解压libfastcommon-V1.0.66.tar.gz压缩包:
tar -zxvf libfastcommon-V1.0.66.tar.gz

解压完成后进入对应文件夹中,执行编译并安装:
cd libfastcommon-1.0.66/
./make.sh && ./make.sh install

安装完成后会输出安装位置信息:

然后返回上级目录继续解压libserverframe-V1.1.25.tar.gz压缩包:
tar -zxvf libserverframe-V1.1.25.tar.gz

解压完之后进入对应文件夹,执行编译并安装:
cd libserverframe-1.1.25/
./make.sh && ./make.sh install

安装完成后会输出安装位置信息:

然后返回上级目录继续解压fastdfs-V6.9.4.tar.gz压缩包:
tar -zxvf fastdfs-V6.9.4.tar.gz

解压完之后进入对应文件夹,执行编译并安装:
cd fastdfs-6.9.4/
./make.sh && ./make.sh install

安装完成后会输出安装位置信息:

FastDFS服务主体安装完以后,进入fastdfs-6.9.4/conf/目录下,将http.conf和http.conf文件拷贝到/etc/fdfs目录下,供Nginx访问使用:
cd conf/
cp http.conf /etc/fdfs/
cp mime.types /etc/fdfs/

然后继续解压fastdfs-nginx-module-V1.23.tar.gz压缩包:
tar -zxvf fastdfs-nginx-module-V1.23.tar.gz

解压完之后进入对应文件夹中的src目录下,将mod_fastdfs.conf文件拷贝到/etc/fdfs目录下:
cd fastdfs-nginx-module-1.23/src/
cp mod_fastdfs.conf /etc/fdfs/

最后一步,解压nginx-1.24.0.tar.gz压缩包:
tar -zxvf nginx-1.24.0.tar.gz

解压完之后进入对应文件夹中,添加fastdfs-nginx-module模块:
cd nginx-1.24.0/
./configure --add-module=/usr/local/source/fastdfs-nginx-module-1.23/src/

模块添加完成之后,执行编译安装:
make && make install

Nginx安装完成之后,进入/etc/fdfs目录下配置Tracker服务的tracker.conf文件:
cd /etc/fdfs/
vim tracker.conf

① tracker服务器端口,默认是22122,一般不修改:
port=22122

② 设置存储日志和数据的根目录,改为之前已经创建好的/home/dfs目录:
base_path=/data/fast_dfs

修改完配置文件之后,进入fastdfs-6.9.4/tracker/目录中,启动Tracker服务:
cd /usr/local/source/fastdfs-6.9.4/tracker/
./fdfs_trackerd /etc/fdfs/tracker.conf

Tracker服务就算是搞定,接下来在Storage服务器上将如上的几个压缩包的解压和安装再来一遍。直到Nginx安装完成之后,进入/etc/fdfs目录下配置Storage服务的storage.conf文件:
cd /etc/fdfs/
vim storage.conf

① storage服务端口,默认是23000,一般不修改:
port=23000

② 设置数据和日志文件存储根目录为/data/fast_dfs,和Tracker一致:
base_path=/data/fast_dfs

③ 将第一个存储目录也设置为/data/fast_dfs:
store_path0=/data/fast_dfs

④ 配置tracker服务器IP和端口:
tracker_server=192.168.126.143:22122

⑤ http访问文件的端口,默认是8888,看情况修改,只要和nginx中保持一致即可:
http.server_port=8888

修改完配置文件之后,进入fastdfs-6.9.4/storage/目录中,启动Storage服务:
cd /usr/local/source/fastdfs-6.9.4/storage/
./fdfs_storaged /etc/fdfs/storage.conf

Storage服务安装完成之后,就可以对整个FastDFS服务进行文件上传的测试。切换到Client(也就是Tracker)服务器上,配置/etc/fdfs目录下的client.conf文件:
cd /etc/fdfs/
vim client.conf

① 设置数据和日志文件存储根目录为/data/fast_dfs,和Tracker一致:
base_path=/data/fast_dfs

② 配置tracker服务器IP和端口:
tracker_server=192.168.126.143:22122

配置文件修改完之后,进入fastdfs-6.9.4/client/目录下,使用原生的文件上传命令上传测试图片: 
cd /usr/local/source/fastdfs-6.9.4/client/
./fdfs_upload_file /etc/fdfs/client.conf /images/jessica.jpg

如果看到返回的组信息和重命名后的图片名称,则表示图片上传成功。我们可以拿着这串图片地址,切换到Storage服务器中的对应目录下,查看图片是不是真的存在:

确认图片上传没有问题之后,最后一步就该能正常访问到上传的图片。我们都知道,一般在服务器上想要访问图片此类的静态资源,必不可少需要经过Nginx的转发。切换到Storage服务器的/etc/fdfs目录下, 修改mod_fastdfs.conf文件:
cd /etc/fdfs/
vim mod_fastdfs.conf

① 配置tracker服务器IP和端口:
tracker_server=192.168.126.143:22122

② 设置URL前强制加上组名group_name:
url_have_group_name = true

③ 将第一个存储目录也设置为/data/fast_dfs:
store_path0=/data/fast_dfs

然后修改Nginx配置文件,添加对于8888端口的监听代理:
cd /usr/local/nginx/conf/
vim nginx.conf

启动Nginx服务:
/usr/local/nginx/sbin/nginx -c /usr/local/nginx/conf/nginx.conf

拿着Tracker服务器测试图片上传后返回的图片ID,直接访问Storage服务器获取图片:
http://192.168.126.142:8888/group1/M00/00/00/wKh-jmSmuVCATN3xAEMYaPqDnz8276.jpg

可以成功访问到这张上传的图片则说明FastDFS服务安装成功。如果上传成功,但是Nginx一直报错404,大概率是图片的Nginx的配置错误,或者文件的目录设置错。检查Tracker和Storage服务器上的/etc/fdfs目录下的文件:

两边目录下的这些配置文件中的IP、路径等设置要保持一致。一般来说,去到/usr/local/nginx/logs/目录下排查Nginx的错误日志文件error.log,大概率是图片的地址设置错误,导致图片找不到。

或许小概率会遇到:unknown directive "ngx_fastdfs_module" in /usr/local/nginx/conf/nginx.conf:151,可能是Nginx一直是启动的,必须要重启Nginx才可以,使用`nginx -s reload`命令多半是无效操作;

如果Nginx的error.log中提示:ERROR - file: ini_file_reader.c, line: 1051, include file "http.conf" not exists, line: "#include http.conf" ERROR - file: /root/fastdfs-nginx-module/src/common.c, line: 163, load conf file "/etc/fdfs/mod_fastdfs.conf" fail, ret code: 2,则需要将fastdfs的源码中的conf文件夹中的http.conf和mime.types cp到/etc/fdfs文件夹中。

FastDFS原生JavaAPI操作图片上传及下载

老规矩,第1步必然是引入相关依赖

<dependency>
    <groupId>cn.bestwu</groupId>
    <artifactId>fastdfs-client-java</artifactId>
    <version>1.27</version>
</dependency>

其实这个依赖包在Git上也有包含,如果不走maven引入的话,可以直接从Git上下载

 第2步,在项目的resources目录下新建fastdfs-config.conf文件,都是一些访问路径的基础配置:

connect_timeout=10
network_timeout=30
charset=UTF-8
http.tracker_http_port=8080
tracker_server=192.168.126.143:22122

第3步,读取配置文件,调用官方提供的方法进行图片的上传下载:

package com.feenix.fdfsupload.config;

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

import java.io.*;

public class FastDFSClient {

    private static final String CONF_FILENAME = Thread.currentThread().getContextClassLoader()
                                                .getResource("").getPath() + "fastdfs-config.conf";

    private static StorageClient storageClient = null;

    /**
     * 只加载一次.
     */
    static {
        try {
            ClientGlobal.init(CONF_FILENAME);
            TrackerClient trackerClient = new TrackerClient(ClientGlobal.g_tracker_group);
            TrackerServer trackerServer = trackerClient.getConnection();
            StorageServer storageServer = trackerClient.getStoreStorage(trackerServer);
            storageClient = new StorageClient(trackerServer, storageServer);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * @param inputStream 上传的文件输入流
     * @param fileName    上传的文件原始名
     * @return
     */
    public static String[] uploadFile(InputStream inputStream, String fileName) {
        try {
            // 文件的元数据
            NameValuePair[] meta_list = new NameValuePair[2];
            // 第一组元数据,文件的原始名称
            meta_list[0] = new NameValuePair("file name", fileName);
            // 第二组元数据
            meta_list[1] = new NameValuePair("file length", inputStream.available() + "");
            // 准备字节数组
            byte[] file_buff = null;
            if (inputStream != null) {
                // 查看文件的长度
                int len = inputStream.available();
                // 创建对应长度的字节数组
                file_buff = new byte[len];
                // 将输入流中的字节内容,读到字节数组中。
                inputStream.read(file_buff);
            }
            // 上传文件。参数含义:要上传的文件的内容(使用字节数组传递),上传的文件的类型(扩展名),元数据
            String[] fileids = storageClient.upload_file(file_buff, getFileExt(fileName), meta_list);
            return fileids;
        } catch (Exception ex) {
            ex.printStackTrace();
            return null;
        }
    }

    /**
     * @param file     文件
     * @param fileName 文件名
     * @return 返回Null则为失败
     */
    public static String[] uploadFile(File file, String fileName) {
        FileInputStream fis = null;
        try {
            NameValuePair[] meta_list = null; // new NameValuePair[0];
            fis = new FileInputStream(file);
            byte[] file_buff = null;
            if (fis != null) {
                int len = fis.available();
                file_buff = new byte[len];
                fis.read(file_buff);
            }

            String[] fileids = storageClient.upload_file(file_buff, getFileExt(fileName), meta_list);
            return fileids;
        } catch (Exception ex) {
            return null;
        } finally {
            if (fis != null) {
                try {
                    fis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * 根据组名和远程文件名来删除一个文件
     *
     * @param groupName      例如 "group1" 如果不指定该值,默认为group1
     * @param remoteFileName 例如"M00/00/00/wKgxgk5HbLvfP86RAAAAChd9X1Y736.jpg"
     * @return 0为成功,非0为失败,具体为错误代码
     */
    public static int deleteFile(String groupName, String remoteFileName) {
        try {
            int result = storageClient.delete_file(groupName == null ? "group1" : groupName, remoteFileName);
            return result;
        } catch (Exception ex) {
            return 0;
        }
    }

    /**
     * 修改一个已经存在的文件
     *
     * @param oldGroupName 旧的组名
     * @param oldFileName  旧的文件名
     * @param file         新文件
     * @param fileName     新文件名
     * @return 返回空则为失败
     */
    public static String[] modifyFile(String oldGroupName, String oldFileName, File file, String fileName) {
        String[] fileids = null;
        try {
            // 先上传
            fileids = uploadFile(file, fileName);
            if (fileids == null) {
                return null;
            }
            // 再删除
            int delResult = deleteFile(oldGroupName, oldFileName);
            if (delResult != 0) {
                return null;
            }
        } catch (Exception ex) {
            return null;
        }
        return fileids;
    }

    /**
     * 文件下载
     *
     * @param groupName      卷名
     * @param remoteFileName 文件名
     * @return 返回一个流
     */
    public static InputStream downloadFile(String groupName, String remoteFileName) {
        try {
            byte[] bytes = storageClient.download_file(groupName, remoteFileName);
            InputStream inputStream = new ByteArrayInputStream(bytes);
            return inputStream;
        } catch (Exception ex) {
            return null;
        }
    }

    public static NameValuePair[] getMetaDate(String groupName, String remoteFileName) {
        try {
            NameValuePair[] nvp = storageClient.get_metadata(groupName, remoteFileName);
            return nvp;
        } catch (Exception ex) {
            ex.printStackTrace();
            return null;
        }
    }

    /**
     * 获取文件后缀名(不带点).
     *
     * @return 如:"jpg" or "".
     */
    private static String getFileExt(String fileName) {
        if (StringUtils.isBlank(fileName) || !fileName.contains(".")) {
            return "";
        } else {
            return fileName.substring(fileName.lastIndexOf(".") + 1); // 不带最后的点
        }
    }

}

主要用到的就是StorageClient这个类中提供的各种文件操作的方法,除了上传下载之外,还支持断点续传、修改、删除图片等操作。提供的方法倒是蛮多的,有兴趣自己去翻一翻源码:

测试一下使用Java API进行图片的上传是否好用:

拿着上传后返回的数组信息,直接访问Storage服务器获取图片:

进入Storage服务器的图片目录下,确实文件也老老实实躺在那儿:

有一点值得注意的是,在上传文件的时候,调用uploadFile方法,直接将File对象传进去,这样的图片是没有封装图片元数据的。如果需要图片的元数据,传参的时候就得传FileInputStream进去

有了图片的元数据,在图片下载的时候,就可以还原图片上传前的原始文件名。

FastDFS StorageClient线程不安全问题

虽然官方文档没有详细标注出来,但是StorageClient对象本身确是没有做线程安全的设计。所以在多线程调用的时候,FastDFSClient中提供的方法都会产生问题。最简单粗暴的解决方式,每个方法都无脑加上synchronized修饰,必然可以保证线程安全。

或者,可以考虑将StorageClient变成局部变量来保证线程的安全性,每次操作文件的时候都获取一个新的StorageClient对象:

加锁的方式效率肯定是最低的,局部变量的方式每次都要建立新的连接,效率同样的会受到影响,所以最好的方式其实是把StorageClient交给自定义的连接池来管理。而在实际的企业级开发中,就是与SpringBoot整合,直接交给SpringBoot来管理。

FastDFS整合SpringBoot

说到对FastDFS的集成,SpringBoot感觉也没太重视过,几乎找不到官方支持的依赖。不过,我从maven中心仓库里发现了一个现成的很好玩的依赖:

<dependency>
    <groupId>com.luhuiguo</groupId>
    <artifactId>fastdfs-spring-boot-starter</artifactId>
    <version>0.2.0</version>
</dependency>

不要问我这个卢惠国是何许人也。。。。。我也不知道。。。。。

依赖引入之后,来看看这个包需要我们给它那些参数:

上面提到的连接池的信息在这个框架中也有支持:

从源码中可以看到这些连接池的参数配置:

package com.luhuiguo.fastdfs.conn;

import org.apache.commons.pool2.impl.GenericKeyedObjectPoolConfig;

/**
 * 连接池配置
 * 
 * @author luhuiguo
 *
 */
public class ConnectionPoolConfig extends GenericKeyedObjectPoolConfig {

    /** 从池中借出的对象的最大数目 */
    public static final int FDFS_MAX_TOTAL = 50;

    /** 在空闲时检查有效性, 默认false */
    public static final boolean FDFS_TEST_WHILE_IDLE = true;

    /**
     * 连接耗尽时是否阻塞(默认true)
     * false报异常,ture阻塞直到超时
     */
    public static final boolean FDFS_BLOCK_WHEN_EXHAUSTED = true;

    /**
     * 获取连接时的最大等待毫秒数(如果设置为阻塞时BlockWhenExhausted)
     * 如果超时就抛异常,小于零:阻塞不确定的时间,默认-1
     */
    public static final long FDFS_MAX_WAIT_MILLIS = 100;

    public static final long FDFS_MIN_EVICTABLE_IDLETIME_MILLIS = 180000;

    /**
     * 逐出扫描的时间间隔(毫秒) 每过60秒进行一次后台对象清理的行动
     * 如果为负数,则不运行逐出线程, 默认-1
     */
    public static final long FDFS_TIME_BETWEEN_EVICTION_RUNS_MILLIS = 60000;

    /**
     * 每次逐出检查时 逐出的最大数目 如果为负数就是 : 1/abs(n), 默认3
     * -1表示清理时检查所有线程
     */
    public static final int FDFS_NUM_TESTS_PEREVICTION_RUN = -1;

    public ConnectionPoolConfig() {
        // 从池中借出的对象的最大数目
        setMaxTotal(FDFS_MAX_TOTAL);
        // 在空闲时检查有效性
        setTestWhileIdle(FDFS_TEST_WHILE_IDLE);
        // 连接耗尽时是否阻塞(默认true)
        setBlockWhenExhausted(FDFS_BLOCK_WHEN_EXHAUSTED);
        // 获取连接时的最大等待毫秒数100
        setMaxWaitMillis(FDFS_MAX_WAIT_MILLIS);
        // 视休眠时间超过了180秒的对象为过期
        setMinEvictableIdleTimeMillis(FDFS_MIN_EVICTABLE_IDLETIME_MILLIS);
        // 每过60秒进行一次后台对象清理的行动
        setTimeBetweenEvictionRunsMillis(FDFS_TIME_BETWEEN_EVICTION_RUNS_MILLIS);
        // 清理时候检查所有线程
        setNumTestsPerEvictionRun(FDFS_NUM_TESTS_PEREVICTION_RUN);

        setJmxEnabled(false);
    }

}

回到FdfsAutoConfiguration类中,可以看出核心的操作配置类:

package com.luhuiguo.fastdfs;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import com.luhuiguo.fastdfs.conn.ConnectionManager;
import com.luhuiguo.fastdfs.conn.ConnectionPoolConfig;
import com.luhuiguo.fastdfs.conn.FdfsConnectionPool;
import com.luhuiguo.fastdfs.conn.PooledConnectionFactory;
import com.luhuiguo.fastdfs.conn.TrackerConnectionManager;
import com.luhuiguo.fastdfs.service.AppendFileStorageClient;
import com.luhuiguo.fastdfs.service.DefaultAppendFileStorageClient;
import com.luhuiguo.fastdfs.service.DefaultFastFileStorageClient;
import com.luhuiguo.fastdfs.service.DefaultTrackerClient;
import com.luhuiguo.fastdfs.service.FastFileStorageClient;
import com.luhuiguo.fastdfs.service.TrackerClient;

@Configuration
@EnableConfigurationProperties(FdfsProperties.class)
public class FdfsAutoConfiguration {

  private final FdfsProperties properties;

  public FdfsAutoConfiguration(FdfsProperties properties) {
    super();
    this.properties = properties;
  }

  @Bean
  public PooledConnectionFactory pooledConnectionFactory() {
    PooledConnectionFactory pooledConnectionFactory = new PooledConnectionFactory();
    pooledConnectionFactory.setSoTimeout(properties.getSoTimeout());
    pooledConnectionFactory.setConnectTimeout(properties.getConnectTimeout());
    return pooledConnectionFactory;
  }


  @Bean
  @ConfigurationProperties(prefix = "fdfs.pool")
  public ConnectionPoolConfig connectionPoolConfig() {
    ConnectionPoolConfig connectionPoolConfig = new ConnectionPoolConfig();
    return connectionPoolConfig;
  }

  @Bean
  public FdfsConnectionPool fdfsConnectionPool(PooledConnectionFactory pooledConnectionFactory,
      ConnectionPoolConfig connectionPoolConfig) {
    FdfsConnectionPool pool =  new FdfsConnectionPool(pooledConnectionFactory, connectionPoolConfig);
    return pool;
  }

  @Bean
  public TrackerConnectionManager trackerConnectionManager(FdfsConnectionPool fdfsConnectionPool) {
    return new TrackerConnectionManager(fdfsConnectionPool, properties.getTrackerList());
  }

  @Bean
  public TrackerClient trackerClient(TrackerConnectionManager trackerConnectionManager) {
    return new DefaultTrackerClient(trackerConnectionManager);
  }

  @Bean
  public ConnectionManager connectionManager(FdfsConnectionPool fdfsConnectionPool) {
    return new ConnectionManager(fdfsConnectionPool);
  }

  @Bean
  public FastFileStorageClient fastFileStorageClient(TrackerClient trackerClient,
      ConnectionManager connectionManager) {
    return new DefaultFastFileStorageClient(trackerClient, connectionManager);
  }
  
  @Bean
  public AppendFileStorageClient appendFileStorageClient(TrackerClient trackerClient,
      ConnectionManager connectionManager) {
    return new DefaultAppendFileStorageClient(trackerClient, connectionManager);
  }

}

测试一下文件的上传用的怎么样:

拿着上传后返回的数组信息,直接访问Storage服务器获取图片:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/737801.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

XSS漏洞学习笔记

浏览器安全 同源策略 影响源的因素&#xff1a;host,子域名,端口,协议 a.com通过以下代码: <script scrhttp://b.com/b.js> 加载了b.com上的b.js&#xff0c;但是b.js是运行在a.com页面中的&#xff0c;因此相对于当前打开的页面(a.com)来说&#xff0c;b.js的源就应该…

Nodejs 学习笔记

Author&#xff1a;德玛玩前端 Date: 2023-07-06 Nodejs 一、Nodejs概述 1.1、什么是JavaScript 1995年由Netscape公司退出&#xff0c;后经ECMA统一标准的脚本语言。通常狭义上理解的JS是指在浏览器内置的JS解释器中运行的&#xff0c;主要用途是操作网页内容&#xff0c;实…

跨境电商亚马逊卖家为何要使用云服务器?

云服务器&#xff0c;作为跨境电商亚马逊开店必备的工具之一&#xff0c;深受各方卖家的青睐&#xff0c;由于跨境电商亚马逊平台有着一人一店的规定&#xff0c;但很多卖家朋友&#xff0c;为了获得更多的流量&#xff0c;便去开设多个店铺进行引流&#xff0c;这样操作极易诱…

【数据结构】搜索二叉树/map/set

二叉搜索树&#xff08;搜索二叉树&#xff09; 1.1.二叉搜索树概念 二叉搜索树又称二叉排序树&#xff0c;它或者是一棵空树&#xff0c;或者是具有以下性质的二叉树: 若它的左子树不为空&#xff0c;则左子树上所有节点的值都小于根节点的值 若它的右子树不为空&#xff0c;则…

【爆肝帝,花费3个月整理】金九银十面试季,2023年字节跳动所有,软件测试面试题拿走不谢!(附详细答案解析)

前言 最近有收到一些不同公司的面试题&#xff0c;像字节跳动、网易、美团等&#xff0c;趁着有时间&#xff0c;给大家梳理下&#xff0c;说不定面试能派上用场&#xff0c;大概给大家从以下几个方面来做了整理&#xff1a; 个人信息&#xff1a;(工作/实习经验&#xff0c;…

2023 ciscn 华东北分区赛 pwn minidb

2023 ciscn 华东北分区赛 pwn minidb 没去打比赛&#xff0c;做了一下&#xff0c;本地通了&#xff0c;不知道远程可不可以 结构体 00000000 Data struc ; (sizeof0x40, mappedto_8) 00000000 type dd ? 00000004 flag dd ? 00000008 database_name dq ? 00000010 pair d…

用html+javascript打造公文一键排版系统1:设计界面

近日&#xff0c;有同事抱怨收到的文件没有按公文要求进行排版&#xff0c;不得不自已动手帮他们擦PP排版&#xff0c;感慨每天都在做这些无意义的事情&#xff0c;浪费生命&#xff01; 于是打算用用htmljavascript打造公文一键排版系统。 首先是设置界面&#xff0c;主要包…

优化成本,探索WhatsApp API发送更经济的OTP验证信息

在现代的数字化世界中&#xff0c;安全性和使用者验证变得至关重要。随着移动应用程序和在线服务的普及&#xff0c;一次性密码&#xff08;OTP&#xff09;验证已经成为确保使用者身份验证的主要手段之一。然而&#xff0c;对于许多企业来说&#xff0c;发送OTP验证信息可能会…

fileinclude

看题目提示&#xff0c;应该是一道文件包含的题目&#xff0c;打开环境后直接告诉我flag在flag.php里 但是因为不知道绝对路径&#xff0c;不能直接利用file读取 查看源码后&#xff0c;发现里面嵌入了一段php代码 代码审计 首先&#xff0c;通过if( !ini_get(display_errors) …

springboot中banner.txt文件说明

springboot中banner.txt文件说明 通常在启动springboot项目的时候&#xff0c;&#xff0c;控制台会打印一些东西 比如&#xff1a; 如何自定义控制台输出的图形化符号 只需要在项目resources目录下创建一个banner.txt文件即可&#xff0c;因为启动的时候系统会自己检查该…

7.10作业

闹钟 mainWindow.ccp TCP服务器 #include "mainwindow.h" #include "ui_mainwindow.h"MainWindow::MainWindow(QWidget *parent): QMainWindow(parent), ui(new Ui::MainWindow) {ui->setupUi(this);server new QTcpServer(this);}MainWindow::~MainWi…

PDF文件转换成CAD图纸怎么做?简单好用的转换方法分享

CAD文件可以进行更加复杂的编辑&#xff0c;例如添加图层、修改线条颜色和粗细等&#xff0c;而PDF文件则只能进行简单的编辑操作。CAD软件中还可以添加文字注释、标注、尺寸和符号&#xff0c;这些功能大大提高了设计的灵活性和精度。下面给大家分享几种能够将PDF文件转换成CA…

交流充电桩通信方式和模块设计介绍

交流充电桩是新能源汽车充电系统的主要设备之一&#xff0c;可分为即插即用、刷卡取电和联网对接云端三种。即插即用&#xff0c;用户直接将充电枪连接到车辆上&#xff0c;就可以开始充电&#xff1b;刷卡取电&#xff0c;用户可以使用刷卡等方式取得充电权限&#xff0c;并根…

打印机一直重复打印不停止

打印一张纸&#xff0c;打印机一直重复打印不停止这个问题其实很简单&#xff0c;一般情况下是因为双向打印不兼容的问题&#xff1b; 选中打印机&#xff0c;点击右键&#xff0c;在弹出的菜单中选择“打印机属性” 在弹出的窗口中点“端口”&#xff0c;将“启用双向支持”前…

实现流程编排设计器的心路历程

接上回《「AntV」使用AntV X6实现流程编排设计器》一文说到&#xff0c;流程编排设计器的实现方案是将低代码引擎和AntV X6作为画布相结合。 为什么会有这样的想法&#xff1f; 可行性 起因是业务中有用到低代码引擎的场景&#xff0c;它的交互形式、页面结构正好符合流程编…

超级实用~低生物量的样本如何进行污染控制

上次小编主要介绍了低生物量比如口腔、阴道等样本的常见微生物和污染物&#xff0c;但是测序技术的高灵敏度也放大了样本中DNA污染的影响&#xff0c;那么对于低生物量的样本如何进行污染控制就至关重要了~ 2019年在《Contamination in Low Microbial Biomass Microbiome Studi…

vue + el-table点击表头改变其当前样式

废话不多说&#xff0c;先看效果&#xff1a; 网上找了一大圈没有符合的&#xff0c;只能自己看着搞&#xff1a; 直接贴代码&#xff1a; <el-tableref"table":data"tableData"borderstripesort-change"changeColumn"><el-table-colu…

vue语法详解

以下页面就是用vue开发的 模板语法 注意 模板语法不能在标签属性中用 文本插值 {{ msg }} 使用JavaScript表达式 {{ number 1 }} {{ ok ? YES : NO }} {{ message.split().reverse().join() }} 使用HTML 双大括号将会将数据插值为纯文本&#xff0c;而不是HTML&…

Apikit 自学日记:测试数据集

测试数据集 添加数据集的变量 在测试用例详情页面中&#xff0c;您可以点击上方的 测试数据 标签&#xff0c;进入用例的数据管理页面。在这里您可以添加多组测试数据&#xff0c;以及每组测试数据的变量。 在添加数据集前&#xff0c;我们需要设置数据集中存在什么变量。可以…

Qt:记录一下好看的配色

qss代码 窗体背景色 background-color: #ED6927; border-top-left-radius:35px;border-top-right-radius:35px;border-bottom-right-radius:0px; border-bottom-left-radius:0px;background-color: #203A32; border-radius:35px; border-top-left-radius:0px;border-top-righ…