說起分布式文件管理系統(tǒng),大家可能很容易想到 HDFS、GFS 等系統(tǒng),前者是 Hadoop 的一部分,后者則是 google 提供的分布式文件管理系統(tǒng)。除了這些之外,國內(nèi)淘寶和騰訊也有自己的分布式文件管理系統(tǒng),都叫 TFS(Taobao File System 和 Tencent File System)。
相對于上面提到的這些分布式文件管理系統(tǒng)而言,F(xiàn)astDFS 可能離我們 JAVA 工程師更近一些,因?yàn)槲募蟼鬟@個(gè)功能太常見了,而想要搭建獨(dú)立的分布式文件管理系統(tǒng),F(xiàn)astDFS+Nginx 組合無疑是最佳方案。因此,松哥今天就來和大家簡單聊一聊這個(gè)問題。
如果小伙伴們還不懂在傳統(tǒng)的開發(fā)環(huán)境下如何進(jìn)行文件上傳,可以參考松哥之前發(fā)在gongzhonghao的文件上傳教程:
- Spring Boot + Vue,手把手教你做文件上傳
1.什么是 FastDFS
1.1 FastDFS 簡介
FastDFS 由淘寶的余慶大佬在 2008 年開源的一款輕量級分布式文件管理系統(tǒng),F(xiàn)astDFS 用 C 語言實(shí)現(xiàn),支持 linux、FreeBSD、macOS 等類 UNIX 系統(tǒng)。FastDFS 類似 google FS,屬于應(yīng)用級文件系統(tǒng),不是通用的文件系統(tǒng),只能通過專有 API 訪問,目前提供了 C 和 Java SDK ,以及 php 擴(kuò)展 SDK。
這款開源軟件從發(fā)布至今,歷經(jīng)數(shù)十年,這款開源軟件的生命力依然旺盛,在業(yè)界依然備受推崇,當(dāng)然這也得益于作者一直在不斷完善該軟件。
FastDFS 專為互聯(lián)網(wǎng)應(yīng)用量身定做,解決大容量文件存儲問題,追求高性能和高擴(kuò)展性,它可以看做是基于文件的 key/value 存儲系統(tǒng),key 為文件 ID,value 為文件內(nèi)容,因此稱作分布式文件存儲服務(wù)更為合適。
1.2 為什么需要 FastDFS
傳統(tǒng)的企業(yè)級開發(fā)對于高并發(fā)要求不是很高,而且數(shù)據(jù)量可能也不大,在這樣的環(huán)境下文件管理可能非常 Easy。
但是互聯(lián)網(wǎng)應(yīng)用訪問量大、數(shù)據(jù)量大,在互聯(lián)網(wǎng)應(yīng)用中,我們必須考慮解決文件大容量存儲和高性能訪問的問題,而 FastDFS 就特別適合干這件事情,常見的圖片存儲、視頻存儲、文檔存儲等等我們都可以采用 FastDFS 來做。
1.3 FastDFS 架構(gòu)
作為一款分布式文件管理系統(tǒng),F(xiàn)astDFS 主要包括四個(gè)方面的功能:
- 文件存儲
- 文件同步
- 文件上傳
- 文件下載
這個(gè)方面的功能,基本上就能搞定我們常見的文件管理需求了。
下面這是一張來自 FastDFS 官網(wǎng)的系統(tǒng)架構(gòu)圖:
從上面這張圖中我們可以看到,F(xiàn)astDFS 架構(gòu)包括 Tracker 和 Storage 兩部分,看名字大概就能知道,Tracker 用來追蹤文件,相當(dāng)于是文件的一個(gè)索引,而 Storage 則用來保存文件。
我們上傳文件的文件最終保存在 Storage 上,文件的元數(shù)據(jù)信息保存在 Tracker 上,通過 Tracker 可以實(shí)現(xiàn)對 Storage 的負(fù)載均衡。
Storage 一般會搭建成集群,一個(gè) Storage Cluster 可以由多個(gè)組構(gòu)成,不同的組之間不進(jìn)行通信,一個(gè)組又相當(dāng)于一個(gè)小的集群,組由多個(gè) Storage Server 組成,組內(nèi)的 Storage Server 會通過連接進(jìn)行文件同步來保證高可用。
2.FastDFS 安裝
介紹完 FastDFS 之后,相信小伙伴已經(jīng)摩拳擦掌躍躍欲試了,接下來我們就來看下 FastDFS 的安裝。
我這里為了測試方便,就不開啟多臺虛擬機(jī)了,Tracker 和 Storage 我將安裝在同一臺服務(wù)器上。
圖片上傳我們一般使用 FastDFS,圖片上傳成功之后,接下來的圖片訪問我們一般采用 Nginx,所以這里的安裝我將從三個(gè)方面來介紹:
- Tracker 安裝
- Storage 安裝
- Nginx 安裝
2.1 Tracker 安裝
安裝,我們首先需要準(zhǔn)備一個(gè)環(huán)境兩個(gè)庫以及一個(gè)安裝包。
「1.一個(gè)環(huán)境」
先來看一個(gè)環(huán)境,由于 FastDFS 采用 C 語言開發(fā),所以在安裝之前,如果沒有 gcc 環(huán)境,需要先安裝,安裝命令如下:
yum install gcc-c++
「2.兩個(gè)庫」
再來看兩個(gè)庫,由于 FastDFS 依賴 libevent 庫,安裝命令如下:
yum -y install libevent
另一個(gè)庫是 libfastcommon,這是 FastDFS 官方提供的,它包含了 FastDFS 運(yùn)行所需要的一些基礎(chǔ)庫。
libfastcommon 下載地址:
https://github.com/hAppyfish100/libfastcommon/archive/V1.0.43.tar.gz
?
考慮到 GitHub 訪問較慢,松哥已經(jīng)把安裝文件下載好了,放在百度網(wǎng)盤上,小伙伴們可以在松哥公眾號后臺回復(fù) fastdfs 獲取下載鏈接。
?
將下載好的 libfastcommon 拷貝至 /usr/local/ 目錄下,然后依次執(zhí)行如下命令:
cd /usr/local
tar -zxvf V1.0.43.tar.gz
cd libfastcommon-1.0.43/
./make.sh
./make.sh install
「3.一個(gè)安裝包」
接下來我們下載 Tracker,注意,由于 Tracker 和 Storage 是相同的安裝包,所以下載一次即可(2.2 小節(jié)中不用再次下載)。
安裝文件可以從 FastDFS 的 GitHub 倉庫上下載,下載地址:
https://github.com/happyfish100/fastdfs/archive/V6.06.tar.gz
?
考慮到 GitHub 訪問較慢,松哥已經(jīng)把安裝文件下載好了,放在百度網(wǎng)盤上,小伙伴們可以在松哥公眾號后臺回復(fù) fastdfs 獲取下載鏈接。
?
下載成功后,將下載文件拷貝到 /usr/local 目錄下,然后依次執(zhí)行如下命令安裝:
cd /usr/local
tar -zxvf V6.06.tar.gz
cd fastdfs-6.06/
./make.sh
./make.sh install
安裝成功后,執(zhí)行如下命令,將安裝目錄內(nèi) conf 目錄下的配置文件拷貝到 /etc/fdfs 目錄下:
cd conf/
cp ./* /etc/fdfs/
「4.配置」
接下來進(jìn)入 /etc/fdfs/ 目錄下進(jìn)行配置:
打開 tracker.conf 文件:
vi tracker.conf
修改如下配置:
默認(rèn)端口是 22122,可以根據(jù)實(shí)際需求修改,我這里就不改了。然后下面配置一下元數(shù)據(jù)的保存目錄(注意目錄要存在)。
「5.啟動(dòng)」
接下來執(zhí)行如下命令啟動(dòng) Tracker:
/usr/bin/fdfs_trackerd /etc/fdfs/tracker.conf start
如此之后,我們的 Tracker 就算安裝成功了。
2.2 Storage 安裝
簡單起見,這里我們搭建一個(gè) Storage 實(shí)例即可。Storage 安裝也需要 libevent 和 libfastcommon,這兩個(gè)庫的安裝參考上文,這里我不在細(xì)說。
Storage 本身的安裝,也和 Tracker 一致,執(zhí)行命令也都一樣,因?yàn)槲疫@里將 Tracker 和 Storage 安裝在同一臺服務(wù)器上,所以不用再執(zhí)行安裝命令了(相當(dāng)于安裝 Tracker 時(shí)已經(jīng)安裝了 Storage 了)。
唯一要做的,就是進(jìn)入到 /etc/fdfs 目錄下,配置 Storage:
vi storage.conf
這里一共配置三個(gè)地方,分別是 base_path、store_path0 以及 tracker_server ,tracker_server 模板有兩個(gè)地址,我們這里只有一個(gè),配置完成后,記得注釋掉另外一個(gè)不用的。
配置完成后,執(zhí)行如下命令啟動(dòng) Storage:
/usr/bin/fdfs_storaged /etc/fdfs/storage.conf start
這兩個(gè)啟動(dòng)完成后,現(xiàn)在就可以做文件的上傳了,但是一般如果是圖片文件,我們還需要提供一個(gè)圖片的訪問功能,目前來說最佳方案當(dāng)然是 Nginx 了,所以我們這里連同 Nginx 一起配置好,再來做測試。
2.3 Nginx 安裝
Nginx 可以算是 FastDFS 的重要搭檔。
Nginx 的安裝分為兩個(gè)步驟:
- 安裝 Nginx
- 首先在 Storage 下安裝 fastdfs-nginx-module
第一步簡單,松哥之前專門寫過一篇文章掃盲 Nginx,所以 Nginx 安裝大家直接參考這里:Nginx 極簡入門教程!
接下來看第二步。
首先下載 fastdfs-nginx-module,下載地址:
https://github.com/happyfish100/fastdfs-nginx-module/archive/V1.22.tar.gz
?
考慮到 GitHub 訪問較慢,松哥已經(jīng)把安裝文件下載好了,放在百度網(wǎng)盤上,小伙伴們可以在松哥公眾號后臺回復(fù) fastdfs 獲取下載鏈接。
?
下載完成后,將下載的文件拷貝到 /usr/local 目錄下。然后進(jìn)入 /usr/local 目錄,分別執(zhí)行如下命令:
cd /usr/local
tar -zxvf V1.22.tar.gz
然后將
/usr/local/fastdfs-nginx-module-1.22/src/mod_fastdfs.conf 文件拷貝到 /etc/fdfs/ 目錄下,并修改該文件的內(nèi)容:
vi /etc/fdfs/mod_fastdfs.conf
接下來,回到第一步下載的 nginx 安裝文件的解壓目錄中,執(zhí)行如下命令,重新配置編譯安裝:
./configure --add-module=/usr/local/fastdfs-nginx-module-1.22/src
make
make install
安裝完成后,修改 nginx 的配置文件,如下:
vi /usr/local/nginx/conf/nginx.conf
在這里配置 nginx 請求轉(zhuǎn)發(fā)。
配置完成后,啟動(dòng) nginx,看到如下日志,表示 nginx 啟動(dòng)成功:
ngx_http_fastdfs_set pid=9908
「疑問:fastdfs-nginx-module 有啥用」
看了整個(gè)安裝過程之后,很多小伙伴有疑問,到頭來還是 nginx 本身直接找到了圖片文件目錄,fastdfs-nginx-module 到底有啥用?
前面我們說過,Storage 由很多組構(gòu)成,每個(gè)組又是一個(gè)小的集群,在每一個(gè)組里邊,數(shù)據(jù)會進(jìn)行同步,但是如果數(shù)據(jù)還沒同步,這個(gè)時(shí)候就有請求發(fā)來了,該怎么辦?此時(shí)fastdfs-nginx-module 會幫助我們直接從源 Storage 上獲取文件。
安裝成功了。
3.Java 客戶端調(diào)用
安裝成功后,接下來我們就用 Java 客戶端來測試一下文件上傳下載。
首先我們來創(chuàng)建一個(gè)普通的 Maven 工程,添加如下依賴:
<dependency>
<groupId>net.oschina.zcx7878</groupId>
<artifactId>fastdfs-client-java</artifactId>
<version>1.27.0.0</version>
</dependency>
然后,在項(xiàng)目的 resources 目錄下添加 FastDFS 的配置文件 fastdfs-client.properties,內(nèi)容如下:
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
這里的配置基本上都能見名知義,我就不挨個(gè)解釋了。這里先配置下 fastdfs.tracker_servers,這是 Tracker 的地址,根據(jù)實(shí)際情況配置即可。
fastdfs.http_secret_key 配置這里先不用管它,后面我會跟大家解釋。
3.1 文件上傳
配置完成后,先來看文件上傳,代碼如下:
@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;
//上傳到文件系統(tǒng)
String fileId = client.upload_file1("C:\Users\javaboy\Pictures\picpick\1.png", "png",
nvp);
logger.info(fileId);
} catch (Exception e) {
e.printStackTrace();
}
}
這里,首先加載配置文件,然后構(gòu)造一個(gè) TrackerClient 對象,接著再根據(jù)這個(gè)對象獲取到一個(gè) TrackerServer,然后創(chuàng)建一個(gè) StorageClient1 實(shí)例。NameValuePair 中保存的是文件的元數(shù)據(jù)信息,如果有的話,就以 key/value 的方式來設(shè)置,如果沒有的話,直接給一個(gè) null 即可。
最后,調(diào)用 client 的 upload_file1 方法上傳文件,第一個(gè)參數(shù)是文件路徑,第二個(gè)參數(shù)是文件的擴(kuò)展名,第三個(gè)參數(shù)就是文件的元數(shù)據(jù)信息,這個(gè)方法的返回值,就是上傳文件的訪問路徑。執(zhí)行該方法,打印日志如下:
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 就是文件的路徑,此時(shí),在瀏覽器中輸入
http://192.168.91.128/group1/M00/00/00/wKhbgF5aMteAWy0gAAJkI7-2yGk361.png 就可以看到上傳的圖片了。
3.2 文件下載
@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();
}
}
這段代碼就很好理解了,直接調(diào)用 download_file1 方法獲取到一個(gè) byte 數(shù)組,然后通過 IO 流寫出到本地文件即可。
4.安全問題
現(xiàn)在,任何人都可以訪問我們服務(wù)器上傳文件,這肯定是不行的,這個(gè)問題好解決,加一個(gè)上傳時(shí)候的令牌即可。
首先我們在服務(wù)端開啟令牌校驗(yàn):
vi /etc/fdfs/http.conf
配置完成后,記得重啟服務(wù)端:
./nginx -s stop
./nginx
接下來,在前端準(zhǔn)備一個(gè)獲取令牌的方法,如下:
@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());
}
這里,我們主要是根據(jù) ProtoCommon.getToken 方法來獲取令牌,注意這個(gè)方法的第一個(gè)參數(shù)是你要訪問的文件 id,**注意,這個(gè)地址里邊不包含 group,千萬別搞錯(cuò)了;**第二個(gè)參數(shù)是時(shí)間戳,第三個(gè)參數(shù)是密鑰,密鑰要和服務(wù)端的配置一致。
將生成的字符串拼接,追加到訪問路徑后面,如:
http://192.168.91.128/group1/M00/00/00/wKhbgF5aMteAWy0gAAJkI7-2yGk361.png?token=
7e329cc50307000283a3ad3592bb6d32&ts=1582975854。「此時(shí)訪問路徑里邊如果沒有令牌,會訪問失敗。」
好了,大功告成!