GridFS学习与实践
GridFS是基于mongodb存储引擎是实现的“分布式文件系统”,不过严格意义上说,它并非是一个文件系统(以本地文件系统的方式组织),而是基于Nosql模式、application级别的“文件系统”;它具备大数据存储的多个优点。GridFS适宜存储超过16MB的大型文件,不过16M数据在当今互联网时代,已经不足为奇。
GridFS并不是将单个文件直接存储为一个document,而是将文件分成多个parts或者说chunks,然后将每个chunk作为作为一个单独的document存储。默认情况下,GridFS的chunk大小位255k。GridFS使用2个collections来存储这些文件,一个collection存储文件的chunks(实际文件数据),另一个则存储文件的metadata(用户自定义的属性,filename,content-type等)。
当用户查询GridFS中的文件时,客户端或者driver将会重新按序组装这些chunks。用户可以range查询文件,也可以获取文件的任意部分的信息,当然也可以跳过(skip)视频或者音频(任何文件)的中间部分--实现range access of single file。
GridFS不仅对存储超过16M的数据非常有用,用户可以存储任何尺寸的文件,而不需要将它们整个加载入内存。(注意:mongodb中普通的document大小最大为16M,如果超过16M,需要使用GridFS支持)使用GridFS存储文档,可以从Mongodb的高性能、高可用、replications等特性中获益。
When shoud I use GridFS?
对于mongodb而言,如果你的document的大小超过16M,那么应该将它存储在GridFS中。
在一些情况下,将这些大文件存储在GridFS中,比直接存储在系统级的文件系统中更加适合:
1)如果你的文件系统对每个目录下文件的个数有限制(或者太多,将会影响文件的打开速度等),你应该根据需要将它们存储在Mongodb中。
2)如果你的文件数据,有分数据中心镜像保存(大数据情况,可用性保证),那么Mongodb是一个不错的选择。
3)如果你希望访问一个超大的文件,而不希望将它全部加入内存,而是有“range access”的情况---分段读取,那么GridFS天生就具备这种能力,你可以随意访问任意片段。
对于一个大文件,如果你希望原子性的更新它的全部内容,那么GridFS将不适合。
如果你文件大小通常小于16M,比如一些图片、CSS数据,那么开发者应该手动设定将文件保存在一个document中,可以使用BinData数据类型来保存这些数据,直接存储在Mongodb Collection中而非GridFS。(参见org.bson.types.Binary)不过,我们还是有一种倾向,将这些小文件也使用GridFS保存,当然性能也不会差的太多,如果我们的文件通常在1M左右,而不需要“range access”,特别是图片、CSS静态文件,我们可以在存储时手动设置它的chunkSize,避免文件被切分成多个chunks,来提高性能。
GridFS Collections
GridFS将文件存储在2个Collections中:
1)chunks:存储二进制数据。
{ "_id" : <ObjectId>, //chunk ID,全局唯一 "files_id" : <ObjectId>, //file collection表的"_id"值 "n" : <num>, //chunk的序列号,第一个chunk位0。 "data" : <binary> //二进制数据 }
2)files:存储文件的metadata。
{ "_id" : <ObjectId>, // "length" : <num>, //文件的长度 "chunkSize" : <num>, //chunkSize "uploadDate" : <timestamp>, //文件生成的时间戳 "md5" : <hash>, //HASH值 "filename" : <string>, //文件名,用户指定 "contentType" : <string>, // "aliases" : <string array>, //别名 "metadata" : <dataObject>, //用户自定的metadata }
GridFS将上述两个collection的命名默认位fs.files、fs.chunks。
GridFS Index
GridFS在chunks这个collection上,对“files_id”和“n”两个字段创建了“unique、compound”索引,file_id的值与fs.files表中的_id对应。那么从chunks表中获取一个file的所有chunks将是非常简单。
GridFS API中提供了多种方式可以获取一个文件或者指定的chunk。
代码实践
1、MongoDBClient.java:通用代码,用来链接mongodb实例,封装indexes等。
/** * Author: qing * Date: 14-10-11 */ public class MongoDBClient { private BSONConvertor convertor = BSONConvertor.getInstance(); protected MongoDBDriver mongoDBDriver; protected String dbname; protected boolean inited = false; public void setMongoDBDriver(MongoDBDriver mongoDBDriver) { this.mongoDBDriver = mongoDBDriver; } public void setDbname(String dbname) { this.dbname = dbname; } public BSONConvertor getConvertor() { return convertor; } public synchronized void init() { if(inited) { return; } mongoDBDriver.ensureIndex(this.dbname); inited = true; } public DBCollection getCollection(String collectionName) { DB db = mongoDBDriver.getDb(dbname); DBCollection dbc = db.getCollection(collectionName); return dbc; } public DB getDB() { return mongoDBDriver.getDb(this.dbname); } }
2、GridFSClient.java:用来访问指定GridFS库。
/** * Author: qing * Date: 14-10-11 */ public class GridFSClient extends MongoDBClient{ private GridFS gridFS = null; private Object lock = new Object(); public void setMongoDBDriver(MongoDBDriver mongoDBDriver) { this.mongoDBDriver = mongoDBDriver; } public GridFS getInstance() { if(gridFS != null) { return gridFS; } synchronized (lock) { if(gridFS != null) { return gridFS; } gridFS = new GridFS(mongoDBDriver.getDb(this.dbname)); return gridFS; } } public void close() { mongoDBDriver.close(); } }
3、spring配置:
<bean id="mongodbProperties" class="org.springframework.beans.factory.config.PropertiesFactoryBean"> <property name="locations"> <list> <value>classpath:mongodb-config.properties</value> </list> </property> </bean> <bean id="mongodbDriver" class="com.test.common.mongo.MongoDBDriver" init-method="init" destroy-method="close"> <property name="properties" ref="mongodbProperties"/> <property name="configuration"> <bean class="com.test.common.mongo.object.MongoDBConfig"> <property name="addresses" value="${mongodb.filesystem.addresses}"/> <property name="credentials"> <list> <bean class="com.test.common.mongo.object.MongoDBCredential"> <property name="dbname" value="${mongodb.filesystem.dbname}"/> <property name="username" value="${mongodb.filesystem.username}"/> <property name="password" value="${mongodb.filesystem.password}"/> </bean> </list> </property> </bean> </property> </bean> <bean id="gridFSClient" class="com.test.common.mongo.GridFSClient"> <property name="mongoDBDriver" ref="mongodbDriver"/> <property name="dbname" value="${mongodb.filesystem.dbname}"/> </bean>
5、GridFS操作--写入
GridFS gridFS = gridFSClient.getInstance(); String filename = randomFileName();//生成一个随机的名字 //如果有文件重复,则重新生成filename while (true) { GridFSDBFile _current = gridFS.findOne(filename); //如果文件不存在,则保存操作 if (_current == null) { break; } //否则,重新生成文件名称 filename = randomFileName(); } GridFSInputFile file = gridFS.createFile(inputStream, filename); //file.setChunkSize(1 * 1024 * 1024);//1M,通常我们采用默认 DBObject metadata = new BasicDBObject(); metadata.put("format",format); file.setMetaData(metadata); file.save();
6、GridFS操作--读取
GridFS gridFS = gridFSClient.getInstance(); GridFSDBFile file = gridFS.findOne(path + "/" + filename); if(file != null) { return file.getInputStream();//无需关闭,GridFS会在读取结束后关闭 }
对于GridFS作为online类型web文件存储,nginx中支持了gridfs模块:https://github.com/mdirolf/nginx-gridfs,相关的安装和配置,请参考其他文档。
参考文档:
1)GridFS:【官方文档】