在这一章,我们实现单机版的FastDFS图片服务器。
一、前言
一般而言,当用户量较少的时候,我们就把图片之类的静态资源文件存储到一台装有 Tomcat 的服务器上。但是,随着用户访问量的增大,一个Tomcat服务就不能满足业务需求了,下面提出几种解决方案:
1. 部署Tomcat集群,使用 Nginx作为负载均衡服务器,分散存储到不同的 Tomcat 服务器上
这种做法可以简单的扩大存储,但是需要处理一个问题:分散存储后,需要正确的处理逻辑来访问到正确的Tomcat 分支上。例如:存储的时候,由Nginx 分配到 Tomcat1 上;访问的时候,由Nginx 分配到 Tomcat2 上。
2. 搭建一个唯一的图片服务器,即tomcat本身不保存图片,而将图片上传到图片服务器上
这种方式采用http的方式来访问图片,需要使用http服务器。可以使用Tomcat ,但是Tomcat的强项在于处理动态请求,这里使用Nginx 服务器比较合适。
注意:在上述方案一中,我们也使用了 Nginx,但是“负载均衡”只是 Nginx的功能之一。
3. 搭建图片服务器集群,即分布式文件系统
这种方式用于解决进一步的用户量增长问题,是方案二的升级版。分布式系统可以解决图片、音视频、文件共享的问题。目前,流行的分布式文件系统有FastDFS、GFS(谷歌)、HDFS(Hadoop)、TFS(淘宝),我们采用FastDFS 来进行介绍和部署。
二、FastDFS 介绍
2.1 简介
FastDFS 是由淘宝的余庆先生开发,是一个轻量级、高性能的开源分布式文件系统,用纯C语言开发,包括文件存储、文件同步、文件访问(上传、下载)、存取负载均衡、在线扩容、相同内容只存储一份等功能,适合有大容量存储需求的应用或系统。
FastDFS 按组区分存储资源,当存储空间不足时,可以通过增加水平分组来达到扩容的目的。
FastDFS 允许少数Nginx发生故障,是一个满足高可用的分布式文件系统。
2.2角色介绍和操作流程
角色介绍:
FastDFS 两个主要的角色:TrackerServer 和 Storage Server;
Tracker Server:跟踪服务器,主要负责调度storage节点与client通信,在访问上起负载均衡的作用,和记录storage节点的运行状态,是连接client和storage节点的枢纽;
Storage Server:存储服务器,保存文件和文件的meta data(元数据);
Group:文件组,也称为卷。做集群时一组会有多台服务器,同组服务器上的文件是完全相同的,当文件上传到组内的一台机器后,该文件会同步到同组内的其它所有机器上,起到备份的作用;
meta data:文件相关属性,键值对(KeyValue Pair)方式,如:width=1024, height=768;
FastDFS文件上传流程:
1、client询问tracker上传到的storage,不需要附加参数;
2、tracker返回一台可用的storage;
3、client直接和storage通讯完成文件上传。
FastDFS文件下载流程:
1、client询问tracker下载文件的storage,参数为文件标识(组名和文件名);
2、tracker返回一台可用的storage;
3、client直接和storage通讯完成文件下载。
2.3 安装准备
跟踪服务器(TrackerServer)和存储服务器(Storage Server):192.168.232.100
操作系统:CentOS7(最小化安装即可)
安装包:
fastdfs-master.zip:FastDFS源码
libfastcommon-master.zip:(从 FastDFS 和 FastDHT 中提取出来的公共 C 函数库)
fastdfs-nginx-module-master.zip:storage节点http服务nginx模块
nginx-1.12.1.tar.gz:Nginx安装包
FastDFS相关的安装包可到https://github.com/happyfish100去下载,Nginx需要到官网下载,下载完成后,将压缩包上传到 “/usr/local”目录下。
三、单机版安装
3.1 安装 FastDFS安装包
3.1.1安装所需的依赖包
shell> yum install make cmake gcc gcc-c++ perl zip unzip
3.1.2 安装 libfastcommon
shell> cd /usr/local
shell> unzip libfastcommon-master.zip
shell> cd libfastcommon-master
## 编译、安装
shell> ./make.sh
shell> ./make.sh install
3.1.3 安装 FastDFS
shell> cd /usr/local
shell> unzip fastdfs-master.zip
shell> cd fastdfs-master
## 编译、安装
shell> ./make.sh
shell> ./make.sh install
3.1.4 启动服务
shell> /etc/init.d/fdfs_storaged
shell> /etc/init.d/fdfs_trackerd
3.1.5 配置文件和命令工具目录
#配置文件(/etc/fdfs/目录)
[root@localhost bin]# ll /etc/fdfs/
total 84
-rw-r--r--. 1 root root 1459 Feb 21 18:12 client.conf
-rw-r--r--. 1 root root 1461 Jan 13 14:50 client.conf.sample
-rw-r--r--. 1 root root 955 Jan 13 15:17 http.conf
-rw-r--r--. 1 root root 31172 Jan 13 15:17 mime.types
-rw-r--r--. 1 root root 3729 Feb 21 18:12 mod_fastdfs.conf
-rw-r--r--. 1 root root 7919 Feb 21 18:11 storage.conf
-rw-r--r--. 1 root root 7927 Jan 13 14:50 storage.conf.sample
-rw-r--r--. 1 root root 105 Jan 13 14:50 storage_ids.conf.sample
-rw-r--r--. 1 root root 7385 Jan 13 14:53 tracker.conf
-rw-r--r--. 1 root root 7389 Jan 13 14:50 tracker.conf.sample
#命令行工具(/usr/bin目录)
shell> ll /usr/bin/ | grep fdfs
-rwxr-xr-x. 1 root root 317560 Jan 13 14:50 fdfs_appender_test
-rwxr-xr-x. 1 root root 317336 Jan 13 14:50 fdfs_appender_test1
-rwxr-xr-x. 1 root root 304184 Jan 13 14:50 fdfs_append_file
-rwxr-xr-x. 1 root root 303920 Jan 13 14:50 fdfs_crc32
-rwxr-xr-x. 1 root root 304248 Jan 13 14:50 fdfs_delete_file
-rwxr-xr-x. 1 root root 304976 Jan 13 14:50 fdfs_download_file
-rwxr-xr-x. 1 root root 304576 Jan 13 14:50 fdfs_file_info
-rwxr-xr-x. 1 root root 322488 Jan 13 14:50 fdfs_monitor
-rwxr-xr-x. 1 root root 1111888 Jan 13 14:50 fdfs_storaged
-rwxr-xr-x. 1 root root 327504 Jan 13 14:50 fdfs_test
-rwxr-xr-x. 1 root root 326712 Jan 13 14:50 fdfs_test1
-rwxr-xr-x. 1 root root 454096 Jan 13 14:50 fdfs_trackerd
-rwxr-xr-x. 1 root root 305168 Jan 13 14:50 fdfs_upload_appender
-rwxr-xr-x. 1 root root 306200 Jan 13 14:50 fdfs_upload_file
3.2 Tracker服务器配置
3.2.1 复制样例配置文件
shell> cp /etc/fdfs/tracker.conf.sample /etc/fdfs/tracker.conf
3.2.2 修改配置文件参数
shell> vi /etc/fdfs/tracker.conf
# 修改的内容如下:
disabled=false # 启用配置文件
port=22122 # tracker服务器端口(默认22122)
base_path=/fastdfs/tracker # 存储日志和数据的根目录
其它参数保留默认配置
3.2.3 创建 base_path 指定的目录
shell> mkdir -p /fastdfs/tracker
3.2.4开启端口(默认为 22122)
使用iptables:
shell> vi /etc/sysconfig/iptables
#添加如下端口行:
-A INPUT -m state --state NEW -m tcp -p tcp --dport 22122 -j ACCEPT
#重启防火墙:
shell> service iptables restart
使用firewall:
shell> firewall-cmd --add-port=22122/tcp --permanent #添加端口
shell> firewall-cmd --reload #重新加载
shell> firewall-cmd --list-port #查看端口
#如果想关闭某个端口,可以使用下面的指令
shell> firewall-cmd --remove-port=22122/tcp --permanent
3.2.5 启动 tracker 服务器
shell> /etc/init.d/fdfs_trackerd start #初次启动,会在/fastdfs/tracker目录下生成logs、data两个目录。
shell> ps -ef|grep fdfs_trackerd #检查FastDFS Tracker Server是否启动成功
如果想停止 tracker服务器,可以使用下面的指令
shell> /etc/init.d/fdfs_trackerd stop
3.2.6 设置tracker服务开机启动
shell> chkconfig fdfs_trackerd on
3.3 配置storage服务器
3.3.1 复制样例配置文件
shell> cp /etc/fdfs/storage.conf.sample /etc/fdfs/storage.conf
3.3.2 编辑文件
shell> vi /etc/fdfs/storage.conf
# 修改的内容如下:
disabled=false # 启用配置文件
port=23000 # storage服务端口
base_path=/fastdfs/storage # 数据和日志文件存储根目录
store_path0=/fastdfs/storage # 第一个存储目录
tracker_server=192.168.232.100:22122 # tracker服务器IP和端口
http.server_port=8888 # http访问文件的端口
其它参数保留默认配置。
3.3.3 创建基础数据目录
shell> mkdir -p /fastdfs/storage
3.3.4开启端口(默认为 23000)
firewall:
shell> firewall-cmd --add-port=23000/tcp --permanent #添加端口
shell> firewall-cmd --reload #重新加载
shell> firewall-cmd --list-port #查看端口
3.3.5 启动 storage 服务器
shell> /etc/init.d/fdfs_storaged start
初次启动,会在/fastdfs/storage目录下生成logs、data两个目录。
检查FastDFSTracker Server是否启动成功:
shell> ps -ef | grep fdfs_storaged
停止 storage 服务器的命令
shell> /etc/init.d/fdfs_storaged stop
3.3.6 设置 storage 服务开机启动
shell> chkconfig fdfs_storaged on
3.4上传测试
3.4.1复制样例配置文件并编辑文件
shell> cp /etc/fdfs/client.conf.sample /etc/fdfs/client.conf
shell> vi /etc/fdfs/client.conf
# 修改以下配置,其它保持默认
base_path=/fastdfs/tracker
tracker_server=192.168.232.100:22122
3.4.2 执行文件上传命令
shell> /usr/bin/fdfs_upload_file /etc/fdfs/client.conf /home/w/test.jpg
返回类似的文件ID号:group1/M00/00/00/wKjodVqNUXeAE452AAE9rfD8DeY539.jpg。
3.5 安装 fastdfs-nginx-module
3.5.1 fastdfs-nginx-module 介绍
上传文件时,同组存储服务器之间需要复制文件,这就会有同步延迟。在文件还没有复制完成的情况下,客户端如果访问同组其他的服务器,就会出现访问错误。fastdfs-nginx-module 可以重定向文件连接到源服务器(上传的服务器)取文件,避免复制延迟导致的访问错误。
3.5.2 解压 fastdfs-nginx-module-master.zip
shell> cd /usr/local
shell> unzip fastdfs-nginx-module-master.zip
3.5.3 安装Nginx
## 安装 Nginx 所需的依赖包
shell> yum install gcc gcc-c++ make automake autoconf libtool pcre pcre-devel zlib zlib-devel openssl openssl-devel
## 编译安装 Nginx(添加 fastdfs-nginx-module 模块)
shell> cd /usr/local
shell> tar -zxvf nginx-1.12.1.tar.gz
shell> unzip fastdfs-nginx-module-master.zip
shell> cd nginx-1.12.1
shell> ./configure --prefix=/opt/nginx --sbin-path=/usr/bin/nginx --add-module=/usr/local/fastdfs-nginx-module-master/src
shell> make && make install
3.5.4 复制并编辑fastdfs-nginx-module配置文件
shell> cp /usr/local/fastdfs-nginx-module-master/src/mod_fastdfs.conf /etc/fdfs/
shell> vi /etc/fdfs/mod_fastdfs.conf
#修改以下配置:
connect_timeout=10 # 客户端访问文件连接超时时长(单位:秒)
base_path=/tmp # 临时目录
tracker_server=192.168.232.100:22122 # tracker服务IP和端口
storage_server_port=23000 # storage服务端口
group_name=group1 # 组名
url_have_group_name=true # 访问链接前缀加上组名
store_path0=/fastdfs/storage # 文件存储路径
其它配置保持默认。
3.5.5 复制 FastDFS 配置文件到/etc/fdfs 目录
shell> cd /usr/local/fastdfs-master/conf
shell> cp http.conf mime.types /etc/fdfs/
3.5.6创建软连接
shell> ln -s /fastdfs/storage/data/ /fastdfs/storage/data/M00
3.5.7 配置nginx访问storage文件
#简洁版nginx配置:
vi /opt/nginx/conf/nginx.conf
user nobody;
worker_processes 1;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
server {
listen 8888;
server_name localhost;
location ~/group([0-9])/M00 {
ngx_fastdfs_module;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}
}
注意1:8888 端口值与/etc/fdfs/storage.conf中的http.server_port=8888 相对应。
注意2:Storage 对应有多个 group 的情况下,访问路径带group 名,如/group1/M00/00/00/xxx,对应的 Nginx 配置为:
location~/group([0-9])/M00 {
ngx_fastdfs_module;
}
注意3:若发现频繁报 404,可将nginx.conf 第一行 user nobody; 修改为 user root; 后重新启动。
3.5.8 开启8888 端口
shell> firewall-cmd --add-port=8888/tcp --permanent #添加端口
shell> firewall-cmd --reload #重新加载
shell> firewall-cmd --list-port #查看端口
3.5.9 启动Nginx
#启动命令如下,会显示类似 "ngx_http_fastdfs_set pid=xxx"的提示信息
shell> /usr/bin/nginx
#nginx重启命令如下:
shell> /usr/bin/nginx -s reload
3.5.10访问上传的文件
测试上传后返回的文件ID为:group1/M00/00/00/wKjodVqNUXeAE452AAE9rfD8DeY539.jpg
浏览访问的地址为:http://192.168.232.111:8888/group1/M00/00/00/wKjodVqNUXeAE452AAE9rfD8DeY539.jpg
注意:不要使用 kill命令,否则可能会导致数据丢失。
3.6 使用FastDFS-Client客户端测试上传和下载
3.6.1 打包fastdfs-client 到本地仓库
访问Github官网下载fastdfs-client源码并转化为maven工程,然后打包到本地maven仓库。这里需要在maven项目中加入坐标,如下所示。
<dependency>
<groupId>org.csource</groupId>
<artifactId>fastdfs-client-java</artifactId>
<version>1.27-SNAPSHOT</version>
</dependency>
3.6.2 新建配置文件
在工程的resources目录下新建一个resource文件夹,如下图所示。
在文件夹得下面创建client.conf文件,client.conf文件中输入tracker所在的设备的IP及端口,只保留红框中的即可,如下图所示。
注意1:注释加在上方,否则会报错。
注意2:tracker 服务器可以有多个,上图的配置文件配置了集群的情况,使用的时候取消注释即可。
3.6.3 新建测试类
下面新建一个测试类来进行测试,在src/test/java目录下新建一个包com.xxx.fastdfs,在该包下新建一个测试类TestFastDFS.java,如下所示。
package com.xxx.fastdfs;
import org.csource.common.NameValuePair;
import org.csource.fastdfs.ClientGlobal;
import org.csource.fastdfs.StorageClient;
import org.csource.fastdfs.StorageServer;
import org.csource.fastdfs.TrackerClient;
import org.csource.fastdfs.TrackerServer;
import org.junit.Test;
import com.xxx.utils.FastDFSClient;
public class TestFastDFS {
@Test
public void testUploadFile() throws Exception{
//1.向工程添加jar包
//2.创建一个配置文件,配置tracker服务器地址
//3.加载配置文件
ClientGlobal.init("D:/Work/taotao2/taotao-manager-web/src/main/resources/resource/client.conf");
//4.创建一个TrackerClient对象
TrackerClient trackerClient = new TrackerClient();
//5.使用TrackerClient对象获得trackerserver对象。
TrackerServer trackerServer = trackerClient.getConnection();
//6.创建一个StorageServer的引用,我们用null就可以
StorageServer storageServer = null;
//7.创建一个StorageClient对象。trackerserver、StorageServer两个参数
StorageClient storageClient = new StorageClient(trackerServer, storageServer);
//8.使用StorageClient对象上传文件
NameValuePair[] metaList = new NameValuePair[3];
metaList[0] = new NameValuePair("fileName", "2");
metaList[1] = new NameValuePair("createTime", "2017-04-13 16:01:00");
metaList[2] = new NameValuePair("createUser", "zhangsan");
String[] upload_files = storageClient.upload_file("D:/test.jpg", "jpg", metaList);
for(String file : upload_files){
System.out.println(file);
}
}
}
注意1:在工程中添加junit的依赖(版本已在父工程中定义)
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</dependency>
注意2:NameValuePair是给图片添加附加信息的,包括图片原文件名、大小、作者等等,导包时会发现有多个选项,我们选择"import org.csource.common.NameValuePair"
注意3:如果在复制粘贴系统的本地文件绝对路径(D:/test.jpg)时,Eclipse识别不了,则手动输入。
3.6.4 执行测试方法
控制台会返回两行数据:第一行是组号;第二行是存放的具体位置。
带有附加信息的图片上传后,会生成一个以"-m"结尾的文件,如下图所示。
3.6.5 通过浏览器访问图片
访问拼接后的地址192.168.232.100:8888/group1/M00/00/00/wKjoZFqNlgCAQ4enAAVOJlr_l7Q570.jpg,端口8888是在Nginx中配置的对外暴露的访问端口,如下图所示。
3.6.6 封装工具类及测试
为了方便使用,下面对类进行封装,内容如下。封装类中使用的Storage客户端是StorageClient1而不是StorageClient,这个客户端能够自动把文件所在的组以及存放位置拼接到一块。
package com.xxx.utils;
import org.csource.common.NameValuePair;
import org.csource.fastdfs.ClientGlobal;
import org.csource.fastdfs.StorageClient1;
import org.csource.fastdfs.StorageServer;
import org.csource.fastdfs.TrackerClient;
import org.csource.fastdfs.TrackerServer;
public class FastDFSClient {
private TrackerClient trackerClient = null;
private TrackerServer trackerServer = null;
private StorageServer storageServer = null;
private StorageClient1 storageClient = null;
public FastDFSClient(String conf) throws Exception {
// 将相对路径"classpath:applications.properties"转为绝对路径"E:/workspace/src/main/resources"
if (conf.contains("classpath:")) {
conf = conf.replace("classpath:", this.getClass().getResource("/").getPath());
}
ClientGlobal.init(conf);
trackerClient = new TrackerClient();
trackerServer = trackerClient.getConnection();
storageServer = null;
// StorageClient1能够自动把文件所在的组以及存放位置拼接到一块
storageClient = new StorageClient1(trackerServer, storageServer);
}
/**
* 上传文件方法
* <p>
* Title: uploadFile
* </p>
* <p>
* Description:
* </p>
*
* @param fileName
* 文件全路径
* @param extName
* 文件扩展名,不包含(.)
* @param metas
* 文件扩展信息
* @return
* @throws Exception
*/
public String uploadFile(String fileName, String extName, NameValuePair[] metas) throws Exception {
String result = storageClient.upload_file1(fileName, extName, metas);
return result;
}
public String uploadFile(String fileName) throws Exception {
return uploadFile(fileName, null, null);
}
public String uploadFile(String fileName, String extName) throws Exception {
return uploadFile(fileName, extName, null);
}
/**
* 上传文件方法
* <p>
* Title: uploadFile
* </p>
* <p>
* Description:
* </p>
*
* @param fileContent
* 文件的内容,字节数组
* @param extName
* 文件扩展名
* @param metas
* 文件扩展信息
* @return
* @throws Exception
*/
public String uploadFile(byte[] fileContent, String extName, NameValuePair[] metas) throws Exception {
String result = storageClient.upload_file1(fileContent, extName, metas);
return result;
}
public String uploadFile(byte[] fileContent) throws Exception {
return uploadFile(fileContent, null, null);
}
public String uploadFile(byte[] fileContent, String extName) throws Exception {
return uploadFile(fileContent, extName, null);
}
}
新建一个工具包com.xxx.utils,然后把封装类FastDFSClient.java放到下面。
下面进行测试,在TestFastDFS类中新建测试方法testFastDFSClient,代码如下:
@Test
public void testFastDFSClient() throws Exception {
FastDFSClient fastDFSClient = new FastDFSClient(
"D:/Work/taotao2/taotao-manager-web/src/main/resources/resource/client.conf");
String imgPath = fastDFSClient.uploadFile("D:/test.jpg");
System.out.println(imgPath);
}
运行testFastDFSClient方法,返回的结果如下图所示。
类似的也可以通过浏览器访问这个图片。