553 lines
19 KiB
Markdown
553 lines
19 KiB
Markdown
---
|
||
title: MongoDB GridFS文件存储
|
||
date: 2021-01-28
|
||
author: ac
|
||
tags:
|
||
- MongoDB
|
||
- GridFS
|
||
- mongofiles
|
||
categories:
|
||
- Database
|
||
---
|
||
|
||
> GIS中遥感影像数据可以使用MongoDB的GridFS进行存储。
|
||
|
||
<!-- more -->
|
||
|
||
### 1 `GridFS` 简介
|
||
|
||
`GridFS`文件系统是`MongoDB`存储大文件的一种规范,所有官方的`MongoDB`驱动都遵循该规范。它解决了`BSON`文档大小不能超过**16M**的问题。
|
||
|
||
`GridFS`不是将文件存储在单个文档中,而是将文件分成多个数据块,并将每个块存储为单独的文档。默认情况下,`GridFS`使用的默认数据块大小为255 kB;也就是说,除了最后一个数据块外,`GridFS`将一个文件划分为255 kB的小块。
|
||
|
||
`GridFS`使用两个集合来存储文件。一个集合存储文件的数据块,另一个集合存储文件元数据,像文件名称、大小、创建日期等。
|
||
|
||
当您在使用`GridFS`查询文件时,会根据需要重新组装这些数据块。另外`GridFS`不仅可以存储操过16MB的文件,还可以访问大文件的部分信息而不必将整个文件加载到内存中。
|
||
|
||
使用`GridFS`的方式有以下两种:
|
||
|
||
- 使用[程序接口](https://docs.mongodb.com/drivers/)的方式,利用`MongoDB`对各程序语言的提供的驱动对文件进行操作。
|
||
|
||

|
||
|
||
- 使用`mongofiles`命令行工具。
|
||
|
||
|
||
|
||
### 2 `GridFS` 集合
|
||
|
||
` GridFS`将文件存储在两个集合中:
|
||
|
||
- `chunks`:存储二进制块
|
||
- `files`:存储文件的元数据
|
||
|
||
|
||
|
||
`GridFS`通过在每个集合前面加上前缀,将这两个集合放在一个公共容器:bucket(命名空间)中。默认情况下,`GridFS`使用两个集合,一个容器名为`fs`:
|
||
|
||
- `fs.files`
|
||
- `fs.chunks`
|
||
|
||
> 官方的英语文档描述的更加形象:The two collections are in a common bucket and the collection names are prefixed with the bucket name.
|
||
|
||
#### 2.1 chunks集合
|
||
|
||
```json
|
||
{
|
||
"_id" : <ObjectId>,
|
||
"files_id" : <ObjectId>,
|
||
"n" : <num>,
|
||
"data" : <binary>
|
||
}
|
||
```
|
||
|
||
> `ObjectId`: 一种特殊的BSON类型,它保证集合内的唯一性。
|
||
>
|
||
> `ObjectId`值的长度为12个字节,包括: 4 byte 时间戳、5 byte 随机数 、3 byte 增长量
|
||
|
||
- chunks._id:数据块的标识
|
||
- chunks.files_id:所属文件的标识
|
||
- chunks.n: 块的序列号。`GridFS`给所有块编号,从0开始。
|
||
- chunks.data :数据块中装载的数据,Binary类型。
|
||
|
||
#### 2.2 files集合
|
||
|
||
```json
|
||
{
|
||
"_id" : <ObjectId>,
|
||
"length" : <num>,
|
||
"chunkSize" : <num>,
|
||
"uploadDate" : <timestamp>,
|
||
"md5" : <hash>,
|
||
"filename" : <string>,
|
||
"contentType" : <string>,
|
||
"aliases" : <string array>,
|
||
"metadata" : <any>,
|
||
}
|
||
```
|
||
|
||
- files._id:文件标识
|
||
- files.length:文件长度
|
||
- files.chunkSize: 每个块的大小(以字节为单位)。GridFS将文档分成大小为chunkSize的块,但最后一个块的大小仅根据需要而定。默认大小是255kb。
|
||
- files. uploadDate : 文件第一次被GridFS存储的日期。该值具有日期类型。
|
||
- files.md5: MD5算法被FIPS 140-2禁止。MongoDB驱动程序已弃用MD5支持,并将在未来版本中删除MD5生成。需要文件摘要的应用程序应该在GridFS之外实现它,并存储在files.metadata中。
|
||
- files.filename: 可读的GridFS文件名称。
|
||
- files.metadata: 可选的。元数据字段可以是任何数据类型,可以保存您想要存储的任何附加信息。
|
||
|
||
|
||
|
||
### 3 `mongofiles`工具
|
||
|
||
#### 3.1 `mongofiles`作用
|
||
|
||
`mongofiles`工具可以通过命令行操作`MongoDB`实例中`GridFS`对象中存储的文件。它提供了存储在文件系统和`GridFS`中的对象之间的接口。
|
||
|
||
> 从`MongoDB`4.4 开始,`mongofiles`从`MongoDB`服务器单独发布,并使用自己的版本控制,初始版本为100.0.0。在此之前,`mongofiles`是与`MongoDB`一起发布的,并使用了匹配的版本控制。
|
||
|
||
|
||
|
||
#### 3.2 安装 `mongofiles`
|
||
|
||
[windows环境](https://docs.mongodb.com/database-tools/installation/installation-windows/)
|
||
|
||
根据之前安装`MongoDB`实例,选择下载工具的版本。之前`MongoDB`使用的是`zip`免安装的形式,所以这里下载`mongodb-database-tools-windows-x86_64-100.2.zip`。
|
||
|
||

|
||
|
||

|
||
|
||

|
||
|
||

|
||
|
||

|
||
|
||
将下载的文件解压到任意位置,再将`mongofiles`工具目录下的`bin`目录添加到系统的`Path`环境变量中。
|
||
|
||
|
||
|
||
[Linux环境(ubuntu)](https://docs.mongodb.com/database-tools/installation/installation-linux/)
|
||
|
||
```shell
|
||
qiusj@u20:~$ cat /etc/os-release
|
||
NAME="Pop!_OS"
|
||
VERSION="20.04 LTS"
|
||
ID=pop
|
||
ID_LIKE="ubuntu debian"
|
||
PRETTY_NAME="Pop!_OS 20.04 LTS"
|
||
VERSION_ID="20.04"
|
||
HOME_URL="https://pop.system76.com"
|
||
SUPPORT_URL="https://support.system76.com"
|
||
BUG_REPORT_URL="https://github.com/pop-os/pop/issues"
|
||
PRIVACY_POLICY_URL="https://system76.com/privacy"
|
||
VERSION_CODENAME=focal
|
||
UBUNTU_CODENAME=focal
|
||
LOGO=distributor-logo-pop-os
|
||
#查看Linux内核版本
|
||
qiusj@u20:~$ arch
|
||
x86_64
|
||
```
|
||
|
||
下载合适的版本,可以下载后使用`sftp`传输到Linux,也可以使用`curl`命令下载:
|
||
|
||

|
||
|
||
```shell
|
||
$ curl -o mongodb-database-tools.tgz https://fastdl.mongodb.org/tools/db/mongodb-database-tools-ubuntu2004-x86_64-100.2.1.tgz
|
||
% Total % Received % Xferd Average Speed Time Time Time Current
|
||
Dload Upload Total Spent Left Speed
|
||
100 64.0M 100 64.0M 0 0 1222k 0 0:00:53 0:00:53 --:--:-- 2106k
|
||
|
||
$ ll
|
||
总用量 128560
|
||
drwxr-xr-x 5 qiusj intplanet 4096 1月 28 17:00 ./
|
||
drwxr-xr-x 6 qiusj intplanet 4096 1月 5 09:42 ../
|
||
-rw-r--r-- 1 qiusj intplanet 67211368 1月 28 17:00 mongodb-database-tools.tgz
|
||
|
||
#解压
|
||
$ tar -zxvf mongodb-database-tools.tgz
|
||
mongodb-database-tools-ubuntu2004-x86_64-100.2.1/LICENSE.md
|
||
mongodb-database-tools-ubuntu2004-x86_64-100.2.1/README.md
|
||
mongodb-database-tools-ubuntu2004-x86_64-100.2.1/THIRD-PARTY-NOTICES
|
||
mongodb-database-tools-ubuntu2004-x86_64-100.2.1/bin/bsondump
|
||
mongodb-database-tools-ubuntu2004-x86_64-100.2.1/bin/mongodump
|
||
mongodb-database-tools-ubuntu2004-x86_64-100.2.1/bin/mongoexport
|
||
mongodb-database-tools-ubuntu2004-x86_64-100.2.1/bin/mongofiles
|
||
mongodb-database-tools-ubuntu2004-x86_64-100.2.1/bin/mongoimport
|
||
mongodb-database-tools-ubuntu2004-x86_64-100.2.1/bin/mongorestore
|
||
mongodb-database-tools-ubuntu2004-x86_64-100.2.1/bin/mongostat
|
||
mongodb-database-tools-ubuntu2004-x86_64-100.2.1/bin/mongotop
|
||
|
||
#将解压的文件bin目录下的命令复制到/usr/local/bin/目录下
|
||
$ cd mongodb-database-tools-ubuntu2004-x86_64-100.2.1/bin
|
||
$ sudo cp * /usr/local/bin
|
||
$ mongofiles --version
|
||
mongofiles version: 100.2.1
|
||
git version: 0cb6f592fcb425ee5b3d5540341de75531d28dac
|
||
Go version: go1.12.17
|
||
os: linux
|
||
arch: amd64
|
||
compiler: gc
|
||
```
|
||
|
||
|
||
|
||
#### 3.3 基本使用
|
||
|
||
`mongofiles`命令的语法格式如下:
|
||
|
||
```shell
|
||
mongofiles <options> <connection-string> <command> <filename or _id>
|
||
```
|
||
|
||
- `options`主要配置`mongofiles`的一些读写优先级。
|
||
- `connection-string`是连接`mongod/mongos`的配合信息,如host、port、安全认证等相关配置。
|
||
- `command`是`mongofiles`具体的文件操作,如导入、导出、查询等。
|
||
|
||
> 对于副本集,`mongofiles`只能从主节点读。
|
||
>
|
||
> 当启动安全认证后,`mongofiles`连接的用户需要有`read`权限(`list`、`earch`、`get`等命令)和`readWrite`权限(`put`、`delete`等命令)
|
||
|
||
**常用Options配置项:**
|
||
|
||
| options | description |
|
||
| --------- | ------------------------------------------------------------ |
|
||
| --help | 返回 mongofiles 的配置项和使用信息 |
|
||
| --version | 返回 mongofiles 的版本信息 |
|
||
| --uri | 指定MongoDB 的连接信息,如:<br>--uri="mongodb://[username:password@]host1\[:port1]\[,host2[:port2],...\[,hostN\[:portN]]][/\[database][?options]]" |
|
||
| --config | 指定配置文件,以配置文件的形式启动 |
|
||
|
||
|
||
|
||
**Commands: **
|
||
|
||
| 命令 | 说明 |
|
||
| -------------------------------------------------- | ------------------------------------------------------------ |
|
||
| list \<prefix> | 列出`GridFS`存储的文件。\<prefix>用于筛选返回列表中以 prefix字符串为前缀的文件。 |
|
||
| search \<string> | 列出`GridFS`中名称与\<string>的任何部分匹配的文件。 |
|
||
| put \<filename1[ filename2] ...> | 将指定的文件从本地文件系统复制到`GridFS`中。多个文件可以指定为空格分隔的列表。 |
|
||
| get \<filename1[ filename2] ...> | 将指定的文件从`GridFS`存储复制到本地文件系统。 |
|
||
| get_id "<_id>" | 将文件(由\<\_id>指定)从`GridFS`中复制到本地文件系统。\<\_id>是该对象在`GridFS`中的扩展JSON \_id。 |
|
||
| get_regex \<regex> --regexOptions \<regex-options> | 将匹配指定的\<regex>表达式的一个或多个文件从`GridFS`复制到本地文件系统。get_regex命令使用Perl兼容的正则表达式(“PCRE”)8.42版本,支持UTF-8。 |
|
||
| delete \<filename> | 从`GridFS`中删除指定的文件 |
|
||
| delete_id "<_id>" | 从`GridFS`中删除由\<\_id>指定的文件。 |
|
||
|
||
#### 3.4 示例
|
||
|
||
在系统的命令行中运行 `mongofiles`命令:
|
||
|
||
```shell
|
||
# 查看本地mongofiles实例执行过的历史命令
|
||
mongofiles -d=records list
|
||
```
|
||
|
||
导入文件,下面命令`mongofiles` 实例将连接 `uri`中的数据库,将文件存入`GridFS`中。
|
||
|
||
```shell
|
||
$ mongofiles --uri=mongodb://192.168.0.152:27017 put /home/qiusj/.local/mongodb/mongofiles/data/shenzhen_CholophyII_2020.tif
|
||
2021-03-04T17:18:06.681+0800 connected to: mongodb://192.168.0.152:27017
|
||
2021-03-04T17:18:06.682+0800 adding gridFile: /home/qiusj/.local/mongodb/mongofiles/data/shenzhen_CholophyII_2020.tif
|
||
|
||
2021-03-04T17:18:06.824+0800 added gridFile: /home/qiusj/.local/mongodb/mongofiles/data/shenzhen_CholophyII_2020.tif
|
||
|
||
```
|
||
|
||
查看集合:
|
||
|
||
```shell
|
||
#连接数据库
|
||
$ /usr/local/mongodb/bin/mongo --host=192.168.0.152 --port=27017
|
||
#查看数据库
|
||
> db
|
||
test
|
||
#查看集合
|
||
> show collections
|
||
fs.chunks
|
||
fs.files
|
||
|
||
#查看files集合
|
||
> db.fs.files.find()
|
||
{
|
||
"_id" : ObjectId("6040a5ce6211dcaf818ac9aa"),
|
||
"length" : NumberLong(15838376),
|
||
"chunkSize" : 261120,
|
||
"uploadDate" : ISODate("2021-03-04T09:18:06.824Z"),
|
||
"filename" : "/home/qiusj/.local/mongodb/mongofiles/data/shenzhen_CholophyII_2020.tif",
|
||
"metadata" : { }
|
||
}
|
||
```
|
||
|
||
查询文件:
|
||
|
||
```shell
|
||
$ mongofiles search 'Cho'
|
||
2021-03-04T17:48:10.976+0800 connected to: mongodb://localhost/
|
||
/home/qiusj/.local/mongodb/mongofiles/data/shenzhen_CholophyII_2020.tif 15838376
|
||
```
|
||
|
||
> 搜索结果:filename length
|
||
>
|
||
> 可以发现mongofiles命令默认连接本地的 mongo 服务,如果不是本地需带上 uri 连接信息。
|
||
|
||
获取文件:
|
||
|
||
```shell
|
||
$ mongofiles --uri=mongodb://localhost:27017 get /home/qiusj/.local/mongodb/mongofiles/data/shenzhen_CholophyII_2020.tif --local=test.tif
|
||
2021-03-04T17:57:34.507+0800 connected to: mongodb://localhost:27017
|
||
2021-03-04T17:57:34.531+0800 finished writing to test.tif
|
||
```
|
||
|
||
> get \<filename1[ filename2] ...>命令,当只有一个文件时,可以使用--local参数给文件重新命名。
|
||
>
|
||
> 导出的目录为get命令执行所在的当前目录。
|
||
|
||
删除文件:
|
||
|
||
```shell
|
||
$ mongofiles delete /home/qiusj/.local/mongodb/mongofiles/data/shenzhen_CholophyII_2020.tif
|
||
2021-03-04T18:03:23.399+0800 connected to: mongodb://localhost/
|
||
2021-03-04T18:03:23.402+0800 successfully deleted all instances of '/home/qiusj/.local/mongodb/mongofiles/data/shenzhen_CholophyII_2020.tif' from GridFS
|
||
|
||
#再去数据库里查看
|
||
$ /usr/local/mongodb/bin/mongo
|
||
> show collections
|
||
fs.chunks
|
||
fs.files
|
||
> db.fs.files.find()
|
||
>
|
||
```
|
||
|
||
### 4 Springboot中使用GridFS
|
||
|
||
使用一个单实例的未启用认证的`mongodb`作为示例数据库。
|
||
|
||
#### 4.1 导入依赖
|
||
|
||
```groovy
|
||
compile 'org.springframework.boot:spring-boot-starter-data-mongodb'
|
||
testImplementation 'org.springframework.boot:spring-boot-starter-test'
|
||
```
|
||
|
||
#### 4.2 配置文件
|
||
|
||
`application.yml`文件:
|
||
|
||
```yaml
|
||
#如果连接的不是本地的mongo,会比较慢,因为会受到网络传输速度的影响
|
||
spring:
|
||
data:
|
||
mongodb:
|
||
database: files
|
||
host: localhost
|
||
port: 27017
|
||
|
||
#用于测试的本地遥感数据文件
|
||
rsfilepath: "E:\\data\\ShenzhenNDWI_2019_S2L1C.tif"
|
||
```
|
||
|
||
`MongoConfig.java`,在该类中根据配置信息创建`mongo`的客户端实例和`GridFSBucket`实例。
|
||
|
||
> `GridFS`用于存储文件的两个集合都在同一个`bucket`中。集合的前缀为`bucket name`,默认为`fs`。
|
||
|
||
```java
|
||
@Configuration
|
||
public class MongoConfig {
|
||
|
||
@Value("${spring.data.mongodb.host}")
|
||
private String host;
|
||
|
||
@Value("${spring.data.mongodb.port}")
|
||
private int port;
|
||
|
||
@Value("${spring.data.mongodb.database}")
|
||
private String database;
|
||
|
||
@Bean
|
||
public MongoClient getMongoClient(){
|
||
MongoClient mongoClient = MongoClients.create(
|
||
MongoClientSettings.builder()
|
||
.applyToClusterSettings(builder ->
|
||
builder.hosts(Arrays.asList(
|
||
new ServerAddress(host, port)
|
||
)))
|
||
.build());
|
||
return mongoClient;
|
||
}
|
||
|
||
@Bean
|
||
public GridFSBucket getgridFSBucket(MongoClient mongoClient) {
|
||
MongoDatabase mongoDatabase = mongoClient.getDatabase(database);
|
||
//使用默认的fs前缀
|
||
GridFSBucket gridFSBucket = GridFSBuckets.create(mongoDatabase);
|
||
return gridFSBucket;
|
||
}
|
||
}
|
||
```
|
||
|
||
#### 4.3 测试类
|
||
|
||
我们在Test模块下创建一个`GridsApplicationTests.java`测试类,用于测试`GridFSBucket`中的方法:
|
||
|
||

|
||
|
||
```java
|
||
|
||
@SpringBootTest
|
||
class GridfsApplicationTests {
|
||
|
||
@Autowired
|
||
private GridFSService gridFSService;
|
||
|
||
@Value("${rsfilepath}")
|
||
private String rsfilepath;
|
||
|
||
private int chunckSize = 1024;
|
||
|
||
@Autowired
|
||
private GridFSBucket gridFSBucket;
|
||
|
||
/**
|
||
* uploadFromStream()方法
|
||
* 文件导入到GridFS中
|
||
*/
|
||
@Test
|
||
void testUploadToGridFS() throws IOException {
|
||
File file = new File(rsfilepath);
|
||
FileInputStream inputStream = new FileInputStream(file);
|
||
|
||
//GridFSUploadOptions配置chunkSize和添加元数据
|
||
GridFSUploadOptions options = new GridFSUploadOptions()
|
||
.chunkSizeBytes(chunckSize)
|
||
.metadata(new Document("k1","v1").append("k2","v2"));
|
||
|
||
//GridFSBucket.InputStream方法读取字节输入流中的内容并将其保存到GridFSBucket中
|
||
ObjectId fileId = gridFSBucket.uploadFromStream("GridFS输入", inputStream, options);
|
||
System.out.println(fileId);
|
||
inputStream.close();
|
||
}
|
||
|
||
/**
|
||
* openUploadStream()方法
|
||
* 返回GridFSUploadStream,作为缓存
|
||
* 文件导入到GridFS中
|
||
*/
|
||
@Test
|
||
void testUOpenploadToGridFS() throws IOException {
|
||
byte[] data = Files.readAllBytes(Paths.get(rsfilepath));
|
||
|
||
//GridFSUploadOptions 配置 chunkSize 和添加元数据
|
||
GridFSUploadOptions options = new GridFSUploadOptions()
|
||
.chunkSizeBytes(chunckSize)
|
||
.metadata(new Document("format","tiff").append("SRS","EPSG:3857"));
|
||
|
||
GridFSUploadStream uploadStream = gridFSBucket.openUploadStream("缓冲", options);
|
||
//GridFSUploadStream缓冲数据达到chunckSizeBytes时才进行插入数据块chunks操作
|
||
uploadStream.write(data);
|
||
//当GridFSUploadStream被关闭后,才会将最后一块chunk写入chunks集合和将文件元数据插入files集合
|
||
uploadStream.close();
|
||
String fileId = uploadStream.getObjectId().toString();
|
||
System.out.println(fileId);
|
||
}
|
||
|
||
/**
|
||
* find()方法
|
||
* 查找GridFS中的文件
|
||
*/
|
||
@Test
|
||
void testFindFileFromGridFS() throws IOException {
|
||
//查询GridFS中所有的文件
|
||
gridFSBucket.find().forEach(gridFSFile -> {
|
||
System.out.println(gridFSFile.getObjectId()+": "+gridFSFile.getFilename());
|
||
});
|
||
System.out.println("-----------------------");
|
||
gridFSBucket.find(eq("metadata.SRS","EPSG:3857")).forEach(gridFSFile -> {
|
||
System.out.println(gridFSFile.getObjectId()+": "+gridFSFile.getFilename());
|
||
});
|
||
}
|
||
|
||
/**
|
||
* downloadToStream()方法
|
||
* 根据fileId从GridFS中下载文件
|
||
*/
|
||
@Test
|
||
void testDownloadToStreamById() throws IOException {
|
||
//需要导出的文件的fileId
|
||
ObjectId fileId = new ObjectId("6049d240efdc5715fc6ecbbe");
|
||
//指定输出目标路径和输出文件名
|
||
File file = new File("E:\\data\\out\\NDWI_2019_S2L1C.tiff");
|
||
if(!file.exists()){
|
||
file.getParentFile().mkdirs();
|
||
file.createNewFile();
|
||
}
|
||
//创建文件字节输出流
|
||
FileOutputStream out = new FileOutputStream(file);
|
||
|
||
gridFSBucket.downloadToStream(fileId, out);
|
||
out.close();
|
||
}
|
||
|
||
/**
|
||
* downloadToStream()方法
|
||
* 根据filename从GridFS中下载文件
|
||
*/
|
||
@Test
|
||
void testDownloadToStreamByFilename() throws IOException {
|
||
//创建下载条件对象
|
||
GridFSDownloadOptions downloadOptions = new GridFSDownloadOptions();
|
||
/**
|
||
* 版本控制,默认下载最新的一个版本对象,这里指定为0,选择原始的版本
|
||
* 0 = the original stored file
|
||
* 1 = the first revision
|
||
* 2 = the second revision
|
||
* etc..
|
||
* -2 = the second most recent revision
|
||
* -1 = the most recent revision
|
||
*/
|
||
downloadOptions.revision(0);
|
||
|
||
//指定输出目标路径和输出文件名
|
||
File file = new File("E:\\data\\out\\NDWI2.tiff");
|
||
if(!file.exists()){
|
||
file.getParentFile().mkdirs();
|
||
file.createNewFile();
|
||
}
|
||
//创建文件字节输出流
|
||
FileOutputStream out = new FileOutputStream(file);
|
||
|
||
gridFSBucket.downloadToStream("归一化指数2019",out,downloadOptions);
|
||
out.close();
|
||
}
|
||
|
||
|
||
/**
|
||
* rename,重命名
|
||
*/
|
||
@Test
|
||
void testRenameFile(){
|
||
ObjectId fileId = new ObjectId("6049d240efdc5715fc6ecbbe");
|
||
gridFSBucket.rename(fileId,"归一化指数2019");
|
||
}
|
||
|
||
/**
|
||
* delete
|
||
*/
|
||
@Test
|
||
void testDeleteFile(){
|
||
ObjectId fileId = new ObjectId("6049bf658ca88237b95c9952");
|
||
gridFSBucket.delete(fileId);
|
||
}
|
||
}
|
||
|
||
```
|
||
|
||
|
||
|
||
|
||
|
||
### 参考文章
|
||
|
||
[1] `GridFS` https://docs.mongodb.com/manual/core/gridfs/
|
||
|
||
[2] `mongofiles` https://docs.mongodb.com/database-tools/mongofiles
|
||
|
||
[3] `Java Driver Tutorial GridFS` https://mongodb.github.io/mongo-java-driver/4.2/driver/tutorials/gridfs/ |