分佈式文件存儲:FastDFS簡單使用與原理分析


引言

FastDFS 屬於分佈式存儲範疇,分佈式文件系統 FastDFS 很是適合中小型項目,在我接手維護公司圖片服務的時候開始接觸到它,本篇文章目的是總結一下 FastDFS 的知識點。java

用了 2 臺 2 核 4G 的阿里雲服務器作集羣部署,具體部署步驟請參考:https://github.com/happyfish100/fastdfs/wikigit

一、FastDFS 分佈式文件系統概述

FastDFS 是一個輕量級的開源分佈式文件系統,做者爲淘寶資深架構餘慶。 FastDFS 主要解決了分佈式文件存儲與高併發訪問的問題,實現了負載均衡,適合存儲圖片、視頻、文檔等文件,並且支持存儲服務器的在線擴容。github

二、FastDFS 架構

FastDFS 服務端有兩個角色:Tracker 與 Storage,其中 Tracker 主要作調度工做,有着負載均衡做用,Storage 負責文件存取、同步等操做。後端

FastDFS 系統結構:服務器

FastDFS系統結構.png

2.一、Client

客戶端訪問 FastDFS 分佈式存儲,通常爲後端應用。架構

2.二、Tracker

Tracker 在 FastDFS 集羣中有兩大做用:併發

  • 管理 Storage 集羣,在 Storage 服務啓動時,會把本身註冊到 Tracker 上,並按期上報自身狀態信息,包括磁盤剩餘空間、文件同步狀態、文件上傳下載次數等統計信息。
  • Client 訪問 Storage 服務以前,必須先訪問 Tracker,動態獲取到 Storage 服務的鏈接信息,有着負載均衡的做用。

2.三、Storage

Storage 是數據存儲服務器,文件和 meta data 都保存在 Storage 服務器中。 有如下特色:app

  • 採用高可用的方式進行數據存儲。
  • FastDFS 集羣中,Storage 按組(Group/volume)提供服務,不一樣組的 Storage 之間不會互相通訊,同組內的 Storage 之間會相互鏈接,進行文件同步。
  • Storage 服務採用 binlog 文件記錄文件上傳、刪除等更新操做,binlog 中只記錄文件名,不記錄內容。
  • 文件同步只在同組內的 Storage 服務之間進行,採用 push 方式,即圓通服務器同步給目標服務器。
  • FastDFS 將文件及相關的描述信息(MetaData)保存在 Storage 服務中,文件存儲之後將返回惟一的文件標識,文件標識有組名和文件名兩部分構成,MetaData 是文件的描述信息,如 width=1024,height=768。

三、文件上傳原理

文件上傳的原理以下圖:負載均衡

上傳文件交互過程.jpeg

  1. Client 詢問 Tracker 能夠上傳到哪一個 Storage。
  2. Tracker 返回一臺可用的 Storage 鏈接信息。
  3. Client 直接與 Storage 通訊,完成文件上傳。
  4. Storage 保存文件之後,返回 Client 文件標識(組名、文件名)。

四、文件下載原理

文件下載原理以下圖:分佈式

下載文件交互過程.jpeg

  1. Client 詢問 Tracker 下文文件的 Storage,參數爲文件標識(組名、文件名)。
  2. Tracker 返回一臺可用的 Storage。
  3. Client 與 Storage 通訊,完成文件下載過程。

五、文件同步原理

  • 同一個組內的 Storage 服務是對等的,文件上傳、刪除等操做能夠在任意一臺 Storage 服務上執行,數據會在同組內 Storage 內同步。
  • 文件同步(上傳、刪除、更新)採用 push 方式,即源服務器同步給目標服務器。
  • 只有源頭數據才須要同步,若是備份數據再次同步就會造成環路。
  • 當新增 Storage 服務時,將已有的一臺 Storage 的全部數據(源頭數據與備份數據)同步給這臺新增服務器。

六、服務端文件目錄

6.一、TrackerServer

${base_path}

|__data

|     |__storage_groups.dat:存儲分組信息

|     |__storage_servers.dat:存儲服務器列表

|__logs

   |__trackerd.log:tracker server日誌文件

6.二、StorageServer

${base_path}

|__data

|     |__.data_init_flag:當前storage server 初始化信息

|     |__storage_stat.dat:當前storage server統計信息

|     |__sync:存放數據同步相關文件

|     |     |__binlog.index:當前的binlog文件索引號

|     |     |__binlog.###:存放更新操做記錄(日誌)

|     |     |__${ip_addr}_${port}.mark:存放同步的完成狀況

|     |

|     |__一級目錄:256個存放數據文件的目錄,如:00, 1F

|           |__二級目錄:256個存放數據文件的目錄

|__logs

   |__storaged.log:storage server日誌文件

七、服務端與客戶端通信協議

7.一、 通信協議介紹

FastDFS 服務端與客戶端通信時候採用的是自定義的通信協議,以下圖所示:

proto.png

協議包由兩部分組成:header 和 body

  • header 共 10 字節,格式以下:
    • 8 bytes body length
    • 1 byte command
    • 1 byte status
  • body 數據包格式取決於具體的命令,body 能夠爲空。

7.二、命令代碼和通信狀態代碼

7.2.一、Tracker 管理命令代碼
名稱 命令
刪除 storage 93
獲取下載節點 QUERY_FETCH_ONE 102
獲取更新節點 QUERY_UPDATE 103
不按組獲取存儲節點 101
按組獲取存儲節點 104
獲取組列表 91
獲取存儲節點列表 92
7.2.二、 Store 文件上傳命令代碼
名稱 命令 說明
文件上傳 11 通常的文件上傳,上傳後爲主文件
上傳附屬文件 21 "上傳從文件文件,好比主文件爲 xxx.jpg,從文件(縮略圖)爲 xxx-150_150.jpg"
刪除文件 12 刪除文件
設置文件元數據 13 上傳文件建立日期,標籤等
文件下載 14
獲取文件元數據 15
查詢文件信息 22 查詢文件信息
建立支持斷點續傳的文件 23 建立一個支持斷點續傳的文件
斷點續傳 24 上傳可斷點上傳的文件,如將大文件切爲幾份,分開上傳
文件修改 34 修改支持斷點上傳的文件
清除文件 36 截取(清除)支持斷點上傳的文件
7.2.三、報文通信狀態代碼
名稱 代碼
客戶端關閉鏈接命令 82
鏈接狀態檢查命令 111
服務端正確返回報文 100

八、簡單使用

我使用的是 fastdfs-client-java-1.27-SNAPSHOT.jar happyfish100/fastdfs-client-java

這個庫從 17 年 6 月 5 號以後就中止更新了,最近又開始更新代碼了,看樣子要維護了啊。

簡單的對客戶端進行了鏈接池的封裝,方便使用。

  • 系統啓動,池子管理鏈接
  • 心跳確認鏈接是否可靠
  • 構造器模式建立鏈接池
  • 回調方式使用客戶端

源碼地址:

ClawHub/FastDFS-Pool

如下爲核心代碼:

1.一、 初始化鏈接池

/**
     * Build fast dfs conn pool.
     *
     * @return the fast dfs conn pool
     */
    public FastDFSConnPool build() {
        // 初始化空閒鏈接池
        idleConnectionPool = new LinkedBlockingQueue<>(maxPoolSize);
        //初始化全局參數
        try {
            ClientGlobal.init(confFileName);
        } catch (IOException | MyException e) {
            throw new RuntimeException("init client global exception.", e);
        }
        // 往線程池中添加默認大小的線程
        TrackerServer trackerServer;
        for (int i = 0; i < minPoolSize; i++) {
            //獲取到鏈接
            trackerServer = createTrackerServer();
            if (trackerServer != null) {
                //放入空閒池
                idleConnectionPool.offer(trackerServer);
            }
        }
        // 註冊心跳
        new HeartBeat(this).beat();

        return this;
    }

1.二、 客戶端執行請求

/**
     * 執行方式
     *
     * @param <T>    the type parameter
     * @param invoke the invoke
     * @return the t
     */
    public <T> T processFdfs(CallBack<T> invoke) {
        TrackerServer trackerServer = null;
        T t;
        try {
            //獲取tracker鏈接
            trackerServer = fastDFSConnPool.checkOut();
            //獲取storage
            StorageClient1 storageClient = new StorageClient1(trackerServer, null);
            //執行操做
            t = invoke.invoke(storageClient);
            //釋放鏈接
            fastDFSConnPool.checkIn(trackerServer);
            return t;
        } catch (Exception e) {
            //刪除連接
            fastDFSConnPool.drop(trackerServer);
            throw new RuntimeException(e);
        }
    }

1.三、 心跳

/**
     * 心跳任務
     */
    private class HeartBeatTask implements Runnable {

        @Override
        public void run() {
            LinkedBlockingQueue<TrackerServer> idleConnectionPool = fastDFSConnPool.getIdleConnectionPool();
            TrackerServer ts = null;
            for (int i = 0; i < idleConnectionPool.size(); i++) {
                try {
                    ts = idleConnectionPool.poll(fastDFSConnPool.getWaitTimes(), TimeUnit.SECONDS);
                    if (ts != null) {
                        ProtoCommon.activeTest(ts.getSocket());
                        idleConnectionPool.add(ts);
                    } else {
                        //表明已經沒有空閒長鏈接
                        break;
                    }
                } catch (Exception e) {
                    //發生異常,要刪除,進行重建
                    logger.error("heart beat conn  have dead, and reconnect.", e);
                    fastDFSConnPool.drop(ts);
                }
            }

        }
    }

1.四、 使用方式

//初始化鏈接池
        FastDFSConnPool fastDFSConnPool = new FastDFSConnPool()
                .confFileName("./config/fdfs_client.conf")
                .maxPoolSize(8)
                .minPoolSize(1)
                .reConnNum(2)
                .waitTimes(2).build();

        //使用客戶端
        FastDFSClient client = new FastDFSClient(fastDFSConnPool);
        //上傳  ileName 文件全路徑 extName 文件擴展名,不包含(.) metas 文件擴展信息
        String parts = client.processFdfs(storageClient -> storageClient.upload_file1("fileName", "extName", new NameValuePair[0]));
        //下載 fileId: group1/M00/00/00/wKgRsVjtwpSAXGwkAAAweEAzRjw471.jpg
        byte[] bytes = client.processFdfs(storageClient -> storageClient.download_file1("fileId"));
        //刪除 -1失敗,0成功
        int result = client.processFdfs(storageClient -> storageClient.delete_file1("fileId"));
        //獲取遠程服務器文件資源信息  groupName   文件組名 如:group1  remoteFileName M00/00/00/wKgRsVjtwpSAXGwkAAAweEAzRjw471.jpg
        FileInfo fileInfo = client.processFdfs(storageClient -> storageClient.get_file_info("groupName", "remoteFileName"));

參考

FastDFS V5.12 分佈式文件系統介紹

tobato/FastDFS_Client 的 wiki

tencent.jpg

相關文章
相關標籤/搜索