說起分布式文件管理系統,大家可能很容易想到 HDFS、GFS 等系統,前者是 Hadoop 的一部分,后者則是 google 提供的分布式文件管理系統。除了這些之外,國內淘寶和騰訊也有自己的分布式文件管理系統,都叫 TFS(Taobao File System 和 Tencent File System)。
相對于上面提到的這些分布式文件管理系統而言,FastDFS 可能離我們 JAVA 工程師更近一些,因為文件上傳這個功能太常見了,而想要搭建獨立的分布式文件管理系統,FastDFS+Nginx 組合無疑是最佳方案。因此,松哥今天就來和大家簡單聊一聊這個問題。
如果小伙伴們還不懂在傳統的開發環境下如何進行文件上傳,可以參考松哥之前發在gongzhonghao的文件上傳教程:
- Spring Boot + Vue,手把手教你做文件上傳
1.什么是 FastDFS
1.1 FastDFS 簡介
FastDFS 由淘寶的余慶大佬在 2008 年開源的一款輕量級分布式文件管理系統,FastDFS 用 C 語言實現,支持 linux、FreeBSD、macOS 等類 UNIX 系統。FastDFS 類似 google FS,屬于應用級文件系統,不是通用的文件系統,只能通過專有 API 訪問,目前提供了 C 和 Java SDK ,以及 php 擴展 SDK。
這款開源軟件從發布至今,歷經數十年,這款開源軟件的生命力依然旺盛,在業界依然備受推崇,當然這也得益于作者一直在不斷完善該軟件。
FastDFS 專為互聯網應用量身定做,解決大容量文件存儲問題,追求高性能和高擴展性,它可以看做是基于文件的 key/value 存儲系統,key 為文件 ID,value 為文件內容,因此稱作分布式文件存儲服務更為合適。
1.2 為什么需要 FastDFS
傳統的企業級開發對于高并發要求不是很高,而且數據量可能也不大,在這樣的環境下文件管理可能非常 Easy。
但是互聯網應用訪問量大、數據量大,在互聯網應用中,我們必須考慮解決文件大容量存儲和高性能訪問的問題,而 FastDFS 就特別適合干這件事情,常見的圖片存儲、視頻存儲、文檔存儲等等我們都可以采用 FastDFS 來做。
1.3 FastDFS 架構
作為一款分布式文件管理系統,FastDFS 主要包括四個方面的功能:
- 文件存儲
- 文件同步
- 文件上傳
- 文件下載
這個方面的功能,基本上就能搞定我們常見的文件管理需求了。
下面這是一張來自 FastDFS 官網的系統架構圖:

從上面這張圖中我們可以看到,FastDFS 架構包括 Tracker 和 Storage 兩部分,看名字大概就能知道,Tracker 用來追蹤文件,相當于是文件的一個索引,而 Storage 則用來保存文件。
我們上傳文件的文件最終保存在 Storage 上,文件的元數據信息保存在 Tracker 上,通過 Tracker 可以實現對 Storage 的負載均衡。
Storage 一般會搭建成集群,一個 Storage Cluster 可以由多個組構成,不同的組之間不進行通信,一個組又相當于一個小的集群,組由多個 Storage Server 組成,組內的 Storage Server 會通過連接進行文件同步來保證高可用。
2.FastDFS 安裝
介紹完 FastDFS 之后,相信小伙伴已經摩拳擦掌躍躍欲試了,接下來我們就來看下 FastDFS 的安裝。
我這里為了測試方便,就不開啟多臺虛擬機了,Tracker 和 Storage 我將安裝在同一臺服務器上。
圖片上傳我們一般使用 FastDFS,圖片上傳成功之后,接下來的圖片訪問我們一般采用 Nginx,所以這里的安裝我將從三個方面來介紹:
- Tracker 安裝
- Storage 安裝
- Nginx 安裝
2.1 Tracker 安裝
安裝,我們首先需要準備一個環境兩個庫以及一個安裝包。
「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
?
考慮到 GitHub 訪問較慢,松哥已經把安裝文件下載好了,放在百度網盤上,小伙伴們可以在松哥公眾號后臺回復 fastdfs 獲取下載鏈接。
?
將下載好的 libfastcommon 拷貝至 /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
?
考慮到 GitHub 訪問較慢,松哥已經把安裝文件下載好了,放在百度網盤上,小伙伴們可以在松哥公眾號后臺回復 fastdfs 獲取下載鏈接。
?
下載成功后,將下載文件拷貝到 /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 一起配置好,再來做測試。
2.3 Nginx 安裝
Nginx 可以算是 FastDFS 的重要搭檔。
Nginx 的安裝分為兩個步驟:
- 安裝 Nginx
- 首先在 Storage 下安裝 fastdfs-nginx-module
第一步簡單,松哥之前專門寫過一篇文章掃盲 Nginx,所以 Nginx 安裝大家直接參考這里:Nginx 極簡入門教程!
接下來看第二步。
首先下載 fastdfs-nginx-module,下載地址:
https://github.com/happyfish100/fastdfs-nginx-module/archive/V1.22.tar.gz
?
考慮到 GitHub 訪問較慢,松哥已經把安裝文件下載好了,放在百度網盤上,小伙伴們可以在松哥公眾號后臺回復 fastdfs 獲取下載鏈接。
?
下載完成后,將下載的文件拷貝到 /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
安裝完成后,修改 nginx 的配置文件,如下:
vi /usr/local/nginx/conf/nginx.conf

在這里配置 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 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 就可以看到上傳的圖片了。
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();
}
}
這段代碼就很好理解了,直接調用 download_file1 方法獲取到一個 byte 數組,然后通過 IO 流寫出到本地文件即可。
4.安全問題
現在,任何人都可以訪問我們服務器上傳文件,這肯定是不行的,這個問題好解決,加一個上傳時候的令牌即可。
首先我們在服務端開啟令牌校驗:
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。「此時訪問路徑里邊如果沒有令牌,會訪問失敗。」
好了,大功告成!