FastDFS分散式檔案系統簡單使用
FastDFS簡介
1、FastDFS體系結構
FastDFS是一個開源的輕量級分散式檔案系統,它對檔案進行管理,功能包括:檔案儲存、檔案同步、檔案訪問(檔案上傳和下載)等,解決了大容量儲存和負載均衡的問題,特別適合以檔案為載體的線上服務,如相簿網站、視訊網站等等。
FastDFS架構包括Tracker server(跟蹤服務,類似zookeeper註冊中心,負責排程) 和storage server(真正儲存檔案的地方)。客戶端(指的是系統,或者說專案,再或者說寫程式碼的地方)傳送請求,到traker server進行檔案上傳、下載,tracker server又會通過排程各個storage server進行負載均衡演算法,將storage server的地址返回給客戶端,客戶端拿到地址以後就可以將檔案儲存到該地址的storager server的服務上,storger server又會返回儲存好的檔案id給客戶端,客戶端可以通過這個檔案id在瀏覽器上訪問。
2、使用FastDFS上傳檔案流程
解釋:storager server是真正儲存檔案的地方,它會定時向排程中心(tracker server)上傳狀態資訊,並且storage server可以有很多個,這樣tracker server就可以做負載均衡。
在使用的時候,客戶端需要先向排程中心(storager server)傳送請求,排程中心(storage server)會根據負載均衡演算法返回一個tracker server(儲存檔案的服務)的地址。
客戶端獲取到這個地址以後,就可以直接根據地址上傳檔案,storage server(儲存檔案的服務)會生成一個檔案id,並將這個檔案id返回,客戶端拿到這個檔案id,拼接ip和埠號以後,就可以通過瀏覽器訪問該圖片。
檔案id的組成:
group1: 組名:檔案上傳後所在的storage server 組名稱,檔案上傳後由storage server返回,需要客戶端自行儲存
/M00: 虛擬磁碟路徑,storage server配置的虛擬路徑,與磁碟選項store_path*對應。如果配置store_path0 則是 M00,如果配置了 store_path1 則是 M01,以此類推。
/02/44:資料兩級目錄:storage 伺服器在每個虛擬磁碟路徑下建立的兩級目錄,用於儲存資料
/wkgD讓E34E8wAAAAAAAA……檔名:與檔案上傳時不同。是由儲存伺服器根據特定資訊生成,檔名包含:源儲存
3、下載檔案流程
跟上傳差不多,前端傳送請求,帶著圖片路徑,請求傳送到處理圖片的controller中,controller用程式碼去連線tracker server,tracker server返回這張圖片儲存的storage server地址,controller拿到地址以後,訪問該地址的sotrage server,sotrage server就會找到該圖片,並以流的形式返回給controller,contorller先讀流,再寫流到指定的位置,就完成了下載的流程。
4、使用者訪問圖片流程
使用者訪問圖片,不再像上傳和下載那樣,通過controller、tracker server、storage server了,這樣反而繞了一大圈,並且獲得的還是流資料,所以這裡直接由使用者傳送http請求,由nginx接收,處理請求後最終獲得圖片,如上圖,fastdfs_nginx_module能通過檔名中的組名等資訊找到對應的storage server,從而獲得圖片返回。
5、微服務專案中,圖片處理的解決方案
在微服務專案中,如果很多模組都需要用到圖片上傳,例如電商專案,一開啟頁面到處都是圖片,那麼圖片請求的併發量肯定高,並且分佈在各個模組,如果圖片因為併發量的原因,給每個模組都進行叢集的話,那麼一些原本併發量並不大的模組因為其中的圖片請求處理而需要叢集的話,對於專案成本來說是不合適的,所以這個時候就可以將圖片處理單獨寫一個微服務,無論哪個模組需要上傳、下載圖片或者檔案,都只需要發請求到這個微服務中就可以了,如果請求併發量大,對這個圖片處理微服務進行叢集即可,跟原本的功能模組沒有任何聯絡,它們之間想要聯絡,遠端通過feign呼叫即可。
使用FastDFS實現檔案上傳、下載、刪除
建立一個spring boot工程,該工程主要用於實現檔案上傳、下載、刪除
1、新增依賴:下面的依賴其實有最後一個就行了,其它的是因為微服務中需要用到的,跟FastDFS沒有直接聯絡
<!--spring-cloud-feign--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> <!--web起步依賴--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!--eureka客戶端--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <!--httpclient支援--> <dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient</artifactId> </dependency>
<!--fastDFS依賴--> <dependency> <groupId>net.oschina.zcx7878</groupId> <artifactId>fastdfs-client-java</artifactId> <version>1.27.0.0</version> </dependency>
2、FastDFS配置
在resources資料夾下建立fasfDFS的配置檔案fdfs_client.conf:
# connect_timeout:設定連線超時時間,單位為秒 connect_timeout=60 # network_timeout:通訊超時時間,單位為秒。傳送或接收資料時。假設在超時時間後還不能傳送或接收資料,則本次網路通訊失敗 network_timeout=60 # 字符集 charset=UTF-8 # tracker的http埠 http.tracker_http_port=8080 # tracker伺服器IP和埠設定 tracker_server=192.168.211.132:22122
3、application.yml配置
在resources資料夾下建立application.yml:
spring: servlet: multipart: max-file-size: 10MB # 設定單個檔案大小限制,請求檔案超過則不允許傳 max-request-size: 10MB #設定請求總上傳的資料大小 application: name: file server: port: 18082 eureka: client: service-url: defaultZone: http://127.0.0.1:7001/eureka instance: prefer-ip-address: true feign: hystrix: enabled: true
4、啟動類:建立com.xxx包,建立啟動類FileApplication
@SpringBootApplication @EnableEurekaClient public class FileApplication { public static void main(String[] args) { SpringApplication.run(FileApplication.class); } }
5、建立測試類測試:
public class FastDFSTest { //上傳圖片 @Test public void upload() throws Exception { //1.建立一個配置檔案 用於配置連線到tracker server的ip地址和埠 //2.載入配置檔案使其生效,引數指定配置檔案的全路徑 ClientGlobal.init("D:\\XM1\\xxx\\xxx-parent\\xxx-service\\xxx-service-file\\src\\main\\resources\\fastDFS_client.conf"); //3.建立一個trackerClient TrackerClient trackerClient = new TrackerClient(); //4.獲取tracker server TrackerServer trackerServer = trackerClient.getConnection(); //5.建立storageServer 賦值為空 //6.建立storageClient (提供了很多圖片操作的API) //引數1 指定track server //引數2 指定storage server,如果你知道要上傳到哪個storage server的ip地址和埠,那就填,但是這裡並不知道 StorageClient storageClient = new StorageClient(trackerServer,null); //7.執行上傳圖片 //引數1 指定圖片的所在的路徑 //引數2 指定圖片的字尾 去掉 "點" //引數3 指定圖片的元資料 :高度 寬度 畫素 作者 拍攝日期 檔案大小......... String[] pngs = storageClient.upload_file("D:\\aaa\\微信圖片_202008300825239.jpg", "png", null); for (String png : pngs) { System.out.println(png); } } }
啟動上傳方法,就會將指定路徑中的圖片上傳到FastDFS的Storage 伺服器中,然後輸出返回的檔名,列印如下:
group1就是組名
可以用ip、埠、組名、這個檔名拼接,然後在瀏覽器訪問:http://192.168.211.132:8080/group1/M00/00/00/wKjThF-rCGGAQaYRAAFNwOGrCrc492.png
上面測試了上傳,那麼下載和刪除也差不多的步驟,接著在上面的測試類中加兩個方法:
//下載圖片 單例模式 @Test public void download() throws Exception{ //1.建立一個配置檔案 用於配置連線到tracker server的ip地址和埠 //2.載入配置檔案使其生效 ClientGlobal.init("D:\\XM1\\xxx\\xxx-parent\\changgou-service\\xxx-service-file\\src\\main\\resources\\fastDFS_client.conf"); //3.建立一個trackerClient TrackerClient trackerClient = new TrackerClient(); //4.獲取tracker server TrackerServer trackerServer = trackerClient.getConnection(); //5.建立storageServer 賦值為空 //6.建立storageClient (提供了很多圖片操作的API) //引數1 指定track server //引數2 指定storag eserver StorageClient storageClient = new StorageClient(trackerServer,null); //7.執行下載圖片 //引數1 組名 引數2 檔名 byte[] bytes = storageClient.download_file("group1", "M00/00/00/wKjThF-rCGGAQaYRAAFNwOGrCrc492.png"); //將檔案下載到的目錄 FileOutputStream fileOutputStream = new FileOutputStream(new File("D:\\1234.jpg")); fileOutputStream.write(bytes); fileOutputStream.close();//放finally中 }
刪除圖片:
//刪除圖片 @Test public void deleteFile() throws Exception{ //1.建立一個配置檔案 用於配置連線到tracker server的ip地址和埠 //2.載入配置檔案使其生效 ClientGlobal.init("D:\\XM1\\xxx\\xxx-parent\\changgou-service\\xxx-service-file\\src\\main\\resources\\fastDFS_client.conf"); //3.建立一個trackerClient TrackerClient trackerClient = new TrackerClient(); //4.獲取tracker server TrackerServer trackerServer = trackerClient.getConnection(); //5.建立storageServer 賦值為空 //6.建立storageClient (提供了很多圖片操作的API) //引數1 指定track server //引數2 指定storage server StorageClient storageClient = new StorageClient(trackerServer,null); int group1 = storageClient.delete_file("group1", "M00/00/00/wKjThF-rCGGAQaYRAAFNwOGrCrc492.png"); if(group1==0){ System.out.println("成功"); }else{ System.out.println("成仁"); }
以上方式只是簡單使用的api,還有許多地方都是寫死的,所以一般在開發中,都會封裝一下,實現動態。:
建立一個Util類,以及一個封裝檔案操作資料的pojo:
/** * 檔案上傳、下載、刪除的工具類,呼叫可實現。 * */ public class FastDFSClientUtil { //1.載入配置檔案使其生效,用靜態程式碼塊實現單例模式,它只需要載入一次 //不再寫死載入檔案路徑,用類路徑動態獲取 static { try { //建立物件,動態獲取類路徑下指定檔名的配置檔案 ClassPathResource classPathResource = new ClassPathResource("fastDFS_client.conf"); ClientGlobal.init(classPathResource.getPath()); } catch (Exception e) { e.printStackTrace(); } } /** * 檔案上傳 * 返回值:String【】:這個陣列【0】元素是檔案儲存的組名,【1】元素是檔名 * 引數:封裝的儲存前端請求代來的檔案相關資訊 * */ public static String[] uploadFile(FastDFSFile fastDFSFile) throws Exception { //3.建立一個trackerClient TrackerClient trackerClient = new TrackerClient(); //4.獲取tracker server TrackerServer trackerServer = trackerClient.getConnection(); //5.建立storageServer 賦值為空 //6.建立storageClient (提供了很多圖片操作的API) //引數1 指定track server //引數2 指定storage server,如果你知道要上傳到哪個storage server的ip地址和埠,那就填,但是這裡並不知道 StorageClient storageClient = new StorageClient(trackerServer,null); //7.執行上傳圖片,這裡也需要更改,不再傳檔案路徑,而是傳檔案內容(位元組陣列) //引數1 指定檔案本身內容(位元組陣列) //引數2 指定圖片的字尾 去掉 "點" //引數3 指定圖片的元資料 :高度 寬度 畫素 作者 拍攝日期 檔案大小......... NameValuePair[] meta_list = new NameValuePair[]{ new NameValuePair(fastDFSFile.getName()) }; String[] pngs = storageClient.upload_file(fastDFSFile.getContent(), fastDFSFile.getExt(),meta_list); return pngs; } /** * 下載檔案 * 返回值:位元組陣列:檔案內容 * 引數: 要下載的檔案存在Storage server 中的組名、檔名 * */ public static byte[] downFile(String groupName,String fileName) throws Exception { //3.建立一個trackerClient TrackerClient trackerClient = new TrackerClient(); //4.獲取tracker server TrackerServer trackerServer = trackerClient.getConnection(); //5.建立storageServer 賦值為空 //6.建立storageClient (提供了很多圖片操作的API) //引數1 指定track server //引數2 指定storag eserver StorageClient storageClient = new StorageClient(trackerServer,null); //7.執行下載圖片 //引數1 組名 引數2 檔名 byte[] bytes = storageClient.download_file(groupName, fileName); //8.返回位元組陣列 return bytes; } /** * 刪除檔案 * 返回值: 刪除結果,true刪除成功,false者刪除失敗 * 引數:檔案儲存的組名和檔案儲存的檔名 * */ public static boolean deleteFile(String groupName,String fileName) throws Exception { //3.建立一個trackerClient TrackerClient trackerClient = new TrackerClient(); //4.獲取tracker server TrackerServer trackerServer = trackerClient.getConnection(); //5.建立storageServer 賦值為空 //6.建立storageClient (提供了很多圖片操作的API) //引數1 指定track server //引數2 指定storage server StorageClient storageClient = new StorageClient(trackerServer,null); //引數1 指定儲存檔案的組名 //引數2 指定儲存檔案的檔名 //返回值,返回值如果等於0則說明刪除成功 int group1 = storageClient.delete_file(groupName, fileName); //如果下面的運算成立,則返回true,否則返回false return group1==0; }
/** * 封裝檔案屬性 * */ public class FastDFSFile { //檔名字 private String name; //檔案內容 private byte[] content; //副檔名 private String ext; //檔案MD5摘要值 private String md5; //檔案建立作者 private String author; public String getName() { return name; } public void setName(String name) { this.name = name; } public byte[] getContent() { return content; } public void setContent(byte[] content) { this.content = content; } public String getExt() { return ext; } public void setExt(String ext) { this.ext = ext; } public String getMd5() { return md5; } public void setMd5(String md5) { this.md5 = md5; } public String getAuthor() { return author; } public void setAuthor(String author) { this.author = author; } public FastDFSFile(String name, byte[] content, String ext, String md5, String author) { this.name = name; this.content = content; this.ext = ext; this.md5 = md5; this.author = author; } public FastDFSFile() { } }
再然後就寫controller接受請求,呼叫util實現檔案上傳,返回前端路徑即可(以下程式碼沒有完善,還有redis快取和真正將路徑存入資料庫的操作沒有體現):
/** * 圖片上傳、刪除模組 * */ @RequestMapping("/file") @RestController public class FileController { @Value("${pic.url}") private String path; @PostMapping("/upload") public Result<String> uploadFile(MultipartFile multipartFile){ try { //判斷,如果傳過來的檔案內容不為空 if (!StringUtils.isEmpty(multipartFile)){ //建立上傳圖片需要的資料pojo並賦值 FastDFSFile fastDFSFile = new FastDFSFile(); //給檔名賦值 fastDFSFile.setName(multipartFile.getOriginalFilename()); //給檔案字尾(檔案型別)賦值,用StringUtils,從檔名中擷取到檔案字尾 fastDFSFile.setExt(StringUtils.getFilenameExtension(multipartFile.getOriginalFilename())); //給檔案本身賦值(檔案的位元組陣列) fastDFSFile.setContent(multipartFile.getBytes()); //呼叫檔案上傳util執行檔案上傳操作,並將賦值完畢的pojo資料作為引數傳過去 String[] strings = FastDFSClientUtil.uploadFile(fastDFSFile); //將fastDFS服務端返回的檔案路徑拼接成使用者可瀏覽的路徑: //將http:ip,埠號等前面的路徑放在配置檔案中,實現動態獲取,然後拼接路徑 String url = path +"/"+ strings[0] +"/"+ strings[1]; return new Result<String>(true, StatusCode.OK,"上傳成功",url); } } catch (Exception e) { e.printStackTrace(); } return null; } }
上面這個controller顯然還有一個地方,就是拼接使用者可訪問路徑的時候,是實現了動態從配置檔案中獲取的,所以springboot 的application配置檔案中需要有這個路徑才能拼接:檔案中的路徑因為我沒有域名,所以只能這樣:
spring: servlet: multipart: max-file-size: 10MB # max-file-size是單個檔案大小限制 max-request-size: 10MB # max-request-size是設定總上傳的資料大小 application: name: file server: port: 18082 eureka: client: service-url: defaultZone: http://127.0.0.1:7001/eureka instance: prefer-ip-address: true feign: hystrix: enabled: true pic: # 最好是通過域名來訪問設定 url: http://192.168.211.132:8080