GridFS 基于 MongoDB 的分布式文件存储系统

6,065 阅读5分钟
原文链接: blog.720ui.com

GridFS 基于MongoDB的分布式文件存储系统

什么是GridFS

GridFS是一种将大型文件存储在MongoDB的文件规范。所有官方支持的驱动均实现了GridFS规范。GridFS是MongoDB中的一个内置功能,可以用于存放文件。

为什么要使用GridFS

问题1:我之前将文件上传到文件系统中,比如通过FTP上传到文件服务器。并将文件地址存储到MySQL数据库,我觉得这个方式挺好的呀,为什么要使用GridFS?

答:文件系统到了后期会变的很难管理,同时不利于扩展,此外我想做分布式文件系统也显得不那么容易。而GridFS却正好相反,它基于MongoDB的文件系统,便于管理和扩展。

问题2:普通文件系统对于大文件处理比较麻烦,需要考虑文件系统自身的限制,GridFS提供了什么好方案?

答:GridFS 会将大文件对象分割成多个小的chunk(文件片段),一般为256k/块,每块chunk将作为MongoDB的一个文档(document)被存储在chunks集合中。这一点对于很大的内容文件非常有用。此外,GridFS和MongoDB的分片使得你的文件可以分布到多个服务器上,而且没有增加操作的复杂性。

问题3:我可以直接把文件的二进制流存到MongoDB么?

答: MongoDB默认的文件大小上限为16MB。所以,超过这个大小是有问题的。如果你的文件,比如图片、音频、视频等超过了16MB,那么你就应该使用GridFS。

GridFS 的两个集合

GridFS 使用两个集合来存储一个文件:fs.files与fs.chunks。我们可以使用下面命令来进行查看。


show collections

fs.files集合包含了文件的元数据。我们使用命令来查看


db.fs.files.findOne();

以下是刚上传文件的元数据信息的 fs.files 集合文档


{
    "_id" : ObjectId("57fb553ac7fa9102e0000001"),
    "chunkSize" : 261120,
    "uploadDate" : ISODate("2016-10-10T08:45:50.051Z"),
    "length" : 1.53513e+008,
    "md5" : "9222e097e624800fdd9bfb568169ccad",
    "filename" : "E:/jdk-7u79-linux-x64.tar.gz"
}

fs.chunks集合则存储实际的以256KB尺寸进行分割的文件块。如果你有分片的集合,那么文件块会分布到多台服务器上。


db.fs.chunks.getIndexes();

以下是刚上传文件的fs.chunks 集合文档


{
    "0" : {
        "v" : 1,
        "key" : {
            "_id" : 1
        },
        "name" : "_id_",
        "ns" : "gridfs.fs.chunks"
    },
    "1" : {
        "v" : 1,
        "key" : {
            "files_id" : 1,
            "n" : 1
        },
        "name" : "files_id_1_n_1",
        "ns" : "gridfs.fs.chunks"
    }
}

GridFS 基本操作

上传文件

我们使用GridFS的put命令来存储一个文件。首先,调用MongoDB安装目录下bin的mongofiles.exe工具。然后,打开命令提示符,进入到MongoDB的安装目录的bin目录中,找到mongofiles.exe,并输入下面的代码:


mongofiles.exe -d gridfs put E:\elasticsearch-1.7.0.zip

参数说明,–d 指定数据库gridfs

列出清单

使用以下命令来查看数据库中文件的文档


db.fs.files.find()

返回以下内容


/* 1 */
{
    "_id" : ObjectId("57fb553ac7fa9102e0000001"),
    "chunkSize" : 261120,
    "uploadDate" : ISODate("2016-10-10T08:45:50.051Z"),
    "length" : 1.53513e+008,
    "md5" : "9222e097e624800fdd9bfb568169ccad",
    "filename" : "E:/jdk-7u79-linux-x64.tar.gz"
}

/* 2 */
{
    "_id" : ObjectId("57fb6c49c7fa9135d4000001"),
    "chunkSize" : 261120,
    "uploadDate" : ISODate("2016-10-10T10:24:09.654Z"),
    "length" : 3.17848e+007,
    "md5" : "882013a3d205289dddce22c904d3c476",
    "filename" : "E:\\elasticsearch-1.7.0.zip"
}

查找文件

可以看到 fs.chunks 集合中所有的区块,以下我们得到了文件的 _id 值,我们可以根据这个 _id 获取区块(chunk)的数据。


db.fs.chunks.find({files_id:ObjectId('57fb553ac7fa9102e0000001')});

以上实例中,查询返回了 50 个文档的数据,意味着文件被存储在50个区块中。

下载文件

我们使用GridFS的get命令来下载一个文件。


mongofiles.exe -d gridfs get E:\elasticsearch-1.7.0.zip

Java 操作 GridFS

注意,以下是一个简单的入门案例,不适用于实际场景,实际场景需要考虑异常处理、IO流关闭、日志处理等。
使用的Maven版本



    org.mongodb
    mongo-java-driver
    3.2.2

公共方法


public MongoDatabase mongoDatabase() throws Exception{
    MongoClient mongoClient = new MongoClient(mongoHost, mongoPort);
    return mongoClient.getDatabase(dbName);
}

上传文件


@Test
public void upload() throws Exception {
    // 获取文件流
    File file = new File("E:/zookeeper-3.4.8.tar.gz");
    InputStream in = new FileInputStream(file);
    // 创建一个容器,传入一个`MongoDatabase`类实例db
    GridFSBucket bucket = GridFSBuckets.create(mongoDatabase());
    // 上传
    ObjectId fileId = bucket.uploadFromStream(UUID.randomUUID().toString(), in);
    System.out.println("上传完成。 文件ID:"+fileId);
}

查找文件


@Test
public void findOne() throws Exception {
    // 获取文件ID
    String objectId = "57fbaffcec773716ecc54ef4";
    // 创建一个容器,传入一个`MongoDatabase`类实例db
    GridFSBucket bucket = GridFSBuckets.create(mongoDatabase());
    // 获取内容
    GridFSFindIterable gridFSFindIterable = bucket.find(Filters.eq("_id", new ObjectId(objectId)));
    GridFSFile gridFSFile = gridFSFindIterable.first();
    System.out.println("filename: " + gridFSFile.getFilename());
}

下载文件


@Test
public void download() throws Exception {
    // 获取文件ID
    String objectId = "57fbaffcec773716ecc54ef4";
    // 获取文件流
    File file = new File("D:/zookeeper-3.4.8.tar.gz");
    // 创建一个容器,传入一个`MongoDatabase`类实例db
    GridFSBucket bucket = GridFSBuckets.create(mongoDatabase());
    // 创建输出流
    OutputStream os = new FileOutputStream(file);
    // 下载
    bucket.downloadToStream(new ObjectId(objectId), os);
    System.out.println("下载完成。");
}

删除文件


@Test
public void delete() throws Exception {
    // 获取文件ID
    String objectId = "57fbaffcec773716ecc54ef4";
    // 创建一个容器,传入一个`MongoDatabase`类实例db
    GridFSBucket bucket = GridFSBuckets.create(mongoDatabase());
    // 删除
    bucket.delete(new ObjectId(objectId));
    System.out.println("删除完成。");
}

总结

GridFS是一种大型文件存储方式之一,有其一定的使用场景,务必根据实际场景选择技术方案。

现在主流的文件存储方案,例如云存储服务,以amazon的s3云服务,七牛云存储都是不错的选择。此外,例如还有阿里开源的tair等键值结构数据的解决方案。