阅读完需:约 15 分钟
说起分布式文件管理系统,大家可能很容易想到 HDFS、GFS 等系统,前者是 Hadoop 的一部分,后者则是 Google 提供的分布式文件管理系统。除了这些之外,国内淘宝和腾讯也有自己的分布式文件管理系统,都叫 TFS(Taobao File System 和 Tencent File System)。
相对于上面提到的这些分布式文件管理系统而言,FastDFS 可能离我们 Java 工程师更近一些,因为文件上传这个功能太常见了,而想要搭建独立的分布式文件管理系统,FastDFS+Nginx 组合无疑是最佳方案。
但是在学习分布式文件管理系统之前首先要了解最基本的文件下载:
1、简介
FastDFS 是一个开源的高性能分布式文件系统(DFS)。 它的主要功能包括:文件存储,文件同步和文件访问,以及高容量和负载平衡。主要解决了海量数据存储问题,特别适合以中小文件(建议范围:4KB < file_size <500MB)为载体的在线服务。 常见的图片存储、视频存储、文档存储等等我们都可以采用 FastDFS 来做。
FastDFS 由淘宝的余庆大佬在 2008 年开源的一款轻量级分布式文件管理系统,FastDFS 用 C 语言实现,支持 Linux、FreeBSD、MacOS 等类 UNIX 系统。FastDFS 类似 google FS,属于应用级文件系统,不是通用的文件系统,只能通过专有 API 访问,目前提供了 C 和 Java SDK ,以及 PHP 扩展 SDK。
FastDFS 系统有三个角色:跟踪服务器(Tracker Server)、存储服务器(Storage Server)和客户端(Client)。
Tracker server 追踪服务器
追踪服务器负责接收客户端的请求,选择合适的组合storage server ,tracker server 与 storage server之间也会用心跳机制来检测对方是否活着。
Tracker需要管理的信息也都放在内存中,并且里面所有的Tracker都是对等的(每个节点地位相等),很容易扩展
客户端访问集群的时候会随机分配一个Tracker来和客户端交互。
Storage server 储存服务器
实际存储数据,分成若干个组(group),实际traker就是管理的storage中的组,而组内机器中则存储数据,group可以隔离不同应用的数据,不同的应用的数据放在不同group里面,
- 优点:
海量的存储:主从型分布式存储,存储空间方便拓展,
fastDFS对文件内容做hash处理,避免出现重复文件
然后fastDFS结合Nginx集成, 提供网站效率
客户端Client
主要是上传下载数据的服务器,也就是我们自己的项目所部署在的服务器。每个客户端服务器都需要安装Nginx
作为一款分布式文件管理系统,FastDFS 主要包括四个方面的功能:
- 文件存储
- 文件同步
- 文件上传
- 文件下载
这个方面的功能,基本上就能搞定我们常见的文件管理需求了。
下面这是一张来自 FastDFS 官网的系统架构图:
从上面这张图中我们也可以看到,FastDFS 架构包括 Tracker 和 Storage 两部分,看名字大概就能知道,Tracker 用来追踪文件,相当于是文件的一个索引,而 Storage 则用来保存文件。
我们上传文件的文件最终保存在 Storage 上,文件的元数据信息保存在 Tracker 上,通过 Tracker 可以实现对 Storage 的负载均衡。
Storage 一般会搭建成集群,一个 Storage Cluster 可以由多个组构成,不同的组之间不进行通信,一个组又相当于一个小的集群,组由多个 Storage Server 组成,组内的 Storage Server 会通过连接进行文件同步来保证高可用。
2.FastDFS 安装
安装,我们首先需要准备一个环境两个库以及一个安装包。
1.一个环境
先来看一个环境,由于 FastDFS 采用 C 语言开发,所以在安装之前,如果没有 gcc 环境,需要先安装,安装命令如下:
yum install gcc-c++
2.两个库
再来看两个库,由于 FastDFS 依赖 libevent 库,安装命令如下:
yum -y install libevent
另一个库是 libfastcommon,这是 FastDFS 官方提供的,它包含了 FastDFS 运行所需要的一些基础库。
libfastcommon 下载地址:
https://github.com/happyfish100/libfastcommon/archive/V1.0.43.tar.gz
将下载好的 libfastcommon 拷贝至 /usr/local/ 目录下,然后依次执行如下命令:
(首先要在用户目录下创建好 /usr/local/ )
cd /usr/local
tar -zxvf V1.0.43.tar.gz
cd libfastcommon-1.0.43/
./make.sh
./make.sh install
3.一个安装包
接下来我们下载 Tracker,注意,由于 Tracker 和 Storage 是相同的安装包,所以下载一次即可(2.2 小节中不用再次下载)。
安装文件可以从 FastDFS 的 GitHub 仓库上下载,下载地址:
https://github.com/happyfish100/fastdfs/archive/V6.06.tar.gz
下载成功后,将下载文件拷贝到 /usr/local 目录下,然后依次执行如下命令安装:
cd /usr/local
tar -zxvf V6.06.tar.gz
cd fastdfs-6.06/
./make.sh
./make.sh install
安装成功后,执行如下命令,将安装目录内 conf 目录下的配置文件拷贝到 /etc/fdfs 目录下:
cd conf/
cp ./* /etc/fdfs/
4.配置
接下来进入 /etc/fdfs/ 目录下进行配置:
打开 tracker.conf 文件:
vi tracker.conf
修改如下配置:
默认端口是 22122,可以根据实际需求修改,我这里就不改了。然后下面配置一下元数据的保存目录(注意目录要存在)。
这里的元数据存储目录改的时候要确定有这个目录,默认的存储目录时作者的目录,包括下面的安装都要改
5.启动
接下来执行如下命令启动 Tracker:
/usr/bin/fdfs_trackerd /etc/fdfs/tracker.conf start
如此之后,我们的 Tracker 就算安装成功了。
2.2 Storage 安装
简单起见,这里我们搭建一个 Storage 实例即可。Storage 安装也需要 libevent 和 libfastcommon,这两个库的安装参考上文,这里我不在细说。
Storage 本身的安装,也和 Tracker 一致,执行命令也都一样,因为我这里将 Tracker 和 Storage 安装在同一台服务器上,所以不用再执行安装命令了(相当于安装 Tracker 时已经安装了 Storage 了)。
唯一要做的,就是进入到 /etc/fdfs 目录下,配置 Storage:
vi storage.conf
这里一共配置三个地方,分别是 base_path、store_path0 以及 tracker_server ,tracker_server 模板有两个地址,我们这里只有一个,配置完成后,记得注释掉另外一个不用的。
配置完成后,执行如下命令启动 Storage:
/usr/bin/fdfs_storaged /etc/fdfs/storage.conf start
这两个启动完成后,现在就可以做文件的上传了,但是一般如果是图片文件,我们还需要提供一个图片的访问功能,目前来说最佳方案当然是 Nginx 了,所以我们这里连同 Nginx 一起配置好,再来做测试。
安装到这里为止还要注意一点一定要开启22122,23000这两个端口
2.3 Nginx 安装
Nginx 可以算是 FastDFS 的重要搭档。
Nginx 的安装分为两个步骤:
- 安装 Nginx
- 首先在 Storage 下安装 fastdfs-nginx-module
关于的Nginx的安装看这里:
接下来看第二步。
首先下载 fastdfs-nginx-module,下载地址:
https://github.com/happyfish100/fastdfs-nginx-module/archive/V1.22.tar.gz
下载完成后,将下载的文件拷贝到 /usr/local 目录下。然后进入 /usr/local 目录,分别执行如下命令:
cd /usr/local
tar -zxvf V1.22.tar.gz
然后将 /usr/local/fastdfs-nginx-module-1.22/src/mod_fastdfs.conf
文件拷贝到 /etc/fdfs/
目录下,并修改该文件的内容:
vi /etc/fdfs/mod_fastdfs.conf
接下来,回到第一步下载的 nginx 安装文件的解压目录中,执行如下命令,重新配置编译安装:
./configure --add-module=/usr/local/fastdfs-nginx-module-1.22/src
make
make install
这里要注意的是第一条命令的安装,因为我们用的是用户目录的安装,所以要在
/usr/local/fastdfs-nginx-module-1.22/src 前面加上 /root 来表示路径
如果发现在 make 编译安装是出错,我们可以去修改关于 /usr/local/fastdfs-nginx-module-1.22/src 目录下的 config 文件里面的路径!
安装完成后,修改 nginx 的配置文件,如下:
vi /usr/local/nginx/conf/nginx.conf
这里的修改要注意的是要将文件下面的默认的 location ………… 给注释了。
在这里配置 nginx 请求转发。
配置完成后,启动 nginx,看到如下日志,表示 nginx 启动成功:
ngx_http_fastdfs_set pid=9908
这里启动的时候看到没有报错并且开始类似的命令就是成功了。
疑问:fastdfs-nginx-module 有啥用
看了整个安装过程之后,很多小伙伴有疑问,到头来还是 nginx 本身直接找到了图片文件目录,fastdfs-nginx-module 到底有啥用?
前面我们说过,Storage 由很多组构成,每个组又是一个小的集群,在每一个组里边,数据会进行同步,但是如果数据还没同步,这个时候就有请求发来了,该怎么办?此时fastdfs-nginx-module 会帮助我们直接从源 Storage 上获取文件。
安装成功了。
3.Java 客户端调用
安装成功后,接下来我们就用 Java 客户端来测试一下文件上传下载。
首先我们来创建一个普通的 Maven 工程,添加如下依赖:
<dependency>
<groupId>net.oschina.zcx7878</groupId>
<artifactId>fastdfs-client-java</artifactId>
<version>1.27.0.0</version>
</dependency>
然后,在项目的 resources 目录下添加 FastDFS 的配置文件 fastdfs-client.properties,内容如下:
fastdfs.connect_timeout_in_seconds = 5
fastdfs.network_timeout_in_seconds = 30
fastdfs.charset = UTF-8
fastdfs.http_anti_steal_token = false
fastdfs.http_secret_key = FastDFS1234567890
fastdfs.http_tracker_http_port = 80
fastdfs.tracker_servers = 192.168.91.128:22122
fastdfs.connection_pool.enabled = true
fastdfs.connection_pool.max_count_per_entry = 500
fastdfs.connection_pool.max_idle_time = 3600
fastdfs.connection_pool.max_wait_time_in_ms = 1000
这里的配置基本上都能见名知义,我就不挨个解释了。这里先配置下 fastdfs.tracker_servers,这是 Tracker 的地址,根据实际情况配置即可。
fastdfs.http_secret_key 配置这里先不用管它,后面我会跟大家解释。
3.1 文件上传
配置完成后,先来看文件上传,代码如下:
@Test
void contextLoads() throws IOException, MyException {
//调用ClientGlobal类的初始化方法对配置文件进行加载
ClientGlobal.initByProperties("fastdfs.properties");
TrackerClient trackerClient=new TrackerClient();//客户端追踪器
TrackerServer trackerServer=trackerClient.getConnection();//服务器追踪器
StorageServer storageServer=null;
StorageClient1 Client1 = new StorageClient1(trackerServer,storageServer);
NameValuePair Pairs[] = null;//建立一个NameValuePair数组,用于存储欲传送的参数
//要上传文件的地址
String fileId = Client1.upload_file1("C:\\Users\\Lenovo\\Desktop\\其他软件\\123.jpg", "jpg", Pairs);
System.out.println(fileId);
}
这里,首先加载配置文件,然后构造一个 TrackerClient 对象,接着再根据这个对象获取到一个 TrackerServer,然后创建一个 StorageClient1 实例。NameValuePair 中保存的是文件的元数据信息,如果有的话,就以 key/value 的方式来设置,如果没有的话,直接给一个 null 即可。
最后,调用 client 的 upload_file1 方法上传文件,第一个参数是文件路径,第二个参数是文件的扩展名,第三个参数就是文件的元数据信息,这个方法的返回值,就是上传文件的访问路径。
group1/M00/00/00/bD2EnV7LwC2APpY3AACo-AdRQKg651.jpg
就是图片在服务器上的路径
只要加上前面的地址就可以访问了:
http://108.61.132.157/ group1/M00/00/00/bD2EnV7LwC2APpY3AACo-AdRQKg651.jpg
3.2 文件下载
@Test
void download()throws IOException, MyException {
//调用ClientGlobal类的初始化方法对配置文件进行加载
ClientGlobal.initByProperties("fastdfs.properties");
TrackerClient trackerClient=new TrackerClient();//客户端追踪器
TrackerServer trackerServer=trackerClient.getConnection();//服务器追踪器
StorageServer storageServer=null;
StorageClient1 Client1 = new StorageClient1(trackerServer,storageServer);
byte[] bytes=Client1.download_file1("group1/M00/00/00/bD2EnV7GTJeAUh7gAACo-AdRQKg854.jpg");
FileOutputStream fileOutputStream = new FileOutputStream(new File("C:\\Users\\Lenovo\\Desktop\\其他软件\\123-1.jpg"));
fileOutputStream.write(bytes);
fileOutputStream.close();
}
这段代码就很好理解了,直接调用 download_file1 方法获取到一个 byte 数组,然后通过 IO 流写出到本地文件即可。
4.安全问题
现在,任何人都可以访问我们服务器上传文件,这肯定是不行的,这个问题好解决,加一个上传时候的令牌即可。
首先我们在服务端开启令牌校验:
vi /etc/fdfs/http.conf
配置完成后,记得重启服务端:
./nginx -s stop
./nginx
这里注意一下重启Nginx的命令一般都是在根目录下的 /usr/local/…… 里面
接下来,在前端准备一个获取令牌的方法,如下:
@Test
void testToken() throws UnsupportedEncodingException, NoSuchAlgorithmException, MyException {
//时间戳
int ts = (int) Instant.now().getEpochSecond();
String token = ProtoCommon.getToken("M00/00/00/bD2EnV7GpTuAA1CGAACo-AdRQKg983.jpg", ts, "FastDFS1234567890");
StringBuilder sb = new StringBuilder();
sb.append("http://www.tiga.vip");
sb.append("/group1/M00/00/00/bD2EnV7GpTuAA1CGAACo-AdRQKg983.jpg");
sb.append("?token=").append(token);
sb.append("&ts=").append(ts);
System.out.println(sb.toString());
}
这里,我们主要是根据 ProtoCommon.getToken 方法来获取令牌,注意这个方法的第一个参数是你要访问的文件 id,**注意,这个地址里边不包含 group,千万别搞错了;**第二个参数是时间戳,第三个参数是密钥,密钥要和服务端的配置一致。
将生成的字符串拼接,追加到访问路径后面,如:
http://108.61.132.157/group1/M00/00/00/bD2EnV7GpTuAA1CGAACo-AdRQKg983.jpg?token=d1d5d80e3f38741b12bb891631ee3199&ts=1590411730
此时访问路径里边如果没有令牌,会访问失败。