Mongodb 文件存儲 GridFs

轉載自:https://jacoobwang.github.io/2017/12/30/Mongodb%E7%9A%84%E6%96%87%E4%BB%B6%E5%AD%98%E5%82%A8GridFs/

敘述

MongoDB是一種文檔導向的數據庫管理系統,由C++撰寫而成,以此來解決應用程序開發社區中的大量現實問題。2007年10月,MongoDB由10gen團隊所發展。2009年2月首度推出。

MongoDB是一個介於關係數據庫和非關係數據庫之間的產品,是非關係數據庫當中功能最豐富,最像關係數據庫的。它支持的數據結構非常鬆散,是類似json的bson格式,因此可以存儲比較複雜的數據類型。Mongo最大的特點是它支持的查詢語言非常強大,其語法有點類似於面向對象的查詢語言,幾乎可以實現類似關係數據庫單表查詢的絕大部分功能,而且還支持對數據建立索引。

Mongodb中有一個重要功能–GridFS,但並不爲人所熟悉。本文主要講解如何更好地使用GridFS功能,結合實踐中的案例分享經驗。

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

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

應用場景

  • 如果您的文件系統在一個目錄中存儲的文件的數量有限,你可以使用GridFS存儲儘可能多的文件。
  • 當你想訪問大型文件的部分信息,卻不想加載整個文件到內存時,您可以使用GridFS存儲文件,並讀取文件部分信息,而不需要加載整個文件到內存。
  • 當你想讓你的文件和元數據自動同步並部署在多個系統和設施,你可以使用GridFS實現分佈式文件存儲。

原理

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

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

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

MongoDB集合的命名包括了數據庫名字與集合名字,會將數據庫名與集合名通過“.”分隔(eg:.)。而且命名的最大長度不得超過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”字段數據,還原文件。

存儲過程如圖下所示:

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

fs.files集合中文檔的存儲內容如下:

{
  "_id": <ObjectId>,    // 文檔ID,唯一標識
  "chunkSize": <num>,   // chunk大小 256kb
  "uploadDate": <timetamp>, //文件上傳時間 
  "length": <num>,      // 文件長度
  "md5": <string>,      // 文件md5值
  "filename": <string>, // 文件名
  "contentType": <string>,// 文件的MIME類型
  "metadata": <dataObject>// 文件自定義信息
}

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

fs.chunks集合中文檔的存儲內容如下:

{
  "_id": <ObjectId>,    // 文檔ID,唯一標識
  "files_id": <ObjectId>,    // 對應fs.files文檔的ID
  "n": <num>,           // 序號,標識文件的第幾個 chunk
  "data": <binary>      // 文件二級制數據
}

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

如何使用GridFS?

shell 命令之 mongofiles

mongoDB提供mingofiles工具,可以使用命令行來操作GridFS。其實有四個主要命令,分別爲:

Put
#mongofiles -h  -u  -p  --db files put /conn.log
connected to: 127.0.0.1
added file: { _id: ObjectId('530cf1009710ca8fd47d7d5d'), filename: "./conn.log", chunkSize: 262144, uploadDate: new Date(1393357057021), md5: "6515e95f8bb161f6435b130a0e587ccd", length: 1644981 }
done!

Get
#mongofiles -h  -u  -p  --db files get /conn.log
connected to: 127.0.0.1
done write to: ./conn.log

List
# mongofiles -h  -u  -p  list
connected to: 127.0.0.1
/conn.log 1644981

Delete
[root@ip-10-198-25-43 tmp]# mongofiles -h  -u  -p  --db files delete /conn.log
connected to: 127.0.0.1
done!

這些命令都是按照filename操作GridFS中存儲的文件的。

API

MongoDB支持多種編程語言驅動。比如c、java、C#、nodeJs等。因此可以使用這些語言MongoDB驅動API操作,擴展GridFS。

以下是一個nodejs版本的代碼:

const mongoose = require('mongoose')
const fs = require('fs')
const Promise = require('bluebird')
const { isString } = require('lodash')
const ObjectId = mongoose.Types.ObjectId

let bucket
let db

function init (_db) {
  db = _db
  bucket = new mongoose.mongo.GridFSBucket(db)
}

async function uploadFiles (files, options) {
  return Promise.map(files, file =>  // eslint-disable-line
    uploadFile(file.path, file.key, options), { concurrency: 3 })
}

async function uploadFile (filePath, fileName, options) {
  return new Promise((resolve, reject) => {
    let openUploadStream = bucket.openUploadStream(fileName)
    fs.createReadStream(filePath)
      .pipe(openUploadStream)
      .on('error', function (error) {
        if (options && options.deleteIfError) {
          deleteFileById(openUploadStream.id)
          fs.unlink(filePath)
        }
        reject(error)
      })
      .on('finish', function (result) {
        resolve(result)
      })
  })
}

function findFileById (id) {
  return new Promise((resolve, reject) => {
    if (isString(id)) {
      id = ObjectId(id)
    }
    db.collection('fs.files').findOne({ _id: id }, function (err, result) {
      if (err) return reject(err)
      resolve(result)
    })
  })
}

function deleteFileById (id) {
  return new Promise((resolve, reject) => {
    if (isString(id)) {
      id = ObjectId(id)
    }
    bucket.delete(id, function (err) {
      resolve(!err)
    })
  })
}

function getStreamById (id) {
  if (isString(id)) {
    id = ObjectId(id)
  }
  return bucket.openDownloadStream(id)
}

module.exports = {
  init,
  uploadFiles,
  uploadFile,
  findFileById,
  deleteFileById,
  getStreamById,
}

總結

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

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正常使用下回收磁盤資源。

MogonDB使用副本集, 實踐使用dump & restore方式,回收磁盤資源。70G的數據在2小時之內完成數據清理及磁盤迴收,並且整個過程不影響MongoDB對外服務,同時可以保證處理過程中數據庫增量數據的完整。

本文參考鏈接:1.http://rdc.hundsun.com/portal/article/703.html

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章