分布式存储发展历程
前段时间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服务器获取图片: