1. 程式人生 > >手把手教你用 FastDFS 構建分散式檔案管理系統

手把手教你用 FastDFS 構建分散式檔案管理系統

說起分散式檔案管理系統,大家可能很容易想到 HDFS、GFS 等系統,前者是 Hadoop 的一部分,後者則是 Google 提供的分散式檔案管理系統。除了這些之外,國內淘寶和騰訊也有自己的分散式檔案管理系統,都叫 TFS(Taobao File SystemTencent File System)。

相對於上面提到的這些分散式檔案管理系統而言,FastDFS 可能離我們 Java 工程師更近一些,因為檔案上傳這個功能太常見了,而想要搭建獨立的分散式檔案管理系統,FastDFS+Nginx 組合無疑是最佳方案。因此,鬆哥今天就來和大家簡單聊一聊這個問題。

如果小夥伴們還不懂在傳統的開發環境下如何進行檔案上傳,可以參考鬆哥之前發的檔案上傳教程:

  • 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。此時訪問路徑裡邊如果沒有令牌,會訪問失敗。

好了,大功告成!下次和大家講我如何在 Spring Boot 中玩這個東西