meface/docs/article/db/mongodb_gridfs.md

553 lines
19 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
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`对各程序语言的提供的驱动对文件进行操作。
![1614838692793](./images/1614838692793.png)
- 使用`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`。
![image-20210128151549082](./images/mongodbtools.png)
![image-20210128152329012](./images/mongofilesdirect.png)
![image-20210128153732500](./images/mongodbvar1.png)
![image-20210128153418405](./images/mongodbvar2.png)
![image-20210128153920377](./images/mongofilescommondline.png)
将下载的文件解压到任意位置,再将`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`命令下载:
![image-20210128164131790](./images/image-20210128164131790.png)
```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`中的方法:
![1615447216815](./images/1615447216815.png)
```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/