MongoDB 的 GridFS 詳細分析
GridFS簡介
GridFS是MongoDB中的一個內置功能,可以用于存放大量小文件。
http://www.mongodb.org/display/DOCS/GridFS
http://www.mongodb.org/display/DOCS/GridFS+Specification
GridFS使用
MongoDB提供了一個命令行工具mongofiles可以來處理GridFS,在bin目錄下。
列出所有文件:
mongofiles list
上傳一個文件:
mongofiles put xxx.txt
下載一個文件:
mongofiles get xxx.txt
查找文件:
mongofiles search xxx //會查找所有文件名中包含“xxx”的文件
mongofiles list xxx //會查找所有文件名以“xxx”為前綴的文件
參數說明:
–d 指定數據庫 ,默認是fs,Mongofiles list –d testGridfs
-u –p 指定用戶名,密碼
-h 指定主機
-port 指定主機端口
-c 指定集合名,默認是fs
-t 指定文件的MIME類型,默認會忽略
使用MongoVUE來查看,管理GridFS
MongoVUE地址:http://www.mongovue.com/
MongoVUE是個免費軟件,但超過15天后功能受限。可以通過刪除以下注冊表項來解除限制:
[HKEY_CURRENT_USER\Software\Classes\CLSID\{B1159E65-821C3-21C5-CE21-34A484D54444}\4FF78130]
把這個項下的值全刪掉就可以了。
用java驅動上傳下載文件:
下載地址:https://github.com/mongodb/mongo-java-driver/downloads
官方的文檔貌似不是最新的,不過通過查看api來使用也不困騅。
http://api.mongodb.org/java/2.7.2/
以下代碼基于mongo-2.7.3.jar
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.UnknownHostException;
import java.security.NoSuchAlgorithmException;
import com.mongodb.BasicDBObject;
import com.mongodb.DB;
import com.mongodb.DBCollection;
import com.mongodb.DBObject;
import com.mongodb.Mongo;
import com.mongodb.MongoException;
import com.mongodb.gridfs.GridFS;
import com.mongodb.gridfs.GridFSDBFile;
import com.mongodb.gridfs.GridFSInputFile;
public class Test {
Mongo connection;
DB db;
DBCollection collection;
GridFS myFS;
String mongoDBHost = "127.0.0.1";
int mongoDBPort = 27017;
String dbName = "testGridfs";
String collectionName = "fs";
public static void main(String[] args) throws MongoException, IOException, NoSuchAlgorithmException {
Test t = new Test();
String fileName = "F:/CPU.txt";
String name = "CPU.txt";
//把文件保存到gridfs中,并以文件的md5值為id
t.save(new FileInputStream(fileName), name);
//據文件名從gridfs中讀取到文件
GridFSDBFile gridFSDBFile = t.getByFileName(name);
if(gridFSDBFile != null){
System.out.println("filename:" + gridFSDBFile.getFilename());
System.out.println("md5:" + gridFSDBFile.getMD5());
System.out.println("length:" + gridFSDBFile.getLength());
System.out.println("uploadDate:" + gridFSDBFile.getUploadDate());
System.out.println("--------------------------------------");
gridFSDBFile.writeTo(System.out);
}else{
System.out.println("can not get file by name:" + name);
}
}
public Test() throws UnknownHostException, MongoException, NoSuchAlgorithmException {
_init();
}
public Test(String mongoDBHost, int mongoDBPort, String dbName,
String collectionName) throws UnknownHostException, MongoException, NoSuchAlgorithmException {
this.mongoDBHost = mongoDBHost;
this.mongoDBPort = mongoDBPort;
this.dbName = dbName;
this.collectionName = collectionName;
_init();
}
private void _init() throws UnknownHostException, MongoException, NoSuchAlgorithmException{
connection = new Mongo(mongoDBHost, mongoDBPort);
db = connection.getDB(dbName);
collection = db.getCollection(collectionName);
myFS = new GridFS(db);
}
/**
* 用給出的id,保存文件,透明處理已存在的情況
* id 可以是string,long,int,org.bson.types.ObjectId 類型
* @param in
* @param id
*/
public void save(InputStream in, Object id){
DBObject query = new BasicDBObject("_id", id);
GridFSDBFile gridFSDBFile = myFS.findOne(query);
if(gridFSDBFile != null)
return;
GridFSInputFile gridFSInputFile = myFS.createFile(in);
gridFSInputFile.save();
return;
}
/**
* 據id返回文件
* @param id
* @return
*/
public GridFSDBFile getById(Object id){
DBObject query = new BasicDBObject("_id", id);
GridFSDBFile gridFSDBFile = myFS.findOne(query);
return gridFSDBFile;
}
/**
* 據文件名返回文件,只返回第一個
* @param fileName
* @return
*/
public GridFSDBFile getByFileName(String fileName){
DBObject query = new BasicDBObject("filename", fileName);
GridFSDBFile gridFSDBFile = myFS.findOne(query);
return gridFSDBFile;
}
}nginx-gridfs模塊的安裝使用
項目主頁:https://github.com/mdirolf/nginx-gridfs
通過nginx-gridfs,可以直接用http來訪問GridFS中的文件。
1. 安裝
安裝各種依賴包:zlib,pcre,openssl
在ubuntu下可能是以下命令:
sudo apt-get install zlib1g-dev //貌似sudo apt-get install zlib-dev 不能安裝
sudo apt-get install libpcre3 libpcre3-dev
sudo apt-get install openssl libssl-dev
安裝git(略)
用git下載nginx-gridfs的代碼:
git clone git://github.com/mdirolf/nginx-gridfs.git
cd nginx-gridfs
git submodule init
git submodule update
下載nginx:
wget http://nginx.org/download/nginx-1.0.12.zip
tar zxvf nginx-1.0.12.zip
cd nginx-1.0.12
./configure --add-module=<nginx-gridfs的路徑>
make
sudo make install
如果編譯出錯,則在configure時加上--with-cc-opt=-Wno-error 參數。
2. 配置nginx
在server的配置中加上以下
location /pics/ {
gridfs pics
field=filename
type=string;
mongo 127.0.0.1:27017;
}
上面的配置表示:
數據庫是pics,通過文件名filename來訪問文件,filename的類型是string
目前只支持通過id和filename來訪問文件。
啟動nginx:/usr/local/nginx/sbin/nginx
用MongoVUE把一個圖片001.jpg上傳到pics數據庫中。
打開:http://localhost/pics/001.jpg
如果成功,則可以看到顯示圖片了。
3. nginx-gridfs的不足
沒有實現http的range support,也就是斷點續傳,分片下載的功能。
GridFS實現原理
GridFS在數據庫中,默認使用fs.chunks和fs.files來存儲文件。
其中fs.files集合存放文件的信息,fs.chunks存放文件數據。
一個fs.files集合中的一條記錄內容如下,即一個file的信息如下:
{
"_id" : ObjectId("4f4608844f9b855c6c35e298"), //唯一id,可以是用戶自定義的類型
"filename" : "CPU.txt", //文件名
"length" : 778, //文件長度
"chunkSize" : 262144, //chunk的大小
"uploadDate" : ISODate("2012-02-23T09:36:04.593Z"), //上傳時間
"md5" : "e2c789b036cfb3b848ae39a24e795ca6", //文件的md5值
"contentType" : "text/plain" //文件的MIME類型
"meta" : null //文件的其它信息,默認是沒有”meta”這個key,用戶可以自己定義為任意BSON對象
}對應的fs.chunks中的chunk如下:
{
"_id" : ObjectId("4f4608844f9b855c6c35e299"), //chunk的id
"files_id" : ObjectId("4f4608844f9b855c6c35e298"), //文件的id,對應fs.files中的對象,相當于fs.files集合的外鍵
"n" : 0, //文件的第幾個chunk塊,如果文件大于chunksize的話,會被分割成多個chunk塊
"data" : BinData(0,"QGV...") //文件的二進制數據,這里省略了具體內容
}默認chunk的大小是256K。
public static final int DEFAULT_CHUNKSIZE = 256 * 1024;
所以在把文件存入到GridFS過程中,如果文件大于chunksize,則把文件分割成多個chunk,再把這些chunk保存到fs.chunks中,最后再把文件信息存入到fs.files中。
在讀取文件的時候,先據查詢的條件,在fs.files中找到一個合適的記錄,得到“_id”的值,再據這個值到fs.chunks中查找所有“files_id”為“_id”的chunk,并按“n”排序,最后依次讀取chunk中“data”對象的內容,還原成原來的文件。
自定義Gridfs的hash函數
盡管從理論上,無論用什么hash函數,都有可能出現hash值相同,但內容不相同的文件,但是對于GridFS默認使用的md5算法,目前已出現長度和md5值都相同但內容不一樣的文件。
如果想要自已改用其它hash算法,可以從驅動入手。因為GridFS在MongoDB中實際也只是兩個普通的集合,所以完全可以自已修改驅動,替換下hash算法即可。
目前java版的驅動比較簡單,可以很容易修改實現。
但是要注意,這樣不符合GridFS的規范了。
注意事項
1. GridFS不自動處理md5相同的文件,對于md5相同的文件,如果想在GridFS中只有一個存儲,要用戶自已處理。Md5值的計算由客戶端完成。
2. 因為GridFS在上傳文件過程中是先把文件數據保存到fs.chunks,最后再把文件信息保存到fs.files中,所以如果在上傳文件過程中失敗,有可能在fs.chunks中出現垃圾數據。這些垃圾數據可以定期清理掉。