1. 1. 分布式文件系统前期背景了解
    1. 1.1. 3. 选型要求
    2. 1.2. 常用分布式文件系统
    3. 1.3. 按功能分类
    4. 1.4. 详细评估优缺点
    5. 1.5. 初步结论
  2. 2. 5. FastDFS
    1. 2.1. 5.1 文档
    2. 2.2. 5.2 FastDFS架构
      1. 2.2.1. 1. 架构图
      2. 2.2.2. 2.角色
      3. 2.2.3. 3.架构解读
    3. 2.3. 5.3 FastDFS安装
      1. 2.3.1. 1.安装FastDFS依赖
      2. 2.3.2. 2 上传并解压libfastcommon-master
      3. 2.3.3. 3 编译并安装
      4. 2.3.4. 4 创建软连接
      5. 2.3.5. 5 上传并解压FastDFS主程序
      6. 2.3.6. 6 编译并安装FastDFS
      7. 2.3.7. 7 配置tracker
        1. 2.3.7.1. 7.1 复制配置文件
        2. 2.3.7.2. 7.2 创建数据目录
        3. 2.3.7.3. 7.3 修改配置文件
        4. 2.3.7.4. 7.4 启动服务
        5. 2.3.7.5. 7.5 查看服务运行状态
        6. 2.3.7.6. 7.6 关闭防火墙
      8. 2.3.8. 8 配置storage
        1. 2.3.8.1. 8.1 复制配置文件
        2. 2.3.8.2. 8.2 创建目录
        3. 2.3.8.3. 8.3 修改配置文件
      9. 2.3.9. 9 配置Nginx
        1. 2.3.9.1. 9.1上传并安装fastdfs-nginx-module
        2. 2.3.9.2. 9.2.修改配置文件
        3. 2.3.9.3. 9.3.安装nginx的依赖
        4. 2.3.9.4. 9.4.上传Nginx 并解压
        5. 2.3.9.5. 9.5.修改Nginx配置
        6. 2.3.9.6. 进入到Nginx文件夹
        7. 2.3.9.7. 创建临时目录
        8. 2.3.9.8. 修改配置文件参数
        9. 2.3.9.9. 9.6.编译并安装
        10. 2.3.9.10. 9.7 配置fastdfs-nginx-module模块配置文件
        11. 2.3.9.11. 9.8 修改 mod_fastdfs.conf
        12. 2.3.9.12. 进入到 /etc/fdfs
        13. 2.3.9.13. 编辑配置文件
        14. 2.3.9.14. 文件内容修改
        15. 2.3.9.15. 9.9. 提供FastDFS需要的HTTP配置文件
        16. 2.3.9.16. 9.10. 创建网络访问存储服务的软连接
        17. 2.3.9.17. 9.11. 修改nginx配置文件
        18. 2.3.9.18. 编辑配置文件
        19. 2.3.9.19. 修改内容
        20. 2.3.9.20. 9.12启动nginx
        21. 2.3.9.21. 9.13 http请求测试
    4. 2.4. 5.4 FastDFS Docker安装
    5. 2.5. 5.5 Fastdfs-java-client
      1. 2.5.1. 1 添加依赖
      2. 2.5.2. 2 导入工具类
    6. 2.6. 5.6 文件上传流程
      1. 2.6.1. 1 时序图
      2. 2.6.2. 2 流程说明
      3. 2.6.3. 3 新工程增加配置文件
      4. 2.6.4. 4. 测试上传功能
    7. 2.7. 5.7 文件下载
      1. 2.7.1. 1 时序图
      2. 2.7.2. 2 下载说明
      3. 2.7.3. 3 代码实现
      4. 2.7.4. 5.8 整合Spring Boot

分布式文件系统FastDFS入门

在我们的项目中有很多需要存储的内容出现,比如图片,视频,文件等等,在早期的时候用户量不大,产生的文件也不是很多,这时我们可以把文件和服务程序放在一个服务器中。
后面随着文件越来越多,服务器的资源会被文件资源大量占据,从而影响到服务器的稳定,这时我们可以单独的把文件服务器拆出来。
拆解出来后,文件服务的使用不会影响到我们的系统服务的稳定,但是当用户量越来越大,存储的文件就会越来越多。
这时如果还是单台的文件服务,比如100T的文件,这时是存储不下去的,这时就产生了我们将的分布式文件存储,

分布式文件系统(Distributed File System,DFS)是指文件系统管理的物理存储资源不一定直接连接在本地节点上,而是通过计算机网络与节点(可简单的理解为一台计算机)相连;或是若干不同的逻辑磁盘分区或卷标组合在一起而形成的完整的有层次的文件系统。DFS为分布在网络上任意位置的资源提供一个逻辑上的树形文件系统结构,从而使用户访问分布在网络上的共享文件更加简便。

分布式文件系统前期背景了解

3. 选型要求

需求
频繁读写、从不修改、基本不删除
能够存储图片、音频以及100MB以上的视频等文件
非常稳定、高可用、可扩容、可运维部署
上手简单、易于维护、社区相对活跃

常用分布式文件系统

分布式文件系统名称 描述
GFS(Google File System) Google公司为满足公司需求而开发的基于Linux的可扩展的分布式文件系统,用于大型的、分布式的、对大数据进行访问和应用,成本低,应用于廉价的普通硬件上,但不开源,暂不考虑。
TFS(Taobao File System) 阿里巴巴为满足了淘宝对小文件存储的需求而开发的一个可扩展、高可用、高性能、面向互联网服务、开源的分布式文件系统,主要针对海量的非结构化数据,它构筑在普通的Linux机器集群上,可为外部提供高可靠和高并发的存储访问。TFS为淘宝提供海量小文件存储,通常文件大小不超过1M,这个也暂不考虑。
HDFS(Hadoop Distributed File System) Hadoop分布式文件系统,适合运行在通用硬件上做分布式存储和计算,因为它具有高容错性和可扩展性的特点,可部署在廉价的机器上,适合大数据的处理,在离线批量处理大数据上有先天的优势。
Hadoop是Apache Lucene创始人Doug Cutting开发的使用广泛的文本搜索库。它起源于Apache Nutch,后者是一个开源的网络搜索引擎,本身也是Luene项目的一部分。Aapche Hadoop架构是MapReduce算法的一种开源应用,是Google开创其帝国的重要基石。
MooseFS MooseFS 是来自波兰的开源且具备冗余容错功能的分布式 POSIX 文件系统,也是参照了 GFS 的架构,实现了绝大部分 POSIX 语义和 API,它支持通过FUSE方式将文件挂载操作,同时其提供的web管理界面非常方便查看当前的文件存储状态,对master服务器有单点依赖,用perl编写,用于中、大型文件应用,但性能相对较差,由于可能会实时访问所以暂不考虑。
备注:POSIX表示可移植操作系统接口(Portable Operating System Interface of UNIX,缩写为 POSIX ),POSIX标准定义了操作系统应该为应用程序提供的接口标准
FastDFS 由淘宝的余庆先生所开发的一个开源分布式文件系统。它对文件进行管理,功能包括:文件存储、文件同步、文件访问(文件上传、文件下载)等,解决了大容量存储和负载均衡的问题。适合以文件为载体的在线服务,如相册网站、视频网站等等。FastDFS为互联网量身定制,充分考虑了冗余备份、负载均衡、线性扩容等机制,并注重高可用、高性能等指标,使用FastDFS搭建一套高性能的文件服务器集群提供文件上传、下载等服务。但是FastDFS部署有点麻烦,且它的SKD是不全的。
MogileFS MogileFS是一套高效开源的文件自动备份组件,由Six Apart开发,广泛应用在包括LiveJournal等web2.0站点上。支持多节点冗余,可实现自动的文件复制。不需要RAID,应用层可以直接实现RAID,不共享任何东西,通过集群接口提供服务工作于应用层,没有特殊的组件要求。使用HTTP方式通信。
国内所知道的使用 MogileFS 的公司有图片托管网站 yupoo又拍,digg, 土豆, 豆瓣,1 号店, 大众点评,搜狗,安居客等等网站.基本很多网站容量,图片都超过 30T 以上。
GridFS MongoDB是一种知名的NoSql数据库,GridFS是MongoDB的一个内置功能,它用于存储和恢复那些超过16M(BSON文件限制)的文件(如:图片、音频、视频等),是文件存储的一种方式,但是它是存储在MonoDB的集合中。它可以直接利用已建立的复制或分片机制,所以对于文件存储来说故障恢复和扩展都容易,且GridFS不产生磁盘碎片。
MinIO MinIO 是一个基于Apache License v2.0开源协议的对象存储服务。它兼容亚马逊S3云存储服务接口,非常适合于存储大容量非结构化的数据,例如图片、视频、日志文件、备份数据和容器/虚拟机镜像等,而一个对象文件可以是任意大小,从几kb到最大5T不等。它也是一个非常轻量的服务,可以很简单的和其他应用的结合。MinIO的特色在于简单、轻量级,对开发者友好,学习成本低,安装运维简单,开箱即用。
SeaweedFS SeaweedFS是基于go语言开发高度可扩展开源的分布式存储系统,能存储数十亿文件(最终受制于你的硬盘大小)、并且速度快,内存占用小。上手使用比fastDFS要简单很多,自带Rest API。对于中小型文件效率非常高,但是单卷最大容量被程序限制到30G,建议存储文件以100MB以内为主。
Ceph Ceph是Red Hat旗下一个成熟的分布式文件系统,而且还是一个有企业级功能的对象存储生态环境。该系统具备高性能、高可用性、高可扩展性、实时存储性等特点。虽然ceph很强大,但是学习成本高、安装运维复杂。Ceph用C++编写,存储容量可轻松达到PB级别。
GlusterFS GlusterFS 是由美国的 Gluster 公司开发的 POSIX 分布式文件系统(以 GPL 开源),它主要应用在集群系统中,具有高扩展性、高可用性、高性能、可横向扩展等特点,并且其没有元数据服务器的设计,让整个服务没有单点故障的隐患。该系统主要是为中大型文件设计的,存储容量可轻松达到PB。它存在扩容缩容影响服务器较多、遍历目录下文件耗时、小文件性能较差的缺点。

按功能分类

功能 系统列表
适合做通用文件系统 Ceph、MooseFS、MinIO;
适合做中小文件存储的文件系统 Ceph、FastDFS、MinIO、SeaweedFS;
适合做大文件存储的文件系统 HDFS、MinIO、Ceph、GridFS;
轻量级文件系统 FastDFS、MinIO、SeaweedFS;
简单易用,用户活跃的文件系统 HDFS、FastDFS、MinIO;

综上:Ceph目前不够成熟稳定,官方也明确指出不要把ceph用在生产环境中,暂不考虑;
经初步筛选剩下的文件系统有:HDFS、FastDFS、MinIO、GridFS。

详细评估优缺点

分布式文件系统名称 详细评估
HDFS 适合批量数据处理.可以部署在廉价的机器上。可以部署在廉价的机器上,但是不适合大量小文件,通过牺牲响应延时来换取高的吞吐量。
FastDFS 功能精简,支持在线扩容、冗余备份,部分支持跨集群同步,不存在单点故障,性能较好。但是不支持FUSE挂载和POSIX访问接口,且学习成本相对MinIO较高,且部署也相对比较复杂点。
MinIO 学习成本低,部署容易,适合存储大容量非结构化的数据,且有详细的中文文档。
GridFS 能够简化技术栈,如果已经使用了MongoDB,那么使用GridFS,就不需要其它独立的存储工具了(但是我们当前没引入MongoDB),不过性能不如直接访问文件系统快,而且无法修改文档。如果要修改GridFS里面的文档,只能是先删除再添加(对我们当前业务没有影响)

初步结论

目前提供的建议选型参考为MinIO或FastDFS,
如果想减少技术栈的话可以考虑HDFS或GridFS,
如果不在乎响应时间可以考虑HDFS。

5. FastDFS

FastDFS是一个轻量级的开源分布式文件系统。2008年4月份开始启动。类似google FS的一个轻量级分布式文件系统,纯C实现,支持Linux、FreeBSD、AIX等UNIX系统。

主要解决了大容量的文件存储和高并发访问的问题,文件存取时实现了负载均衡。实现了软件方式的磁盘阵列(Redundant Arrays of Independent Drives,RAID),可以使用廉价的IDE(Integrated Drive Electronics)硬盘进行存储。并且支持存储服务器在线扩容。支持相同内容的文件只保存一份,节约磁盘空间。

​ FastDFS只能通过Client API访问,不支持POSIX访问方式。

​ FastDFS特别适合大中型网站使用,用来存储资源文件(如:图片、文档、音频、视频等等)

Google FS体系结构

两个角色 架构特点
名字服务器(索引服务器) 不支持文件修改功能。
存储服务器 文件分块存储,需要索引服务器
一个文件可以存储多份,一个文件存储到哪些存储服务器,通常采用动态分配的方式。

5.1 文档

​ FastDFS没有官网。但是作者余庆(happy_fish100)担任chinaunix中FastDFS板块版主。并且会不定期更新板块中内容。http://bbs.chinaunix.net/

​ FastDFS软件可以在sourceforge中进行下载,最新版本为5.08,https://sourceforge.net/projects/fastdfs/files/

5.2 FastDFS架构

1. 架构图

2.角色

角色 介绍
Client 客户端。使用java语言编写的项目属于客户端。
Tracker Server 跟踪服务器,主要做调度工作,在访问上起负载均衡的作用。在内存中记录集群中group和storage server的状态信息,是连接Client和Storage server的枢纽。
Storage Server 存储服务器,文件和文件属性(meta data)都保存到存储服务器上

3.架构解读

​ 只有两个角色,tracker server和storage server,不需要存储文件索引信息。

​ 所有服务器都是对等的,不存在Master-Slave关系。

​ 存储服务器采用分组方式,同组内存储服务器上的文件完全相同(RAID 1)。

​ 不同组的storage server之间不会相互通信。

​ 由storage server主动向tracker server报告状态信息,tracker server之间不会相互通信。

5.3 FastDFS安装

1.安装FastDFS依赖

FastDFS是C语言开发的应用。安装必须使用 make , cmake 和 gcc编译器。

1
yum install -y make cmake gcc gcc-c++

2 上传并解压libfastcommon-master

​ 上传libfastcommon-master 到 /root下。 libfastcommon是从FastDFS和FastDHT中提取出来的公共C函数库

​ 解压 libfastcommon-master.zip 由于是zip文件所以要使用 unzip命令

1
2
cd ~
unzip libfastcommon-master.zip

3 编译并安装

​ libfastcommon没有提供make命令安装文件。使用的是shell脚本执行编译和安装。shell脚本为 make.sh

1
2
3
4
5
6
## 进入解压后的文件
cd libfastcommon-master
## 编译
./make.sh
## 安装
./make.sh install

​ 有固定的默认安装位置。在/usr/lib64 和 /usr/include/fastcommon两个目录中

4 创建软连接

​ 因为FastDFS 主程序设置的lib目录是 /usr/local/lib, 所以需要创建软连接

1
2
# ln -s /user/lib64/libfastcommon.so /usr/local/lib/libfastcommon.so
# ln -s /usr/local/lib64/libfdfsclient.so /usr/local/lib/libfdfsclient.so

5 上传并解压FastDFS主程序

​ 上传 FastDFS_v5.08.tar.gz 到 /root下后解压

1
2
cd ~
tar zxf FastDFS_v5.08.tar.gz

6 编译并安装FastDFS

1
2
3
4
5
6
## 进入到解压后的FastDFS文件中
cd FastDFS
## 编译
./make.sh
## 安装
./make.sh install

​ 安装后 FastDFS主程序所在的位置是

目录 文件说明
/usr/bin 可执行文件所在的位置
/etc/fdfs 配置文件所在的位置
/usr/bin 主程序代码所在位置
/usr/include/fastdfs 包含一些插件组所在的位置

7 配置tracker

7.1 复制配置文件

​ 进入到 /etc/fdfs 中 , 把tracker配置文件复制一份

1
2
cd /etc/fdfs
cp tracker.conf.sample tracker.conf
7.2 创建数据目录

​ 创建放置 tracker数据的目录

1
mkdir -p /usr/local/fastdfs/tracker
7.3 修改配置文件

​ 修改 tracker.conf 设置 tracker 内容存储目录

1
2
#vim tracker.conf
base_path=/usr/local/fastdfs/tracker

默认端口 22122 不需要修改

7.4 启动服务
1
service fdfs_trackerd start

​ 启动成功后, 配置文件中 base_path 指向的目录出现 FastDFS服务相关数据目录(data目录, logs 目录)

7.5 查看服务运行状态
1
service fdfs_trackerd status

​ 如果显示 is running 表示正常运行。

7.6 关闭防火墙
1
2
3
service iptables stop
chkconfig iptables off
systemctl stop firewalld #centos7 关闭防火墙

8 配置storage

storage可以和tracker不在同一台服务器上。示例中把storage和tracker安装在同一台服务器上了。

8.1 复制配置文件

​ 进入到 /etc/fdfs, 把 storage 配置文件复制一份

1
2
cd /etc/fdfs
cp storage.conf.sample storage.conf
8.2 创建目录

​ 创建两个目录, 把base用于存储基础数据和日志,store用于存储上传数据。

1
2
mkdir -p /usr/local/fastdfs/storage/base
mkdir -p /usr/local/fastdfs/storage/store
8.3 修改配置文件

​ storage.conf配置文件用于描述存储服务的行为,需要进行下述修改

1
vim /etc/fdfs/storage.conf

​ 配置内容如下:

1
2
3
base_path=/usr/local/fastdfs/storage/base
store_path0=/usr/local/fastdfs/storage/store
tracker_server=tracker 服务IP:22122
参数 说明
base_path 基础路径。用于保存storage server 基础数据内容和日志内容的目录。
store_path0 存储路径。是用于保存FastDFS中存储文件的目录,就是要创建256*256个子目录的位置。
tracker_server 跟踪服务器位置。就是跟踪服务器的IP和端口。

base_path 和 store_path0 可以使用同一个目录。

​ 启动服务

1
service fdfs_storaged start

​ 启动成功后,配置文件中base_path 指向的目录中出现FastDFS服务相关数据目录(data目录、logs目录)配置文件中的store_path0指向的目录中同样出现FastDFS存储相关数据录(data目录)。其中$store_path0/data/目录中默认创建若干子孙目录(两级目录层级总计256*256个目录),是用于存储具体文件数据的。

​ Storage 服务器启动比较慢,因为第一次启动的时候,需要创建256*256个目录。

​ 查看启动状态

1
service fdfs_storaged status

9 配置Nginx

​ FastDFS是没有文件访问功能的,需要借助其他工具实现图片HTTP访问的。Nginx就具备代理虚拟机主机功能。

​ Nginx (engine x) 是一个高性能的HTTP反向代理服务。Nginx是由伊戈尔·赛索耶夫为俄罗斯访问量第二的Rambler.ru站点(俄文:Рамблер)开发的,第一个公开版本0.1.0发布于2004年10月4日。

​ Nginx 是一个很强大的高性能Web反向代理服务,它具有很多非常优越的特性:在连接高并发的情况下,Nginx是Apache服务不错的替代品:Nginx在美国是做虚拟主机生意的老板们经常选择的软件平台之一。

9.1上传并安装fastdfs-nginx-module

​ 上传 /fastdfs-nginx-model_v1.16.tar.gz 到 /root 中

1
2
cd /root
tar zxf fastdfs-nginx-module_v1.16.tar.gz
9.2.修改配置文件

进入解压目录中src目录

1
2
cd fastdfs-nginx-module/src
vim config

​ 修改配置文件中第四行,把路径中local去掉。参数是用于配置安装nginx中的FastDFS组件的时候,在什么位置查找FastDFS核心代码。

修改结果如下:

9.3.安装nginx的依赖
1
# yum install -y gcc gcc-c++ make automake autoconf libtool pcre pcre-devel zlib zlib-devel openssl openssl-devel
9.4.上传Nginx 并解压

上传nginx-1.16.1.tar.gz 到/root中

1
2
cd /root
tar zxf nginx-1.16.1.tar.gz
9.5.修改Nginx配置
进入到Nginx文件夹
1
cd nginx-1.16.1
创建临时目录

​ 修改配置文件中好多位置都使用了/var/temp/nginx目录,但是默认不会自动创建这个目录,需要手动创建。

1
# mkdir -p /var/temp/nginx
修改配置文件参数
1
2
3
4
5
6
7
8
9
10
11
12
13
./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=/root/fastdfs-nginx-module/src

​ –add-module 必须定义,此配置信息是用于指定安装Nginx时需要加载的模块,如果未指定,Nginx安装过程不会加载fastdfs-nginx-module模块,后续功能无法实现。

9.6.编译并安装
1
2
make
make install
9.7 配置fastdfs-nginx-module模块配置文件

​ 复制配置文件fastdfs-nginx-module/src/mod_fastdfs.conf 到 /etc/fdfs目录中

1
cp /root/fastdfs-nginx-module/src/mod_fastdfs.conf /etc/fdfs/
9.8 修改 mod_fastdfs.conf
进入到 /etc/fdfs
1
cd /etc/fdfs
编辑配置文件
1
vim mod_fastdfs.conf
文件内容修改

​ 需要修改文件中四处内容, 这四处内容的含义:

1
2
3
4
5
6
7
connect_timeout=2 #连接超时时间,单位秒

tracker_server=tracker:22122 #tracker 服务结点

url_have_group_name=false #URL是否包含group名称

store_path0=/home/yuqing/fastdfs #storage服务结点的存储位置,与配置storage结点一致

修改结果如下:

1
2
3
4
connect_timeout=10
tracker_server=192.168.93.10:22122
url_have_group_name=true
store_path0=/usr/local/fastdfs/storage/store
9.9. 提供FastDFS需要的HTTP配置文件

复制FastDFS安装包中的两个配置文件(http.conf 和 mine.types) 到 /etc/fdfs目录中

1
2
cp /root/FastDFS/conf/http.conf /etc/fdfs/
cp /root/FastDFS/conf/mime.types /etc/fdfs/
9.10. 创建网络访问存储服务的软连接

​ 在上传文件到FastDFS后,FastDFS会返回group1/M00/00/00/xxxxxxxxx.xxx其中group1是卷名,在mod_fastdfs.conf配置文件中已配置了url_have_group_name, 以保证URL解析正确。其中的M00是FastDFS保存数据时使用的虚拟目录, 需要将这个虚拟目录定位到真实数据目录上。

1
ln -s /usr/local/fastdfs/storage/store/data/ /usr/local/fastdfs/storage/store/data/M00
9.11. 修改nginx配置文件

进入到安装后 nginx目录,注意是安装目录,不是解压目录

1
cd /usr/local/nginx/conf
编辑配置文件
1
vim nginx.conf
修改内容

​ 需要修改两处

第一处

​ user root; #Nginx需要访问linux文件系统,必须有文件系统的权限。User root代表nginx文件系统的权限是root用户权限。如果不开启权限,可能有404反问错误。

​ 默认效果:此内容在文件最上面

​ 修改后的效果:去掉注释,user后面写上root

第二处

1
2
3
4
5
6
7
server{
listen 8888; #storage 配置中, 有http.server_post=8888的配置信息,必须一致。配置文件是 /etc/fdfs/storaged.conf
server_name localhost;
location ~/group([0-9])/M00{
ngx_fastdfs_module;
}
}

​ 默认效果:

​ 修改后的效果:

9.12启动nginx
1
2
3
4
5
6
## 进入到nginx安装目录的sbin文件夹
cd /usr/local/nginx/sbin/
## 启动nginx
./nginx
## 关闭nginx
./nginx -s quit
9.13 http请求测试

http://ip:nginx端口/group1/M00/00/00/xxx.jpg

5.4 FastDFS Docker安装

1
2
3
4
5
6
7
8
docker search fastdfs
docker pull delron/fastdfs
#创建 fastdfs工作空间,存一些配置文件
mkdir -p /Users/fubin/DevTool/fastdfs
## 创建Tracker服务,tracker服务默认的端口为22122,-v 实现了容器和本地目录的挂载操作。
docker run -d --name tracker --network=host -v /Users/fubin/DevTool/fastdfs/tracker:/var/fdfs delron/fastdfs tracker
## 创建Storage服务,在执行该命令时要注意对应修改TRACKER_SERVER中的ip要修改为你的Tracker服务所在的服务IP地址。
docker run -d --name storage --network=host -e TRACKER_SERVER=指定ip:22122 -v /Users/fubin/DevTool/fastdfs/storage:/var/fdfs -e GROUP_NAME=group1 delron/fastdfs storage

默认情况下在Storage服务中是帮我们安装了Nginx服务的,相关的端口为

服务 默认端口
tracker 22122
storage 23000
Nginx 8888

如果端口被占用,需要进入容器修改,docker exec -it storage bash

fastdfs提供了命令行工具来做测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[root@docker-desktop nginx-1.12.2]# ls -al /usr/bin | grep fdfs
-rwxr-xr-x 1 root root 304152 Apr 29 2018 fdfs_append_file
-rwxr-xr-x 1 root root 317520 Apr 29 2018 fdfs_appender_test
-rwxr-xr-x 1 root root 317296 Apr 29 2018 fdfs_appender_test1
-rwxr-xr-x 1 root root 303872 Apr 29 2018 fdfs_crc32
-rwxr-xr-x 1 root root 304208 Apr 29 2018 fdfs_delete_file
-rwxr-xr-x 1 root root 304944 Apr 29 2018 fdfs_download_file
-rwxr-xr-x 1 root root 304536 Apr 29 2018 fdfs_file_info
-rwxr-xr-x 1 root root 322448 Apr 29 2018 fdfs_monitor
-rwxr-xr-x 1 root root 1111864 Apr 29 2018 fdfs_storaged
-rwxr-xr-x 1 root root 327464 Apr 29 2018 fdfs_test
-rwxr-xr-x 1 root root 326680 Apr 29 2018 fdfs_test1
-rwxr-xr-x 1 root root 453984 Apr 29 2018 fdfs_trackerd
-rwxr-xr-x 1 root root 305136 Apr 29 2018 fdfs_upload_appender
-rwxr-xr-x 1 root root 306160 Apr 29 2018 fdfs_upload_file

我们可以使用/usr/bin/fdfs_upload_file /etc/fdfs/client.conf 1.jpg来上传图片

5.5 Fastdfs-java-client

1 添加依赖

1
2
3
4
5
6
7
8
9
10
11
12
<dependencies>
<dependency>
<groupId>cn.bestwu</groupId>
<artifactId>fastdfs-client-java</artifactId>
<version>1.27</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.4</version>
</dependency>
</dependencies>

2 导入工具类

​ 在com.utils.FastDFSClient 下粘贴配置工具类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;

import org.apache.commons.lang3.StringUtils;
import org.csource.common.NameValuePair;
import org.csource.fastdfs.ClientGlobal;
import org.csource.fastdfs.StorageClient;
import org.csource.fastdfs.StorageClient1;
import org.csource.fastdfs.StorageServer;
import org.csource.fastdfs.TrackerClient;
import org.csource.fastdfs.TrackerServer;

/**
* FastDFS分布式文件系统操作客户端.
*/
public class FastDFSClient {

private static final String CONF_FILENAME = Thread.currentThread().getContextClassLoader().getResource("").getPath() + "fdfs_client.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); // 不带最后的点
}
}
}

5.6 文件上传流程

1 时序图

2 流程说明

  1. 客户端访问Tracker
  2. Tracker 返回Storage的ip和端口
  3. 客户端直接访问Storage,把文件内容和元数据发送过去。
  4. Storage返回文件存储id。包含了组名和文件名

3 新工程增加配置文件

​ 文件名:fdfs_client.conf

​ 修改成自己的tracker服务器ip

1
2
3
4
5
connect_timeout = 10
network_timeout = 30
charset = UTF-8
http.tracker_http_port = 8080
tracker_server = 192.168.93.10:22122

4. 测试上传功能

​ 随意新建一个包含主方法的类。com.xxx.MyMain

1
2
3
4
5
6
7
8
9
10
11
12
13
public class FastDFSUpload {
public static void main(String[] args) {
try {
File file = new File("D:/b.png");
InputStream is = new FileInputStream(file);
String fileName = UUID.randomUUID().toString()+".png";
String[] result = FastDFSClient.uploadFile(is, fileName);
System.out.println(Arrays.toString(result));
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
}

5.7 文件下载

1 时序图

2 下载说明

  1. client询问tracker下载文件的storage,参数为文件标识(组名和文件名);

  2. tracker返回一台可用的storage;

  3. client直接和storage通讯完成文件下载。

3 代码实现

​ 直接使用工具方法完成下载。

1
2
3
4
5
6
7
8
9
10
11
12
13
try {
InputStream is = FastDFSClient.downloadFile("group1", "M00/00/00/wKg0gF3zAKCARs6kAAASjQVYlWA098.png");
OutputStream os = new FileOutputStream(new File("D:/jqk.png"));
int index = 0 ;
while((index = is.read())!=-1){
os.write(index);
}
os.flush();
os.close();
is.close();
} catch (IOException e) {
e.printStackTrace();
}

注意:StorageClient是线程不安全的。那么我们的解决方案

  1. 对文件的操作的每个方法我们做同步处理
  2. 每次操作文件的时候我们都获取一个新的StorageClient对象

第一种方式效率肯定是最低的,第二种方式每次都要建立新的连接效率同样的会受到影响,这时最好的方式其实是把StorageClient交给我们自定义的连接池来管理

5.8 整合Spring Boot

我们在实际工作中基本都是和SpringBoot整合在一起来使用的,那么我们就来看看FastDFS是如何在SpringBoot项目中来使用的。首先创建一个普通的SpringBoot项目,然后导入fastdfs-spring-boot-starter这个依赖。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>

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

&emsp;&emsp;既然是一个starter,那么必然会在spring.factories文件中提供对应的自动配置类。

&emsp;&emsp;可以看到给我们提供的配置类为FdfsAutoConfiguration进入后可以看到帮我们注入了很多的核心对象。

&emsp;&emsp;然后可以看到系统提供的配置信息,前缀为 fdfs

&emsp;&emsp;然后我们就可以在application.properties中配置FastDFS的配置信息了。

&emsp;&emsp;配置完成后我们就可以测试文件的上传下载操作了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@SpringBootTest
class FastDfsSpringBootApplicationTests {

@Autowired
public FastFileStorageClient storageClient;


@Test
void contextLoads() throws Exception{
// http://10.211.55.4:8888/group1/M00/00/00/group1/M00/00/00/CtM3BGNNCXSAT_v3AAtTC5Fgj5c785.jpg
File file = new File("/Users/fubin/Pictures/wallhaven/331673.jpg");
//图片后缀
String[] split = file.getName().split("\\.");
StorePath path = fastFileStorageClient.uploadFile(null,new FileInputStream(file),file.length(),split[1]);
System.out.println(path.getFullPath());
}

}