FastDFS 屬於分佈式存儲範疇,分佈式文件系統 FastDFS 很是適合中小型項目,在我接手維護公司圖片服務的時候開始接觸到它,本篇文章目的是總結一下 FastDFS 的知識點。java
用了 2 臺 2 核 4G 的阿里雲服務器作集羣部署,具體部署步驟請參考:https://github.com/happyfish100/fastdfs/wikigit
FastDFS 是一個輕量級的開源分佈式文件系統,做者爲淘寶資深架構餘慶。 FastDFS 主要解決了分佈式文件存儲與高併發訪問的問題,實現了負載均衡,適合存儲圖片、視頻、文檔等文件,並且支持存儲服務器的在線擴容。github
FastDFS 服務端有兩個角色:Tracker 與 Storage,其中 Tracker 主要作調度工做,有着負載均衡做用,Storage 負責文件存取、同步等操做。後端
FastDFS 系統結構:服務器
客戶端訪問 FastDFS 分佈式存儲,通常爲後端應用。架構
Tracker 在 FastDFS 集羣中有兩大做用:併發
Storage 是數據存儲服務器,文件和 meta data 都保存在 Storage 服務器中。 有如下特色:app
文件上傳的原理以下圖:負載均衡
文件下載原理以下圖:分佈式
${base_path} |__data | |__storage_groups.dat:存儲分組信息 | |__storage_servers.dat:存儲服務器列表 |__logs |__trackerd.log:tracker server日誌文件
${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日誌文件
FastDFS 服務端與客戶端通信時候採用的是自定義的通信協議,以下圖所示:
協議包由兩部分組成:header 和 body
名稱 | 命令 |
---|---|
刪除 storage | 93 |
獲取下載節點 QUERY_FETCH_ONE | 102 |
獲取更新節點 QUERY_UPDATE | 103 |
不按組獲取存儲節點 | 101 |
按組獲取存儲節點 | 104 |
獲取組列表 | 91 |
獲取存儲節點列表 | 92 |
名稱 | 命令 | 說明 |
---|---|---|
文件上傳 | 11 | 通常的文件上傳,上傳後爲主文件 |
上傳附屬文件 | 21 | "上傳從文件文件,好比主文件爲 xxx.jpg,從文件(縮略圖)爲 xxx-150_150.jpg" |
刪除文件 | 12 | 刪除文件 |
設置文件元數據 | 13 | 上傳文件建立日期,標籤等 |
文件下載 | 14 | |
獲取文件元數據 | 15 | |
查詢文件信息 | 22 | 查詢文件信息 |
建立支持斷點續傳的文件 | 23 | 建立一個支持斷點續傳的文件 |
斷點續傳 | 24 | 上傳可斷點上傳的文件,如將大文件切爲幾份,分開上傳 |
文件修改 | 34 | 修改支持斷點上傳的文件 |
清除文件 | 36 | 截取(清除)支持斷點上傳的文件 |
名稱 | 代碼 |
---|---|
客戶端關閉鏈接命令 | 82 |
鏈接狀態檢查命令 | 111 |
服務端正確返回報文 | 100 |
我使用的是 fastdfs-client-java-1.27-SNAPSHOT.jar happyfish100/fastdfs-client-java
這個庫從 17 年 6 月 5 號以後就中止更新了,最近又開始更新代碼了,看樣子要維護了啊。
簡單的對客戶端進行了鏈接池的封裝,方便使用。
源碼地址:
如下爲核心代碼:
/** * 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; }
/** * 執行方式 * * @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); } }
/** * 心跳任務 */ 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); } } } }
//初始化鏈接池 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"));