--- title: MongoDB GridFS文件存储 date: 2021-01-28 author: ac tags: - MongoDB - GridFS - mongofiles categories: - Database --- > GIS中遥感影像数据可以使用MongoDB的GridFS进行存储。 ### 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" : , "files_id" : , "n" : , "data" : } ``` > `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" : , "length" : , "chunkSize" : , "uploadDate" : , "md5" : , "filename" : , "contentType" : , "aliases" : , "metadata" : , } ``` - 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`主要配置`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 的连接信息,如:
--uri="mongodb://[username:password@]host1\[:port1]\[,host2[:port2],...\[,hostN\[:portN]]][/\[database][?options]]" | | --config | 指定配置文件,以配置文件的形式启动 | **Commands: ** | 命令 | 说明 | | -------------------------------------------------- | ------------------------------------------------------------ | | list \ | 列出`GridFS`存储的文件。\用于筛选返回列表中以 prefix字符串为前缀的文件。 | | search \ | 列出`GridFS`中名称与\的任何部分匹配的文件。 | | put \ | 将指定的文件从本地文件系统复制到`GridFS`中。多个文件可以指定为空格分隔的列表。 | | get \ | 将指定的文件从`GridFS`存储复制到本地文件系统。 | | get_id "<_id>" | 将文件(由\<\_id>指定)从`GridFS`中复制到本地文件系统。\<\_id>是该对象在`GridFS`中的扩展JSON \_id。 | | get_regex \ --regexOptions \ | 将匹配指定的\表达式的一个或多个文件从`GridFS`复制到本地文件系统。get_regex命令使用Perl兼容的正则表达式(“PCRE”)8.42版本,支持UTF-8。 | | delete \ | 从`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 \命令,当只有一个文件时,可以使用--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/