Spring Boot使用mongo的GridFS模塊


1. GridFS簡介

GridFS是Mongo的一個子模塊,使用GridFS可以基於MongoDB來持久存儲文件。並且支持分布式應用(文件分布存儲和讀取)。作為MongoDB中二進制數據存儲在數據庫中的解決方案,通常用來處理大文件,對於MongoDB的BSON格式的數據(文檔)存儲有尺寸限制,最大為16M。但是在實際系統開發中,上傳的圖片或者文件可能尺寸會很大,此時我們可以借用GridFS來輔助管理這些文件。

GridFS不是MongoDB自身特性,只是一種將大型文件存儲在MongoDB的文件規范,所有官方支持的驅動均實現了GridFS規范。GridFS制定大文件在數據庫中如何處理,通過開發語言驅動來完成、通過API接口來存儲檢索大文件。

2. GridFS使用場景

(1) 如果您的文件系統在一個目錄中存儲的文件的數量有限,你可以使用GridFS存儲盡可能多的文件。

(2) 當你想訪問大型文件的部分信息,卻不想加載整個文件到內存時,您可以使用GridFS存儲文件,並讀取文件部分信息,而不需要加載整個文件到內存。

(3) 當你想讓你的文件和元數據自動同步並部署在多個系統和設施,你可以使用GridFS實現分布式文件存儲。

3. GridFS存儲原理

GridFS使用兩個集合(collection)存儲文件。一個集合是chunks, 用於存儲文件內容的二進制數據;一個集合是files,用於存儲文件的元數據。

GridFS會將兩個集合放在一個普通的buket中,並且這兩個集合使用buket的名字作為前綴。MongoDB的GridFs默認使用fs命名的buket存放兩個文件集合。因此存儲文件的兩個集合分別會命名為集合fs.files ,集合fs.chunks。

當然也可以定義不同的buket名字,甚至在一個數據庫中定義多個bukets,但所有的集合的名字都不得超過mongoDB命名空間的限制。

MongoDB集合的命名包括了數據庫名字與集合名字,會將數據庫名與集合名通過“.”分隔(eg:<database>.<collection>)。而且命名的最大長度不得超過120bytes。

當把一個文件存儲到GridFS時,如果文件大於chunksize (每個chunk塊大小為256KB),會先將文件按照chunk的大小分割成多個chunk塊,最終將chunk塊的信息存儲在fs.chunks集合的多個文檔中。然后將文件信息存儲在fs.files集合的唯一一份文檔中。其中fs.chunks集合中多個文檔中的file_id字段對應fs.files集中文檔”_id”字段。

讀文件時,先根據查詢條件在files集合中找到對應的文檔,同時得到“_id”字段,再根據“_id”在chunks集合中查詢所有“files_id”等於“_id”的文檔。最后根據“n”字段順序讀取chunk的“data”字段數據,還原文件。

4. 存儲過程

fs.files 集合存儲文件的元數據,以類json格式文檔形式存儲。每在GridFS存儲一個文件,則會在fs.files集合中對應生成一個文檔。
fs.files集合中文檔的存儲內容如下:

fs.chunks 集合存儲文件文件內容的二進制數據,以類json格式文檔形式存儲。每在GridFS存儲一個文件,GridFS就會將文件內容按照chunksize大小(chunk容量為256k)分成多個文件塊,然后將文件塊按照類json格式存在.chunks集合中,每個文件塊對應fs.chunk集合中一個文檔。一個存儲文件會對應一到多個chunk文檔。
fs.chunks集合中文檔的存儲內容如下:

為了提高檢索速度 MongoDB為GridFS的兩個集合建立了索引。fs.files集合使用是“filename”與“uploadDate” 字段作為唯一、復合索引。fs.chunk集合使用的是“files_id”與“n”字段作為唯一、復合索引。

5. 注意事項

(1) GridFs不會自動處理md5值相同的文件,也就是說,同一個文件進行兩次put命令,將會在GridFS中對應兩個不同的存儲,對於存儲來說,這是一種浪費。對於md5相同的文件,如果想要在GridFS中只有一個存儲,需要通過API進行擴展處理。

(2) MongoDB 不會釋放已經占用的硬盤空間。即使刪除db中的集合 MongoDB也不會釋放磁盤空間。同樣,如果使用GridFS存儲文件,從GridFS存儲中刪除無用的垃圾文件,MongoDB依然不會釋放磁盤空間的。這會造成磁盤一直在消耗,而無法回收利用的問題。

如何釋放磁盤空間?

(1) 可以通過修復數據庫來回收磁盤空間,即在mongo shell中運行db.repairDatabase()命令或者db.runCommand({ repairDatabase: 1 })命令。(此命令執行比較慢)。

使用通過修復數據庫方法回收磁盤時需要注意,待修復磁盤的剩余空間必須大於等於存儲數據集占用空間加上2G,否則無法完成修復。因此使用GridFS大量存儲文件必須提前考慮設計磁盤回收方案,以解決mongoDB磁盤回收問題。

(2) 使用dump & restore方式,即先刪除mongoDB數據庫中需要清除的數據,然后使用mongodump備份數據庫。備份完成后,刪除MongoDB的數據庫,使用Mongorestore工具恢復備份數據到數據庫。

當使用db.repairDatabase()命令沒有足夠的磁盤剩余空間時,可以采用dump & restore方式回收磁盤資源。如果MongoDB是副本集模式,dump & restore方式可以做到對外持續服務,在不影響MongoDB正常使用下回收磁盤資源。

6. 代碼示例

代碼基於spring boot,主要實現GridFS的基本操作。

(1) application.properties配置如下:

spring.data.mongodb.uri=mongodb://localhost:27017/test

(2) Spring Boot的啟動函數

  1 package com.ws;
  2 
  3 import org.springframework.boot.SpringApplication;
  4 import org.springframework.boot.autoconfigure.SpringBootApplication;
  5 
  6 @SpringBootApplication
  7 public class Application {
  8     public static void main(String[] args) {
  9         SpringApplication.run(Application.class, args);
 10     }
 11 }
View Code

(3) Spring Boot的domain域,主要定義返回標識

  1 package com.ws;
  2 
  3 public class Response {
  4     private String name;
  5 
  6     public Response(String name) {
  7         this.name = name;
  8     }
  9 
 10     public String getName() {
 11         return name;
 12     }
 13 
 14     public void setName(String name) {
 15         this.name = name;
 16     }
 17 
 18 }
View Code

(4) Spring Boot的Controller層,定義接口函數

  1 package com.ws;
  2 
  3 import com.mongodb.BasicDBObject;
  4 import com.mongodb.DBObject;
  5 import com.mongodb.gridfs.GridFSDBFile;
  6 import org.apache.commons.io.IOUtils;
  7 import org.apache.log4j.Logger;
  8 import org.springframework.beans.factory.annotation.Autowired;
  9 import org.springframework.data.mongodb.core.query.Criteria;
 10 import org.springframework.data.mongodb.core.query.Query;
 11 import org.springframework.data.mongodb.gridfs.GridFsTemplate;
 12 import org.springframework.http.MediaType;
 13 import org.springframework.web.bind.annotation.RequestMapping;
 14 import org.springframework.web.bind.annotation.RequestMethod;
 15 import org.springframework.web.bind.annotation.RequestParam;
 16 import org.springframework.web.bind.annotation.RestController;
 17 import org.springframework.web.multipart.MultipartFile;
 18 
 19 import java.io.IOException;
 20 import java.io.InputStream;
 21 import java.util.Date;
 22 import java.util.List;
 23 import java.util.UUID;
 24 
 25 @RestController
 26 @RequestMapping("/api")
 27 public class GridFSApi {
 28     private static Logger LOGGER = Logger.getLogger(GridFSApi.class);
 29     @Autowired
 30     private GridFsTemplate gridFsTemplate;
 31 
 32     @RequestMapping(value = "/save", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE)
 33     public Response save(@RequestParam(value = "file", required = true) MultipartFile file) {
 34 
 35         LOGGER.info("Saving file..");
 36         DBObject metaData = new BasicDBObject();
 37         metaData.put("createdDate", new Date());
 38 
 39         String fileName = UUID.randomUUID().toString();
 40 
 41         LOGGER.info("File Name: " + fileName);
 42 
 43         InputStream inputStream = null;
 44         try {
 45             inputStream = file.getInputStream();
 46             gridFsTemplate.store(inputStream, fileName, "image", metaData);
 47             LOGGER.info("File saved: " + fileName);
 48         } catch (IOException e) {
 49             LOGGER.error("IOException: " + e);
 50             throw new RuntimeException("System Exception while handling request");
 51         }
 52         LOGGER.info("File return: " + fileName);
 53         return new Response(fileName);
 54     }
 55 
 56     @RequestMapping(value = "/get", method = RequestMethod.GET, produces = MediaType.IMAGE_JPEG_VALUE)
 57     public byte[] get(@RequestParam(value = "fileName", required = true) String fileName) throws IOException {
 58         LOGGER.info("Getting file.." + fileName);
 59         List<GridFSDBFile> result = gridFsTemplate
 60                 .find(new Query().addCriteria(Criteria.where("filename").is(fileName)));
 61         if (result == null || result.size() == 0) {
 62             LOGGER.info("File not found" + fileName);
 63             throw new RuntimeException("No file with name: " + fileName);
 64         }
 65         LOGGER.info("File found " + fileName);
 66         return IOUtils.toByteArray(result.get(0).getInputStream());
 67     }
 68 
 69     @RequestMapping(value = "/delete", method = RequestMethod.DELETE)
 70     public void delete(@RequestParam(value = "fileName", required = true) String fileName) {
 71         LOGGER.info("Deleting file.." + fileName);
 72         gridFsTemplate.delete(new Query().addCriteria(Criteria.where("filename").is(fileName)));
 73         LOGGER.info("File deleted " + fileName);
 74     }
 75 }
 76 
View Code


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM