说起分布式文件管理系统,大家可能很容易想到 HDFS、GFS 等系统,前者是 Hadoop 的一部分,后者则是 Google 提供的分布式文件管理系统。不过对于一般的公司而言,自己去搭建分布式文件管理系统并维护,成本太高,还不如直接上云服务。
对于开发者而言,只需要通过 RESTful API 或者一些第三方客户端就可以方便的去操作对象存储,在用户下载使用图片前,可以创建图片样式模板或传入图片处理参数对图片进行处理,例如剪切、压缩。
可以自己搭建分布式文件管理系统,自己搭建的话,FastDFS+Nginx 组合无疑是最佳方案。
FastDFS 简介
FastDFS 由余庆大佬在 2008 年开源的一款轻量级分布式文件管理系统,FastDFS 用 C 语言实现,支持 Linux、FreeBSD、MacOS 等类 UNIX 系统。FastDFS 类似 google FS,属于应用级文件系统,不是通用的文件系统,只能通过专有 API 访问,目前提供了 C 和 Java SDK ,以及 PHP 扩展 SDK。
这款开源软件从发布至今,历经数十年,这款开源软件的生命力依然旺盛,在业界依然备受推崇,当然这也得益于作者一直在不断完善该软件。
FastDFS 专为互联网应用量身定做,解决大容量文件存储问题,追求高性能和高扩展性,它可以看做是基于文件的 key/value 存储系统,key 为文件 ID,value 为文件内容,因此称作分布式文件存储服务更为合适。
github地址: https://github.com/happyfish100/fastdfs
FastDFS 架构
作为一款分布式文件管理系统,FastDFS 主要包括四个方面的功能:
-
文件存储
-
文件同步
-
文件上传
-
文件下载
下面这是一张来自 FastDFS 官网的系统架构图
从上面这张图中我们可以看到,FastDFS 架构包括 Tracker 和 Storage 两部分,看名字大概就能知道,Tracker 用来追踪文件,相当于是文件的一个索引,而 Storage 则用来保存文件。
我们上传文件的文件最终保存在 Storage 上,文件的元数据信息保存在 Tracker 上,通过 Tracker 可以实现对 Storage 的负载均衡。
Storage 一般会搭建成集群,一个 Storage Cluster 可以由多个组构成,不同的组之间不进行通信,一个组又相当于一个小的集群,组由多个 Storage Server 组成,组内的 Storage Server 会通过连接进行文件同步来保证高可用。
FastDFS 安装
官方文档: https://github.com/happyfish100/fastdfs/wiki
其他文档:
https://www.cnblogs.com/chiangchou/p/fastdfs.html
https://www.jianshu.com/p/1c71ae024e5e
Java 客户端调用
1. 首先我们来创建一个普通的 Maven 工程,添加如下依赖
<dependency>
<groupId>net.oschina.zcx7878</groupId>
<artifactId>fastdfs-client-java</artifactId>
<version>1.27.0.0</version>
</dependency>
2. 在项目的 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
3. 文件上传
配置完成后,先来看文件上传,代码如下:
@Test
void testUpload() {
try {
ClientGlobal.initByProperties("fastdfs-client.properties");
TrackerClient tracker = new TrackerClient();
TrackerServer trackerServer = tracker.getConnection();
StorageServer storageServer = null;
StorageClient1 client = new StorageClient1(trackerServer, storageServer);
NameValuePair nvp[] = null;
//上传到文件系统
String fileId = client.upload_file1("C:\\Users\\javaboy\\Pictures\\picpick\\1.png", "png",
nvp);
logger.info(fileId);
} catch (Exception e) {
e.printStackTrace();
}
}
首先加载配置文件,然后构造一个 TrackerClient 对象,接着再根据这个对象获取到一个 TrackerServer,然后创建一个 StorageClient1 实例。NameValuePair 中保存的是文件的元数据信息,如果有的话,就以 key/value 的方式来设置,如果没有的话,直接给一个 null 即可。
最后,调用 client 的 upload_file1 方法上传文件,第一个参数是文件路径,第二个参数是文件的扩展名,第三个参数就是文件的元数据信息,这个方法的返回值,就是上传文件的访问路径。执行该方法,打印日志如下:
2020-02-29 17:46:03.017 INFO 6184 --- [ main] o.j.fastdfs.FastdfsApplicationTests : group1/M00/00/00/wKhbgF5aMteAWy0gAAJkI7-2yGk361.png
group1/M00/00/00/wKhbgF5aMteAWy0gAAJkI7-2yGk361.png 就是文件的路径,此时,在浏览器中输入 http://192.168.91.128/group1/M00/00/00/wKhbgF5aMteAWy0gAAJkI7-2yGk361.png 就可以看到上传的图片了。
4.文件下载
@Test
void testDownload() {
try {
ClientGlobal.initByProperties("fastdfs-client.properties");
TrackerClient tracker = new TrackerClient();
TrackerServer trackerServer = tracker.getConnection();
StorageServer storageServer = null;
StorageClient1 client = new StorageClient1(trackerServer, storageServer);
byte[] bytes = client.download_file1("group1/M00/00/00/wKhbgF5aMteAWy0gAAJkI7-2yGk361.png");
FileOutputStream fos = new FileOutputStream(new File("C:\\Users\\javaboy\\Pictures\\picpick\\666.png"));
fos.write(bytes);
fos.close();
} catch (Exception e) {
e.printStackTrace();
}
}
安全问题
现在,任何人都可以访问我们服务器上传文件,这肯定是不行的,这个问题好解决,加一个上传时候的令牌即可。
首先我们在服务端开启令牌校验:
vi /etc/fdfs/http.conf
配置完成后,记得重启服务端
./nginx -s stop
./nginx
接下来,在前端准备一个获取令牌的方法,如下:
@Test
public void getToken() throws Exception {
int ts = (int) Instant.now().getEpochSecond();
String token = ProtoCommon.getToken("M00/00/00/wKhbgF5aMteAWy0gAAJkI7-2yGk361.png", ts, "FastDFS1234567890");
StringBuilder sb = new StringBuilder();
sb.append("?token=").append(token);
sb.append("&ts=").append(ts);
System.out.println(sb.toString());
}
这里,我们主要是根据 ProtoCommon.getToken 方法来获取令牌,注意这个方法的第一个参数是你要访问的文件 id,**注意,这个地址里边不包含 group,千万别搞错了;**第二个参数是时间戳,第三个参数是密钥,密钥要和服务端的配置一致。
将生成的字符串拼接,追加到访问路径后面,如:http://192.168.91.128/group1/M00/00/00/wKhbgF5aMteAWy0gAAJkI7-2yGk361.png?token=7e329cc50307000283a3ad3592bb6d32&ts=1582975854
。
小结
安装步骤还是比较复杂的, 但是现在可以使用docker安装