FastDFS是一個輕量級分佈式文件系統。能夠對文件進行管理,功能包括:文件存儲、文件同步、文件訪問(文件上傳、文件下載)等,並且能夠集羣部署,有高可用保障。相應的競品有Ceph、TFS等。相比而言FastDFS對硬件的要求比較低,因此適合中小型公司。html
FastDFS服務端由兩個重要部分組成:跟蹤器(Tracker)和存儲節點(Storage)。java
Tracker主要作調度工做,在訪問上起負載均衡的做用。Tracker能夠作集羣部署,各個節點之間是平等的,客戶端請求時採用輪詢機制,某個Tracker不能提供服務時就換另外一個。Storage啓動後會鏈接到Tracker Server告知本身的Group信息,造成映射關聯,並採用心跳機制保持狀態。
Storage存儲節點負責文件的存儲,Storage能夠集羣部署。nginx
Storage集羣有如下特色:git
此章節根據資料整理,可能隨着版本有所改變,這裏只介紹大體的,以便了解整個運做流程。若是須要深刻研究,建議仍是以官方文檔爲標準。github
一,客戶端請求會打到負載均衡層,到tracker server時,因爲每一個server之間是對等的關係,因此能夠任意選擇一個tracker server。web
二,到storage層:tracker server接收到upload file請求時,會爲該請求分配一個能夠存儲該文件的group。spring
分配group規則:docker
三,肯定group後,tracker會在group內選擇一個storage server給客戶端。apache
在group內選擇storage server時規則:segmentfault
四,選擇storage path:當分配好storage server後,客戶端向storage發送寫文件請求,storage將會爲文件分配一個數據存儲目錄,支持規則以下:
五,生成File id:選定存儲目錄以後,storage會爲文件生成一個File id。規則以下:
由storage server ip、文件建立時間、文件大小,文件crc32和一個隨機數拼接而成,而後將這個二進制串進程base64編碼,轉換爲可打印的字符串。
六,選擇兩級目錄:每一個存儲目錄下有兩級256 * 256的子目錄,storage會按文件Field進行兩次hash,路由到其中的一個目錄,而後將文件以file id爲文件名存儲到該子目錄下。
一個文件路徑最終由以下組成:組名/磁盤/目錄/文件名
七,客戶端upload file成功後,會拿到一個storage生成的文件名,接下來客戶端根據這個文件名便可訪問到該文件。
下載流程以下:
一,選擇tracker server:和upload file同樣,在download file時隨機選擇tracker server。
二,選擇group:tracker發送download請求給某個tracker,必須帶上文件名信息,tracker從文件名中解析出group、大小、建立時間等信息,根據group信息獲取對於的group。
三,選擇storage server:從group中選擇一個storage用來服務讀請求。因爲group內的文件同步時在後臺異步進行的,因此有可能出如今讀到的時候,文件尚未同步到某些storage server上,爲了儘可能避免反問道這樣的storage,tracker按照必定的規則選擇group內可讀的storage。
Storage還能夠結合nginx的fastdfs-nginx-module提供http服務,以實現圖片等預覽功能。
這個部分這裏不作介紹,後續可能單獨寫篇文章,由於我發現對fastDFS集羣提供http服務仍是挺複雜,包括我下面找的docker鏡像都不完善,主要是規劃的問題,包括衍生的服務,緩存,以及對圖片的處理(nginx+lua)這些,後續打算研究下,從新開源個docker構建鏡像。
FastDFS安裝方法網上有不少教程,這裏很少講,我建議使用docker來運行FastDFS,能夠本身根據安裝步驟構建本身的鏡像。而後在須要的機器直接運行,後續擴容也方便,再啓動一個storage容器就能夠了。
詳細版安裝推薦篇文章:https://segmentfault.com/a/11...
我這裏從github上找的一個別人構建好的鏡像,能夠直接使用。地址:https://github.com/luhuiguo/f...
使用方法也很簡單
# 啓動一個tracker服務器 docker run -dti --network=host --name tracker -v /var/fdfs/tracker:/var/fdfs luhuiguo/fastdfs tracker # 啓動storage0 docker run -dti --network=host --name storage0 -e TRACKER_SERVER=10.1.5.85:22122 -v /var/fdfs/storage0:/var/fdfs luhuiguo/fastdfs storage # 再啓動一個storage1 docker run -dti --network=host --name storage1 -e TRACKER_SERVER=10.1.5.85:22122 -v /var/fdfs/storage1:/var/fdfs luhuiguo/fastdfs storage # 啓動一個新組的storage docker run -dti --network=host --name storage2 -e TRACKER_SERVER=10.1.5.85:22122 -e GROUP_NAME=group2 -e PORT=22222 -v /var/fdfs/storage2:/var/fdfs luhuiguo/fastdfs storage
1,原github地址上的usage介紹,啓動storage0和storage1有一個參數錯誤(多一個-e),以我上面發的命令爲準。
2,這裏的TRACKER_SERVER注意改成你本身的,同一個網段內網ip。
3,實際上這裏docker容器之間仍是同一個物理主機上部署的(根據network而言),雖而後續能夠經過加硬盤,而後新建storage綁定到新加硬盤mount上,可是若是是大公司的生產環境仍是推薦創建一個overlay網絡,具體見:https://www.cnblogs.com/bigbe...,這樣能夠直接擴物理機集羣了。另外這裏也提供docker-compose方式啓動服務,實際也不推薦使用,由於tracker和storage server之後必然是分開的,因此仍是推薦單個docker容器保持靈活性。這裏高級點能夠用k8s進行自動擴容(後續打算從新開源個鏡像)。
這裏使用官方的客戶端包:https://github.com/happyfish1...
# 下載源碼 git clone https://github.com/happyfish100/fastdfs-client-java.git cd fastdfs-client-java # 打jar包 mvn clean install # 輸出目錄 cd target # 導入到本地倉庫 注意這裏version根據實際生成的來 mvn install:install-file -DgroupId=org.csource -DartifactId=fastdfs-client-java -Dversion=1.27-SNAPSHOT -Dpackaging=jar -Dfile=fastdfs-client-java-1.27-SNAPSHOT.jar
<dependency> <groupId>org.csource</groupId> <artifactId>fastdfs-client-java</artifactId> <version>1.27-SNAPSHOT</version> </dependency> <dependency> <groupId>commons-fileupload</groupId> <artifactId>commons-fileupload</artifactId> <version>1.3.1</version> </dependency> <dependency> <groupId>commons-io</groupId> <artifactId>commons-io</artifactId> <version>2.2</version> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <version>3.1</version> </dependency>
在resource目錄下,添加conf/fdfs_client.conf配置文件
connect_timeout = 2 network_timeout = 30 charset = UTF-8 http.tracker_http_port = 80 http.anti_steal_token = no http.secret_key = FastDFS1234567890 tracker_server = 192.168.1.163:22122
測試時實際上只需關注tracker_server,而且改成你本身的tracker server
applicationContext.xml配置中添加文件上傳bean
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"> <property name="maxUploadSize" value="62914560" /> <property name="defaultEncoding" value="UTF-8" /> </bean>
建一個簡單的client封裝(勿做生產使用)
FastDFSClient.java
package com.rootrl.fastDFSDemo.utiles; import org.apache.commons.lang3.StringUtils; import org.csource.common.NameValuePair; import org.csource.fastdfs.*; import java.io.File; import java.io.IOException; import java.io.InputStream; public class FastDFSClient { private static StorageClient1 storageClient1 = null; static { try { // 獲取配置文件 String classPath = new File(FastDFSClient.class.getResource("/").getFile()).getCanonicalPath(); String CONF_FILENAME = classPath + File.separator + "conf" + File.separator + "fdfs_client.conf"; ClientGlobal.init(CONF_FILENAME); // 獲取觸發器 TrackerClient trackerClient = new TrackerClient(ClientGlobal.g_tracker_group); TrackerServer trackerServer = trackerClient.getConnection(); // 獲取存儲服務器 StorageServer storageServer = trackerClient.getStoreStorage(trackerServer); storageClient1 = new StorageClient1(trackerServer, storageServer); } catch (Exception e) { System.out.println(e); } } /** * 上傳文件 * @param fis 文件輸入流 * @param fileName 文件名稱 * @return */ public static String uploadFile(InputStream fis, String fileName) { try { NameValuePair[] meta_list = null; //將輸入流寫入file_buff數組 byte[] file_buff = null; if (fis != null) { int len = fis.available(); file_buff = new byte[len]; fis.read(file_buff); } String fileid = storageClient1.upload_file1(file_buff, getFileExt(fileName), meta_list); return fileid; } catch (Exception ex) { return null; } finally { if (fis != null) { try { fis.close(); } catch (IOException e) { System.out.println(e); } } } } /** * 獲取文件後綴 * @param fileName * @return */ private static String getFileExt(String fileName) { if (StringUtils.isBlank(fileName) || !fileName.contains(".")) { return ""; } else { return fileName.substring(fileName.lastIndexOf(".") + 1); } } }
而後創建一個File控制器,作測試用
FileController.java
package com.rootrl.fastDFSDemo.controller; import com.rootrl.fastDFSDemo.utiles.FastDFSClient; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.multipart.MultipartFile; @Controller @RequestMapping("fastdfs") public class FileController { @RequestMapping(value = "upload") @ResponseBody public String uploadFileSample(@RequestParam MultipartFile file){ try { String fileId = FastDFSClient.uploadFile(file.getInputStream(), file.getOriginalFilename()); return fileId; } catch (Exception e) { System.out.println(e.getMessage()); return "error"; } } }
而後使用postman客戶端測試,url爲:http://localhost:8080/fastdfs/upload.do(依據本身實際狀況變動)
注意postman使用post請求,而後切換到body/form-data標籤項,添加一個Key爲file,類型爲file,而後value就能夠上傳文件了。成功會返回文件id,相似:group1/M00/00/00/wKgBo1zjxnOAT-k1AAAoMlb3hzU996.png