Mongodb 文件存储 GridFs

本是古典 何须时尚 2023-02-21 06:42 153阅读 0赞

转载自: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”字段数据,还原文件。

存储过程如图下所示:

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2xpemhlbmd5dTg5MTIzMQ_size_16_color_FFFFFF_t_70

fs.files 集合存储文件的元数据,以类json格式文档形式存储。每在GridFS存储一个文件,则会在fs.files集合中对应生成一个文档。

fs.files集合中文档的存储内容如下:

  1. {
  2. "_id": <ObjectId>, // 文档ID,唯一标识
  3. "chunkSize": <num>, // chunk大小 256kb
  4. "uploadDate": <timetamp>, //文件上传时间
  5. "length": <num>, // 文件长度
  6. "md5": <string>, // 文件md5值
  7. "filename": <string>, // 文件名
  8. "contentType": <string>,// 文件的MIME类型
  9. "metadata": <dataObject>// 文件自定义信息
  10. }

fs.chunks 集合存储文件文件内容的二进制数据,以类json格式文档形式存储。每在GridFS存储一个文件,GridFS就会将文件内容按照chunksize大小(chunk容量为256k)分成多个文件块,然后将文件块按照类json格式存在.chunks集合中,每个文件块对应fs.chunk集合中一个文档。一个存储文件会对应一到多个chunk文档。

fs.chunks集合中文档的存储内容如下:

  1. {
  2. "_id": <ObjectId>, // 文档ID,唯一标识
  3. "files_id": <ObjectId>, // 对应fs.files文档的ID
  4. "n": <num>, // 序号,标识文件的第几个 chunk
  5. "data": <binary> // 文件二级制数据
  6. }

为了提高检索速度 MongoDB为GridFS的两个集合建立了索引。fs.files集合使用是“filename”与“uploadDate” 字段作为唯一、复合索引。fs.chunk集合使用的是“files_id”与“n”字段作为唯一、复合索引。

如何使用GridFS?

shell 命令之 mongofiles

mongoDB提供mingofiles工具,可以使用命令行来操作GridFS。其实有四个主要命令,分别为:

  1. Put
  2. #mongofiles -h -u -p --db files put /conn.log
  3. connected to: 127.0.0.1
  4. added file: { _id: ObjectId('530cf1009710ca8fd47d7d5d'), filename: "./conn.log", chunkSize: 262144, uploadDate: new Date(1393357057021), md5: "6515e95f8bb161f6435b130a0e587ccd", length: 1644981 }
  5. done!
  6. Get
  7. #mongofiles -h -u -p --db files get /conn.log
  8. connected to: 127.0.0.1
  9. done write to: ./conn.log
  10. List
  11. # mongofiles -h -u -p list
  12. connected to: 127.0.0.1
  13. /conn.log 1644981
  14. Delete
  15. [root@ip-10-198-25-43 tmp]# mongofiles -h -u -p --db files delete /conn.log
  16. connected to: 127.0.0.1
  17. done!

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

API

MongoDB支持多种编程语言驱动。比如c、java、C#、nodeJs等。因此可以使用这些语言MongoDB驱动API操作,扩展GridFS。

以下是一个nodejs版本的代码:

  1. const mongoose = require('mongoose')
  2. const fs = require('fs')
  3. const Promise = require('bluebird')
  4. const { isString } = require('lodash')
  5. const ObjectId = mongoose.Types.ObjectId
  6. let bucket
  7. let db
  8. function init (_db) {
  9. db = _db
  10. bucket = new mongoose.mongo.GridFSBucket(db)
  11. }
  12. async function uploadFiles (files, options) {
  13. return Promise.map(files, file => // eslint-disable-line
  14. uploadFile(file.path, file.key, options), { concurrency: 3 })
  15. }
  16. async function uploadFile (filePath, fileName, options) {
  17. return new Promise((resolve, reject) => {
  18. let openUploadStream = bucket.openUploadStream(fileName)
  19. fs.createReadStream(filePath)
  20. .pipe(openUploadStream)
  21. .on('error', function (error) {
  22. if (options && options.deleteIfError) {
  23. deleteFileById(openUploadStream.id)
  24. fs.unlink(filePath)
  25. }
  26. reject(error)
  27. })
  28. .on('finish', function (result) {
  29. resolve(result)
  30. })
  31. })
  32. }
  33. function findFileById (id) {
  34. return new Promise((resolve, reject) => {
  35. if (isString(id)) {
  36. id = ObjectId(id)
  37. }
  38. db.collection('fs.files').findOne({ _id: id }, function (err, result) {
  39. if (err) return reject(err)
  40. resolve(result)
  41. })
  42. })
  43. }
  44. function deleteFileById (id) {
  45. return new Promise((resolve, reject) => {
  46. if (isString(id)) {
  47. id = ObjectId(id)
  48. }
  49. bucket.delete(id, function (err) {
  50. resolve(!err)
  51. })
  52. })
  53. }
  54. function getStreamById (id) {
  55. if (isString(id)) {
  56. id = ObjectId(id)
  57. }
  58. return bucket.openDownloadStream(id)
  59. }
  60. module.exports = {
  61. init,
  62. uploadFiles,
  63. uploadFile,
  64. findFileById,
  65. deleteFileById,
  66. getStreamById,
  67. }

总结

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

发表评论

表情:
评论列表 (有 0 条评论,153人围观)

还没有评论,来说两句吧...

相关阅读

    相关 GridFS持久化文件MongoDB

    GridFS是MongoDB提供的用于持久化存储文件的模块。GridFS存储文件是将文件分块存储,文件会按照256KB的大小分割成多个块进行存储,GridFS使用两个集合(co

    相关 MongoDB——GridFS

    上篇文章提到MongoDB内置GridFS,支持海量存储。那么GridFS具体是如何存储的呢?有何特殊之处呢? 在实际系统开发中,经常会有上传图片或文件的功能,这些文件可能尺