惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

爱范儿
爱范儿
Security Latest
Security Latest
NISL@THU
NISL@THU
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
C
Cybersecurity and Infrastructure Security Agency CISA
Cloudbric
Cloudbric
T
Threat Research - Cisco Blogs
大猫的无限游戏
大猫的无限游戏
C
CXSECURITY Database RSS Feed - CXSecurity.com
阮一峰的网络日志
阮一峰的网络日志
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
雷峰网
雷峰网
C
Cisco Blogs
V
Vulnerabilities – Threatpost
S
Security Archives - TechRepublic
V
Visual Studio Blog
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
J
Java Code Geeks
D
Darknet – Hacking Tools, Hacker News & Cyber Security
Know Your Adversary
Know Your Adversary
博客园 - 叶小钗
腾讯CDC
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
P
Privacy International News Feed
P
Palo Alto Networks Blog
博客园_首页
V
V2EX
WordPress大学
WordPress大学
Schneier on Security
Schneier on Security
月光博客
月光博客
博客园 - 司徒正美
Google DeepMind News
Google DeepMind News
TaoSecurity Blog
TaoSecurity Blog
博客园 - 聂微东
酷 壳 – CoolShell
酷 壳 – CoolShell
人人都是产品经理
人人都是产品经理
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
博客园 - 【当耐特】
The Cloudflare Blog
罗磊的独立博客
美团技术团队
N
News | PayPal Newsroom
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
Last Week in AI
Last Week in AI
K
Kaspersky official blog
Google Online Security Blog
Google Online Security Blog
S
SegmentFault 最新的问题
Application and Cybersecurity Blog
Application and Cybersecurity Blog
T
Tailwind CSS Blog

又见苍岚

COLMAP PatchMatch Stereo 算法详解 事件驱动的状态机框架:从理论到工程实践 Git 在国内网络环境下无法 Push 的排查与修复 —— 配置 Clash 代理 分段五次多项式插值原理详解 路径插值方法深度对比研究 Claude Code 使用指南 OpenClaw 记忆管理与技能创建指南 CBS(Conflict-Based Search)算法详解 A* 算法及其变种详解 OpenClaw 配置多 Agents Windows Powershell 无法加载文件,因为在此系统上禁止运行脚本问题的解决方案 MaxClaw 安装流程 大模型 AI 名词介绍 AList 网盘聚合工具简介 Protobuf 简介与测试 Claude Code 简介以及 GLM 4.7 模型接入 Github 歌词下载工具 163MusicLyrics Python __getattr__ 懒加载 Python TypedDict 机器人仿真平台 Gazebo 安装记录 机器人仿真平台 Gazebo 简介 多机器人路径规划问题(Multi-Agent Path Finding, MAPF)简介 Python exifread 读取修改过的 jpeg 信息错误问题修复 3D 坐标系变换的理解 3D 旋转矩阵基本概念 MongoDB Compass 介绍 Python 环境管理工具 uv Flutter 开发指南 Snipaste 安装下载与黑屏问题解决方案 全局路径规划算法记录 2025 Python 版本性能测试 Flutter Hello World Flutter 安装环境配置 Ubuntu VMware 硬盘扩容后 SMBus Host controller not enabled 报错问题解决 Python NetworkX 教程 Docker GPU 报错 - Failed to initialize NVML Unknown Error 解决方案 Python matplotlib 图表绘制 cuda-toolkit 安装替代 Cuda 与 Cudnn Jinja2 Python 利用 docxtpl 和 Jinja2 生成基于模板的 Word 文档 Docker 实现 CPU 核心隔离 LoFTR 基于 Transformer 的特征提取匹配算法 OmniGlue 特征匹配 SuperGlue 使用图神经网络学习特征匹配 Ubuntu 下将 xlsx 文件按照 sheet 转换为 图片 Python 使用 SQLAlchemy Python FastAPI 教程 openwrt 软路由配置安装 Nav2 地图文件(PGM/YAML)规范标准 3D OBJ 模型转换为 glb 瓦片格式 Python 源码 Redis 数据库介绍 Ubuntu 22.04 内核自动升级导致 MongoDB 7.0.12 错误记录 ubuntu 20.04 安装 ROS Noetic ubuntu 18.04 安装 ROS Melodic VMware Workstation Pro 个人免费版下载、安装、使用指南 Hybrid A-star 路径规划 Reeds-Shepp 曲线 Dubins 曲线 Linux kvm 虚拟机网络不通的问题解决方法 Ubuntu 自动内存清理 BiliBili 缓存视频转 mp4 Python 求解线性规划 3D Gaussian Splatting 官方源码实践记录 ImageMagick 教程 Ubuntu 22.04 安装 Colmap 对数几率 odds Ubuntu nmcli 网络管理工具使用指南 SuperPoint 自监督深度学习特征点提取 SyncTV Music Tag Web 在线音乐信息整理工具 ncm 格式转 mp3 MusicBrainz 音乐元数据百科数据库 Ubuntu 网络流量监控工具 私人云音乐平台 Navidrome 入门 手眼标定 四元数(Quaternions) OHTTPS 实现免费自动 https 证书申请、更新、部署 ubuntu 22.04 安装 CloudCompare 单机 KVM 虚拟机冷迁移 Ubuntu 22.04 使用 mdadm 实现软 raid 小鱼 一键安装 ROS-humble Fluid -46- 基于 Simpletex API 构建公式识别页面 公式识别 API 简介 -- Simpletex 使用 Python web 部署库 waitress 3D Gaussian Splatting for Real-Time Radiance Field Rendering Ubuntu Swap 简介与空间扩展 Ubuntu 24.04 安装 forticlient Clash Verge 使用 MongoDB 7.0.17 集群 Docker 构建源码 Error code - 2013. Lost connection to MySQL server during query 问题解决 Python 日志记录库 loguru 使用指北 Python 实现 Web 日志查看服务 MySQL LOAD DATA LOCAL INFILE 极速数据加载 Image size exceeds limit of 89478485 pixels 解决方案 Docker 使用 NVIDIA GPU 驱动错误解决 阿里云 docker 镜像仓库 Ubuntu中没有wired connected的解决方案 MinIO 简介 subconverter 代理订阅格式转换 修复 node –openssl-legacy-provider is not allowed in NODE_OPTIONS 错误
MongoDB GridFS
Yiwei Zhang · 2024-08-13 · via 又见苍岚

GridFS 是MongoDB 的一个子模块,使用 GridFS 可以基于 MongoDB 来持久存储文件,并且支持分布式应用(文件分布存储和读取)。本文介绍相关内容。

背景

MongoDB 是一个介于关系数据库和非关系数据库之间的产品,是非关系数据库当中功能最丰富,最像关系数据库的。它支持的数据结构非常松散,是类似 json 的 bson 格式,因此可以存储比较复杂的数据类型。

MongoDB 最大的特点是它支持的查询语言非常强大,其语法有点类似于面向对象的查询语言,几乎可以实现类似关系数据库单表查询的绝大部分功能,而且还支持对数据建立索引。

MongoDB 提供了高性能、高可用、支持分片及面向文档等特性,是 Nodejs 应用程序最受欢迎的非关系型数据之一。

但是 MongoDB 以 BSON 格式存储数据,其最多只能存储16MB 的数据。这样做的原因是为了避免单个文档占用太多 RAM 或在事务期间过度使用带宽。

这就是 GridFS 可以大显身手的地方。为了存储大于16MB 的数据,GridFSAPI 将数据划分为更小的大小,称为块。在检索时,可以组合数据块以获得相同的数据。每个块都是该数据文件部分的二进制表示形式。

简介

GridFS 是MongoDB 的一个子模块,使用 GridFS 可以基于 MongoDB 来持久存储文件,并且支持分布式应用(文件分布存储和读取)。

GridFS 是 MongoDB 内置的功能,用于存储和检索大于 BSON 文档大小限制的文件,如图片和视频。

GridFS是MongoDB提供的二进制数据存储在数据库中的解决方案,对于 MongoDB 的BSON 格式的数据(文档)存储有尺寸限制,最大为 16M。但是在实际系统开发中,上传的图片或者文件可能尺寸会很大,此时我们可以借用GridFS 来管理这些文件。

  • GridFS 常用的使用场景

如果你的文件系统在一个目录中存储的文件的数量有限,你可以使用 GridFS存储尽可能多的文件。

当你想访问大型文件的部分信息,却不想加载整个文件到内存时,您可以使用GridFS 存储文件,并读取文件部分信息,而不需要加载整个文件到内存。

当你想让你的文件和元数据自动同步并部署在多个系统和设施,你可以使用GridFS 实现分布式文件存储。

存储原理

GridFS 使用两个集合(collection)存储文件。一个集合是 chunks, 用于存储文件内容的二进制数据;一个集合是 files,用于存储文件的元数据。

GridFS 会将两个集合放在一个普通的 buket 中,并且这两个集合使用 buket 的名字作为前缀。MongoDB 的 GridFs 默认使用 fs 命名的 buket 存放两个文件集合。因此存储文件的两个集合分别会命名为集合 fs.files ,集合 fs.chunks。

当然也可以定义不同的 buket 名字,甚至在一个数据库中定义多个 bukets,但所有的集合的名字都不得超过 MongoDB 命名空间的限制。

MongoDB 集合的命名包括了数据库名字与集合名字,会将数据库名与集合名通过“.”分隔,而且命名的最大长度不得超过 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 集合中文档的存储内容如下:

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

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

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

1
2
3
4
5
6
{
"_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 提供 mongofiles 工具,可以使用命令行来操作 GridFS。主要有四个命令:

Put

1
2
3
#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: newDate(1393357057021), md5: "6515e95f8bb161f6435b130a0e587ccd", length:1644981 }

Get

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

List

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

Delete

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

MongoDB API

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

磁盘空间优化

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

MongoDB 使用副本集, 实践使用 dump & restore 方式,回收磁盘资源。70G 的数据在 2 小时之内完成数据清理及磁盘回收,并且整个过程不影响 MongoDB 对外服务,同时可以保证处理过程中数据库增量数据的完整。

注意

GridFs 并非银弹,它还是有一些局限性:

  1. 存储规模,如果你的存储量是不断增加的,或者你预估的规模是比较大的话,还是建议存储到文件服务器上。
  2. 原子更新,GridFs 没有提供对文件的原子更新方式。

Python 调用

需要安装 pymongo 库

1
pip install pymongo

创建 GridFS 对象

1
2
3
4
5
6
7
8
9
10
from pymongo import MongoClient
from gridfs import GridFS

if __name__ == '__main__':
#建立MongoDB数据库连接
conn = MongoClient(host='127.0.0.1',port=27017)
mydb = conn['test']

fs = GridFS(mydb)

保存数据

在创建 GridFS 对象后可以用 fs 对象插入数据

1
2
3
4
5
6
7
8
9
10
image_path = 'test.jpg'
with open(image_path, 'rb') as file:
custom_id = 'my_img_id'
# 检查自定义ID是否已经存在
if not fs.exists(_id=custom_id):
fs.put(file.read(), _id=custom_id, filename=image_file_name, content_type='image/jpeg')
print(f"Image '{image_file_name}' saved with custom id: {custom_id}")
else:
print(f"Image '{image_file_name}' with custom id: {custom_id} already exists.")

读取数据

我们将刚刚保存的数据从数据库中读取出来并显示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
from PIL import Image
from io import BytesIO
from pymongo import MongoClient
from gridfs import GridFS

# MongoDB连接设置
mongo_host = 'your_mongodb_host'
mongo_port = 27017 # 根据实际情况修改端口
mongo_db_name = 'image_database'
mongo_user = 'your_username'
mongo_password = 'your_password'

# 连接到MongoDB
client = MongoClient(mongo_host, mongo_port, username=mongo_user, password=mongo_password)
db = client[mongo_db_name]
fs = GridFS(db)

# 自定义ID,即你之前保存图像时使用的ID
custom_id = 'your_custom_image_id_here'

# 从GridFS读取图像
if fs.exists(_id=custom_id):
file = fs.get(custom_id)
image = Image.open(BytesIO(file.read()))
image.show()
print(f"Image with custom id: {custom_id} displayed.")
else:
print(f"No image found with custom id: {custom_id}")

# 关闭数据库连接
client.close()

列出数据

要列出GridFS中所有图像的ID,可以查询fs.files集合,这个集合存储了GridFS中文件的元数据,包括文件的ID。以下是如何使用Python和pymongo库来执行此操作的代码示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
from pymongo import MongoClient
from gridfs import GridFS

# MongoDB连接设置
mongo_host = 'your_mongodb_host'
mongo_port = 27017 # 根据实际情况修改端口
mongo_db_name = 'image_database'
mongo_user = 'your_username'
mongo_password = 'your_password'

# 连接到MongoDB
client = MongoClient(mongo_host, mongo_port, username=mongo_user, password=mongo_password)
db = client[mongo_db_name]
fs = GridFS(db)

# 列出GridFS中所有图像的ID
try:
files = fs.find()
for file in files:
print(f"File ID: {file._id}")
except Exception as e:
print(f"An error occurred: {e}")

# 关闭数据库连接
client.close()

在这段代码中,fs.find()方法会返回一个迭代器,包含了fs.files集合中的所有文档。每个文档都是一个GridFS文件记录,包含文件的元数据,其中_id字段是文件的唯一标识符。

条件查询

可以添加一个查询过滤条件

1
2
3
4
5
# 列出GridFS中所有JPEG图像的ID
files = fs.find({'contentType': 'image/jpeg'})
for file in files:
print(f"JPEG Image ID: {file._id}")

这里,{'contentType': 'image/jpeg'}是一个查询过滤条件,它将结果限制为内容类型为JPEG图像的文件。

也可以设置其他过滤条件

基于上传日期的查询
1
2
3
4
5
6
7
8
from datetime import datetime

# 查询上传日期在特定日期之后的文件
upload_date = datetime(2023, 1, 1)
files = fs.find({'uploadDate': {'$gte': upload_date}})

for file in files:
print(f"File ID: {file._id}, Filename: {file.filename}, Upload Date: {file.uploadDate}")

基于文件大小的查询
1
2
3
4
5
6
# 查询文件大小大于特定值的文件
min_file_size = 1024 * 1024 * 5 # 5MB
files = fs.find({'length': {'$gte': min_file_size}})

for file in files:
print(f"File ID: {file._id}, Filename: {file.filename}, Size: {file.length} bytes")

删除文件

1
fs.delete(file_id)

要删除单个文件,你需要知道该文件的ID。以下是删除指定ID文件的代码示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
from pymongo import MongoClient
from gridfs import GridFS

# MongoDB连接设置
mongo_host = 'your_mongodb_host'
mongo_port = 27017 # 根据实际情况修改端口
mongo_db_name = 'image_database'
mongo_user = 'your_username'
mongo_password = 'your_password'

# 连接到MongoDB
client = MongoClient(mongo_host, mongo_port, username=mongo_user, password=mongo_password)
db = client[mongo_db_name]
fs = GridFS(db)

# 要删除的文件的ID
file_id_to_delete = 'your_file_id_here'

# 删除文件
if fs.exists(file_id_to_delete):
fs.delete(file_id_to_delete)
print(f"File with ID {file_id_to_delete} has been deleted.")
else:
print(f"No file found with ID {file_id_to_delete}.")

# 关闭数据库连接
client.close()

如果你想根据某些条件删除多个文件,可以使用以下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
# 删除条件,例如删除所有内容类型为'image/jpeg'的文件
delete_condition = {'contentType': 'image/jpeg'}

# 查询符合条件的文件
files_to_delete = fs.find(delete_condition)

# 删除每个找到的文件
for file in files_to_delete:
fs.delete(file._id)
print(f"File with ID {file._id} has been deleted.")

# 关闭数据库连接
client.close()

这段代码会删除所有内容类型为image/jpeg的文件。

注意事项

  • 删除操作是不可逆的,一旦执行,文件和相关的块数据将被永久删除。
  • 在执行删除操作之前,请确保你有适当的权限,并且你确实想要删除这些数据。
  • 如果你在删除过程中遇到问题,可能需要检查MongoDB服务器的日志来获取更多信息。

参考资料

文章链接:
https://www.zywvvd.com/notes/coding/dataset/mongodb-gridfs/mongodb-gridfs/