目录
1.什么是FastDFS?
1.1简介
1.2FastDFS的存储策略
1.3FastDFS的上传过程
1.4FastDFS的文件同步
1.5FastDFS的文件下载
2.FastDFS搭建
2.1环境介绍
2.2依赖库安装
2.2.1下载安装libfastcommon
2.3安装FastDFS
2.4 配置FastDFS
2.4.1配置跟踪器Tracker
2.4.2配置存储Storage
2.4.3 文件上传测试
3.FastDFS集成Nginx 模块
3.1Nginx安装
4.集成Springboot
4.1环境
4.2依赖包
4.3配置
4.4编写核心代码
4.4.1创建FastDFSFile实体类
4.4.2创建FastDFSClient类
4.4.3封装文件上传工具类:
4.4.4编写文件上传的controller
4.4.5编写文件上传的页面index.html
4.5 测试
关于FastDFS的更多参考资料可以访问下面几个地址:
余庆大佬的Github:https://github.com/happyfish100 遗憾的是wiki不全,要上手的话还是得先找别的资料学习一下.
FastDFS的设计原理:http://blog.chinaunix.net/uid-20196318-id-4058561.html 推荐,可以帮助更好的理解FastDFS.
个人封装的Springboot+FastDFS实现图片的上传和获取Demo:https://github.com/laohanjianshen/springboot-fdfs 建议在fdfs搭好之后再来看.
本篇原理部分参考这篇:https://www.cnblogs.com/chiangchou/p/fastdfs.html#_label0_0 搭建不建议参考这篇,有坑.但无论如何感谢作者.
FastDFS 是一个开源的高性能分布式文件系统(DFS)。 它的主要功能包括:文件存储,文件同步和文件访问,以及高容量和负载平衡。是由淘宝的架构师余庆写的,虽然存在一些缺陷,现已被淘宝弃用,但由于其开源和免费以及好用,对一些中小型公司仍是不错的选择.比较适合小文件的海量存储(建议范围:4KB < file_size <500MB),如果文件比较大的话,可以考虑用HDFS.
FastDFS主要有三个角色:跟踪服务TrackerServer,存储服务StorageServer,客户端Client.
Tracker Server:跟踪服务器,主要做调度工作,起到均衡的作用;负责管理所有的 storage server和 group,每个 storage 在启动后会连接 Tracker,告知自己所属 group 等信息,并保持周期性心跳。
Storage Server:存储服务器,主要提供容量和备份服务;以 group 为单位,每个 group 内可以有多台 storage server,数据互为备份。
Client:客户端,上传下载数据的服务器,也就是我们自己的项目所部署在的服务器。
为了支持大容量,存储节点(服务器)采用了分卷(或分组)的组织方式。存储系统由一个或多个卷组成,卷与卷之间的文件是相互独立的,所有卷的文件容量累加就是整个存储系统中的文件容量。一个卷可以由一台或多台存储服务器组成,一个卷下的存储服务器中的文件都是相同的,卷中的多台存储服务器起到了冗余备份和负载均衡的作用。
在卷中增加服务器时,同步已有的文件由系统自动完成,同步完成后,系统自动将新增服务器切换到线上提供服务。当存储空间不足或即将耗尽时,可以动态添加卷。只需要增加一台或多台服务器,并将它们配置为一个新的卷,这样就扩大了存储系统的容量。
FastDFS向使用者提供基本文件访问接口,比如upload、download、append、delete等,以客户端库的方式提供给用户使用。
Storage Server会定期的向Tracker Server发送自己的存储信息。当Tracker Server Cluster中的Tracker Server不止一个时,各个Tracker之间的关系是对等的,所以客户端上传时可以选择任意一个Tracker。
当Tracker收到客户端上传文件的请求时,会为该文件分配一个可以存储文件的group,当选定了group后就要决定给客户端分配group中的哪一个storage server。当分配好storage server后,客户端向storage发送写文件请求,storage将会为文件分配一个数据存储目录。然后为文件分配一个fileid,最后根据以上的信息生成文件名存储文件。
写文件时,客户端将文件写至group内一个storage server即认为写文件成功,storage server写完文件后,会由后台线程将文件同步至同group内其他的storage server。
每个storage写文件后,同时会写一份binlog,binlog里不包含文件数据,只包含文件名等元信息,这份binlog用于后台同步,storage会记录向group内其他storage同步的进度,以便重启后能接上次的进度继续同步;进度以时间戳的方式进行记录,所以最好能保证集群内所有server的时钟保持同步。
storage的同步进度会作为元数据的一部分汇报到tracker上,tracke在选择读storage的时候会以同步进度作为参考。
客户端uploadfile成功后,会拿到一个storage生成的文件名,接下来客户端根据这个文件名即可访问到该文件。
跟upload file一样,在downloadfile时客户端可以选择任意tracker server。tracker发送download请求给某个tracker,必须带上文件名信息,tracke从文件名中解析出文件的group、大小、创建时间等信息,然后为该请求选择一个storage用来服务读请求。
在理清了上面这些FastDFS的原理和流程之后,下面开始正式搭建,在正式搭建前一定要理解上面的原理,否则你会很困惑.
环境:CentOS7.3 64位操作系统
FastDFS版本:V5.05
跟网上众多教程一样,本文也仅演示单机版的,原因主要是因为比较容易理解,在写这篇我搭了一个6个节点的分布式FastDFS,但如果搭建过程都贴上来的话,不仅重复的内容多,而且极难理解,建议初学者先按本篇搭建一个单机版的,搭建完之后你会有一个更深入的理解,在此基础上再考虑进行分布式演进,同时可以加深印象,不必急于求成直接就搭一个N节点的系统,我一开始也这么想的,但现在回头发现这么做其实很蠢,就好比走都没学会就想飞是一样的. 为了避免浪费不必要的时间,建议安装的依赖库,版本,以及操作顺序都按本篇走,因为我亲自踩过一遍坑,确保本篇是100%走的通的.
如果你确实想一步登天,我也给你个传送门:https://www.jianshu.com/p/88ccae4cbd82 可以参考这篇的架构,比较合理,节点数也适中,前提你得有6台服务器或者你的电脑内存16G及以上,开6个虚拟机.
#下载
wget https://github.com/happyfish100/libfastcommon/archive/V1.0.7.tar.gz
#解压
# tar -zxvf V1.0.7.tar.gz
# cd libfastcommon-1.0.7
#安装
./make.sh
./make.sh install
创建软链接:
#libfastcommon.so 安装到了/usr/lib64/libfastcommon.so,但是FastDFS主程序设置的lib目录是/usr/local/lib,所以需要创建软链接
ln -s /usr/lib64/libfastcommon.so /usr/local/lib/libfastcommon.so
ln -s /usr/lib64/libfastcommon.so /usr/lib/libfastcommon.so
ln -s /usr/lib64/libfdfsclient.so /usr/local/lib/libfdfsclient.so
ln -s /usr/lib64/libfdfsclient.so /usr/lib/libfdfsclient.so
#下载
wget https://github.com/happyfish100/fastdfs/archive/V5.05.tar.gz
#解压
tar -zxvf V5.05.tar.gz
cd fastdfs-5.05
#安装
./make.sh
./make.sh install
创建软链接:
#由于FastDFS 服务脚本设置的 bin 目录是 /usr/local/bin, 但实际命令安装在 /usr/bin/ 下,所以需要创建软链接
ln -s /usr/bin/fdfs_trackerd /usr/local/bin
ln -s /usr/bin/fdfs_storaged /usr/local/bin
ln -s /usr/bin/stop.sh /usr/local/bin
ln -s /usr/bin/restart.sh /usr/local/bin
配置文件的详细参考文档可以看这篇:FastDFS配置文件参考
① 进入/etc/fdfs,复制FastDFS跟踪器样例配置文件 tracker.conf.sample,并重命名为tracker.conf
cd /etc/fdfs
cp tracker.conf.sample tracker.conf
vim tracker.conf
②编辑tracker.conf,只需要改我下面提到的即可,其他保持默认.
# 配置文件是否不生效,false 为生效
disabled=false
# Tracker 数据和日志目录地址(根目录必须存在,子目录会自动创建),为了尽量少的改动,我这里在余庆老师给的默认地址上加个tracker文件夹即可
base_path=/home/yuqing/fastdfs/tracker
# HTTP 服务端口 当然你保持8080也没毛病,只要不冲突.
http.server_port=80
③创建tracker基础数据目录,即base_path对于的目录
mkdir -p /home/yuqing/fastdfs/tracker
④关闭防火墙或打开跟踪端口(默认22122)
vim /etc/sysconfig/iptables
添加如下端口行:
-A INPUT -m state --state NEW -m tcp -p tcp --dport 22122 -j ACCEPT
重启防火墙:
service iptables restart
或者用下面这种方式也行,我个人一直用这种方式:
/sbin/iptables -I INPUT -p tcp --dport 22122 -j ACCEPT
iptalbes-save
⑤启动Tracker
初次成功启动tracker后,它会自动在我们配置的目录(/home/yuqing/fastdfs/tracker)下创建data和log两个目录.
/etc/init.d/fdfs_trackerd start
启动后,可以通过下面的命令查看是否启动成功:
netstat -unltp|grep fdfs
如果看到22122端口处于LISTEN状态,说明Tracker已经成功启动.
关闭Tracker命令:
service fdfs_trackerd stop
⑥ 设置Tracker开机启动
chkconfig fdfs_trackerd on
或者:
vim /etc/rc.d/rc.local
加入配置:
/etc/init.d/fdfs_trackerd start
至此,tracker server就配置OK了,接下来的storage的配置步骤也几乎如出一辙.
① 进入 /etc/fdfs 目录,复制 FastDFS 存储器样例配置文件 storage.conf.sample,并重命名为 storage.conf
cd /etc/fdfs
cp storage.conf.sample storage.conf
vim storage.conf
② 编辑storage.conf
下面提到的需要修改,其它的默认即可
# 配置文件是否不生效,false 为生效
disabled=false
# Storage 数据和日志目录地址(根目录必须存在,子目录会自动生成)
base_path=/home/yuqing/fastdfs/storage
# 存放文件时 storage server 支持多个路径。这里配置存放文件的基路径数目,通常只配一个目录。
store_path_count=1
# 逐一配置 store_path_count 个路径,索引号基于 0。
# 如果不配置 store_path0,那它就和 base_path 对应的路径一样。
store_path0=/home/yuqing/fastdfs/file
# tracker_server 的列表 ,会主动连接 tracker_server
# 有多个 tracker server 时,每个 tracker server 写一行
#填写你刚刚启动的trackerServer的ip和端口号
tracker_server=192.168.174.128:22122
# 访问端口
http.server_port=80
③ 创建Storage基础数据目录,对应base_path目录
mkdir -p /home/yuqing/fastdfs/storage
# 这是配置的store_path0路径
mkdir -p /home/yuqing/fastdfs/file
④ 防火墙中打开存储器端口(默认的 23000)
vim /etc/sysconfig/iptables
添加如下端口行:
-A INPUT -m state --state NEW -m tcp -p tcp --dport 23000 -j ACCEPT
重启防火墙:
service iptables restart
⑤ 启动 Storage
启动Storage前确保Tracker是启动的。初次启动成功,会在 /home/yuqing/fastdfs/storage 目录下创建 data、 logs 两个目录。
可以用这种方式启动
# /etc/init.d/fdfs_storaged start
也可以用这种方式,后面都用这种
# service fdfs_storaged start
查看 Storage 是否成功启动,23000 端口正在被监听,就算 Storage 启动成功。
netstat -unltp|grep fdfs
关闭Storage命令:
service fdfs_storaged stop
查看Storage和Tracker是否在通信:
/usr/bin/fdfs_monitor /etc/fdfs/storage.conf
⑥ 设置 Storage 开机启动
chkconfig fdfs_storaged on
#或者:
vim /etc/rc.d/rc.local
#加入配置:
/etc/init.d/fdfs_storaged start
⑦ Storage 目录
同 Tracker,Storage 启动成功后,在base_path 下创建了data、logs目录,记录着 Storage Server 的信息。
在 store_path0 目录下,创建了N*N个子目录:
① 修改 Tracker 服务器中的客户端配置文件
cd /etc/fdfs
cp client.conf.sample client.conf
vim client.conf
修改如下配置即可,其它默认。
# Client 的数据和日志目录
base_path=/home/yuqing/fastdfs/client
# Tracker端口
tracker_server=192.168.174.128:22122
②上传测试
在linux内部执行如下命令上传 root目录下的某张图片(当然你可以上传其他目录下的图片都OK,我这里正好root下有一张)
# /usr/bin/fdfs_upload_file /etc/fdfs/client.conf /root/a.png
上传成功后返回文件ID号:group1/M00/00/00/wKiugFxQeEuAXSoXAABPKl4woFc369.png
返回的文件ID由group、存储目录、两级子目录、fileid、文件后缀名(由客户端指定,主要用于区分文件类型)拼接而成。
Nginx的安装本不想在这里赘述的,已经写过很多篇了,这里就简单演示下,如果不会可以翻我之前的博客.
演示中的Nginx的版本:1.12.2
在安装Nginx前,请先确定你的服务器上已安装相应的库,比如:gcc,PCRE,pcre-devel,zlib,OpenSSL等依赖,若无请自行用yum安装.
①:下载Nginx和FastDFS的Nginx模块:
wget -c https://nginx.org/download/nginx-1.12.2.tar.gz
wget https://github.com/happyfish100/fastdfs-nginx-module/archive/master.zip
②分别解压之:
tar -zxvf nginx-1.12.2.tar.gz
unzip master.zip
③编译和安装:
#进入Nginx解压后的目录,执行下面这条命令添加fdfs-nginx模块:
./configure --add-module=../fastdfs-nginx-module-master/src/
#编译和安装
make && make install
安装完成后确认下是否成功:
/usr/local/nginx/sbin/nginx -V
如果看到红框中的就成功了:
如果出现下图这种错误,请检查你的Nginx版本及fdfs-nginx版本,可能存在不兼容问题.
④配置fdfs-nginx模块:
#复制 fastdfs-nginx-module 源码中的配置文件到/etc/fdfs 目录,并修改:
cd /softpackages/fastdfs-nginx-module-master/src
cp mod_fastdfs.conf /etc/fdfs/
vi /etc/fdfs/mod_fastdfs.conf
修改如下配置,其它的保持默认:
# 连接超时时间
connect_timeout=10
# Tracker Server
tracker_server=192.168.174.128:22122
# StorageServer 默认端口
storage_server_port=23000
# 如果文件ID的uri中包含/group**,则要设置为true
url_have_group_name = true
# Storage 配置的store_path0路径,必须和storage.conf中的一致
store_path0=/home/yuqing/fastdfs/file
⑤复制 FastDFS 的部分配置文件到/etc/fdfs 目录,这步不可少:
cd /softpackages/fastdfs-5.05/conf/
cp anti-steal.jpg http.conf mime.types /etc/fdfs/
⑥配置nginx,修改nginx.conf.
vim /usr/local/nginx/conf/nginx.conf
#在80端口下添加fastdfs-nginx模块,其它配置保持默认
location ~/group([0-9])/M00 {
ngx_fastdfs_module;
}
为了防止有些人配错,我直接贴一份我的配置,以供复制:
worker_processes 1;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
server {
listen 80;
server_name localhost;
location / {
root html;
index index.html index.htm;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html{
root html;
}
location ~/group([0-9])/M00{
ngx_fastdfs_module;
}
}
}
注意:
listen 80 端口值是要与 /etc/fdfs/storage.conf 中的 http.server_port=80 (前面改成80了)相对应。如果改成其它端口,则需要统一,同时在防火墙中打开该端口。
location 的配置,如果有多个group则配置location ~/group([0-9])/M00 ,没有则不用配group。
⑦ 在/home/yuqing/fastdfsfile 文件存储目录下创建软连接,将其链接到实际存放数据的目录,这一步可以省略:
ln -s /home/yuqing/fastdfs/file/data/ /home/yuqing/fastdfs/file/data/M00
⑧ 启动nginx
/usr/local/nginx/sbin/nginx
打印出如下就算配置成功:
⑨测试图片访问
在浏览器中能访问到即算成功。注意和第三点中直接使用nginx路由访问不同的是,这里配置 fastdfs-nginx-module 模块,可以重定向文件链接到源服务器取文件。
http://192.168.174.128/group1/M00/00/00/wKiugFxQeEuAXSoXAABPKl4woFc369.png
效果如图:我上传的图比较小,将就着看吧...
至此,FastDFS+NGINX搭建的文件服务器就完成了,通过亲自搭建,你对整个FastDFS的流程应该有所深入理解,其过程大致如下图所示:
当然这不是最终的服务架构,最终要架构成什么样子,还是要看你公司的服务器量和要设计的存储量级等,一个高可用的企业级FastDFS架构至少需要6个节点,其架构大致如下(盗的图):
每个模块都有2个节点,以确保一个挂了还有另外一个,最终通过Keepalived向外暴露一个虚拟的访问ip.有条件搭6节点的可以在本篇往上翻,我已经给了这篇的URL了,可以进一步深入研究.
上面已经搭好了一个"分布式"的文件系统,最终对我们这些后端码农来说,还是要回到文件的上传和下载,至于为啥选springboot就不再解释了2019年了毕竟...
Jdk:1.8
springboot:2.1.2 RELEASE
fastdfs-client-java:1.27.0.0 这里不要选用余大佬github上推荐的版本,可能是还未发布,加入pom后依赖下载不下来,坑了我好久.
贴一份我项目的pom.xml
4.0.0
org.springframework.boot
spring-boot-starter-parent
2.1.2.RELEASE
com.xpc
fdfs
0.0.1-SNAPSHOT
fdfs
Demo project for Spring Boot
1.8
org.springframework.boot
spring-boot-starter-thymeleaf
org.springframework.boot
spring-boot-starter-web
org.projectlombok
lombok
true
org.springframework.boot
spring-boot-starter-test
test
net.oschina.zcx7878
fastdfs-client-java
1.27.0.0
org.springframework.boot
spring-boot-maven-plugin
在resources目录下创建:fdfs_client.conf文件,并加入以下配置:
connect_timeout = 60
network_timeout = 60
charset = UTF-8
http.tracker_http_port = 80
http.anti_steal_token = false
tracker_server = 192.168.174.128:22122
为了简单,我把防盗链关了,如果是生产环境下,建议开启.
@Data
public class FastDFSFile {
private String name;
private byte[] content;
private String ext;//扩展名
private String md5;
private String author;
public FastDFSFile(String name, byte[] content, String ext) {
this.name = name;
this.content = content;
this.ext = ext;
}
}
@Data
public class FastDFSClient {
private static final Logger logger = LoggerFactory.getLogger(FastDFSClient.class);
private static TrackerClient trackerClient;
private static TrackerServer trackerServer;
private static StorageClient storageClient;
private static StorageServer storageServer;
//通过静态代码块初始化连接
static {
try {
String filePath = new ClassPathResource("fdfs_client.conf").getFile().getAbsolutePath();
ClientGlobal.init(filePath);
trackerClient = new TrackerClient();
trackerServer = trackerClient.getConnection();
storageServer = trackerClient.getStoreStorage(trackerServer);
} catch (Exception e) {
logger.error("FastDFS Client Init Fail!", e);
}
}
/**
* 上传
*
* @param file
* @return
*/
public static String[] upload(FastDFSFile file) {
logger.info("FileName:" + file.getName() + "FileLength:" + file.getContent().length);
Long startTime = System.currentTimeMillis();
NameValuePair[] meta_list = new NameValuePair[1];
meta_list[0] = new NameValuePair("auther", file.getAuthor());
String[] uploadResults = null;
try {
storageClient = new StorageClient(trackerServer, storageServer);
uploadResults = storageClient.upload_file(file.getContent(), file.getExt(), meta_list);
} catch (IOException e) {
logger.error("IOException when upload the file:" + file.getName(), e);
} catch (Exception e) {
logger.error("No IOException when upload the file:" + file.getName(), e);
}
logger.info("Upload spend time:" + (System.currentTimeMillis() - startTime) + " ms");
if (uploadResults == null) {
logger.error("Upload fail! errorCode:" + storageClient.getErrorCode());
}
logger.info("Upload successfully! Group name:" + uploadResults[0] + " remoteFileName:" + uploadResults[1]);
return uploadResults;
}
public static String getTrackerUrl() {
return trackerServer.getInetSocketAddress().getHostName();//获取服务器Ip:192.168.174.128 但这种种方式获取方式有点慢 耗时约1.2s,在单机下当然可以从conf里直接读取,但集群的话好像只能这样取...
}
}
public class FileUtil {
private static final Logger logger = LoggerFactory.getLogger(FileUtil.class);
public static String upload(MultipartFile multipartFile) throws IOException {
String fileAbsolutePath[] = {};
String fileName = multipartFile.getOriginalFilename();
String ext = fileName.substring(fileName.lastIndexOf(".") + 1);
byte[] buffer = null;
InputStream inputStream = null;
try {
inputStream = multipartFile.getInputStream();
if (inputStream != null) {
int len = inputStream.available();
buffer = new byte[len];
inputStream.read(buffer);
}
FastDFSFile fastDFSFile = new FastDFSFile(fileName, buffer, ext);
fileAbsolutePath = FastDFSClient.upload(fastDFSFile);
if (fileAbsolutePath == null) {
logger.error("upload file fail! please try again");
}
} catch (Exception e) {
logger.error("upload file Exception:", e);
} finally {
inputStream.close();
}
String path = FastDFSClient.getTrackerUrl() + "/" + fileAbsolutePath[0] + "/" +fileAbsolutePath[1];//获取上传后的文件的访问全路径
return path;
}
}
@Controller
@RequestMapping
public class FileUploadController {
@RequestMapping("")
public String index() {
return "index.html";
}
@RequestMapping("/upload")
@ResponseBody
public String upload(@RequestParam("file") MultipartFile file) throws Exception {
if (file.isEmpty()){
return "空的你上传个屁啊";
}
return FileUtil.upload(file);
}
}
Title
欢迎观临老汉的FastDFS
页面比较丑,讲究着看吧...
提交后会把上传后的文件访问路径返回给我:
复制后在浏览器中访问:
可以看到刚刚上传的图片已经正确显示,在服务器的对应目录下也可以找到刚刚上传的文件,没毛病.
上传速度也是飞一般的快:
如果上传的是其他文件,比如视频或者excel等都没有问题,只要不要太大,都没问题.
上面的全部代码可以去我github拉取,我已经上传过了:https://github.com/laohanjianshen/springboot-fdfs.git
如果公司有6台及以上的服务器,可以考虑用FastDFS搭建自己的文件存储系统,可以省下购买第三方服务的钱.