一、GridFS是什么&為什么需要它
我們知道目前MongoDB的BSON文件最大只能是16M,也就是說單個文檔最多只能存儲16M的數據,那么如果需要MongoDB存儲超過16M的大文件該怎么辦呢?這就需要通過MongoDB的GridFS規范來實現了。
GridFS並不是MongoDB自身的特性,只是一種將大型文件存儲在MongoDB的文件規范,借助GridFS,我們可以很好地管理存儲在MongoDB中的大文件。由於GridFS只是標准MongoDB框架下存儲文件的一種不同的方式而已,所以也是受BSON最大文件大小限制的,單個文檔最大不能超過16M。GridFS使用兩個集合來存儲文件:fs.files和fs.chunks,fs.files用於存儲文件名稱和大小等元數據,fs.chunks則用於存儲文件內容,fs.chunks中每個文檔最大為256K,大文件內容被分割成多個塊存儲於多個fs.chunks文檔中,每個fs.chunks文檔都存儲了文件id(files中的_id鍵值)和序號(屬於文件內容的第幾個塊)。查看文件時,先查出文件在fs.files中的文檔,再根據其ID在fs.chunks中查詢存儲該文件內容的文檔,根據其序號先后順序進行拼湊,即可讀取整個文件的內容了。
二、命令行操作GridFS
可以使用MongoDB安裝目錄下的bin子目錄中的mongofiles命令行工具來直接操作GridFS。
1. 存儲文件:
mongofiles存儲文件使用put命令。這里我使用MongoDB的一本電子書作為例子,文件大小為56.9M。
注意:GridFS可以添加相同文件名的文件,而且就算兩個文件完全相同,得到的md5值一樣,GridFS還是會當成新的文件進行存儲,而不會更新原有文件。
2. 查詢文件列表:
mongofiles查詢文件列表使用list命令。
使用mongo命令行工具查詢該文件在MongoDB中的存儲內容(數據庫為test):
(上圖中fs.chunks文檔只是截取了前幾個而已,圖中注明了fs.files和fs.chunks集合中文檔的字段含義,其中fs.hunks還有一個data字段是用於存儲文件二進制數據的,這里為了避免輸出一大堆的二進制數據,沒有把這個字段查出來。)
3. 搜索文件:
mongofiles查詢文件列表使用search命令(模糊搜索)。
4. 刪除文件:
mongofiles查詢文件列表使用delete命令(需要輸入確切文件名)。
這時再使用mongofile list命令查詢發現已經沒有這個文件了,集合fs.files和fs.chunks也都已經沒有存儲該文件的任何文檔了。
注意:delete命令是基於文件名刪除文件的,將會刪除所有同名的文件。
5. 讀取文件:
mongofiles使用get命令讀取文件內容,把MongoDB中存儲的某個文件的內容讀取出來,寫入一個文件中。
(這個命令會把MongoDB中存儲的名為“d:\mongodb\files\MongoDB.pdf”的文件內容讀取出來輸出到文件d:\mongodb\files\MongoDB.pdf中,若文件已存在則會被覆寫。)
三、php操作GridFS
MongoDB的PHP驅動也支持使用GridFS進行大文件存儲。
1. 存儲文件:
這里以存儲一張圖片為例:
$mongo = new mongoClient('mongodb://localhost:27017');//連接MongoDB(ip:port)
$db = $mongo->cdn;//選擇一個數據庫來存儲文件,這里選擇數據庫cdn
$gridfs = $db->getGridFS();//獲取MongoGridFS對象
$ret = $gridfs->storeFile('/data/wwwroot/cdn/myPhoto.jpg');//存儲文件
echo '
';
print_r($ret);
若文件存儲成功,將返回一個MongoId對象,打印出來是這樣的:
這個MongoId對象就是文件存儲在fs.files集合中的文檔的_id鍵的值,我們可以把’$id’鍵對應的這個字符串記錄起來,以便以后可以根據這個字符串讀取這個文件。注意不能根據文件名來讀取文件,因為可能存在多個同名文件,只有這個_id鍵才是唯一的。
除了storeFile()方法,你也可以使用storeBytes()方法存儲文件的二進制流:
$data = file_get_contents('/data/wwwroot/cdn/ycl.png');
$ret = $gridfs->storeBytes($data, array('desc'=>'這是ycl的照片,哈哈!', 'info' => '無可奉告'));//可以利用第二個參數添加一些額外信息
若文件是用戶上傳的,還可以使用storeUpload()方法直接存儲用戶上傳的文件,該函數的參數就是html頁面中上傳文件的表單字段名稱:
$ret = $gridfs->storeUpload('photo');
或:
$ret = $gridfs->storeFile($_FILES['photo']['tmp_name']);
2. 文件的查詢和讀取:
首先我們查詢一下剛剛存儲的這張圖片在fs.files集合中是怎么存儲的,這里就利用剛才返回的MongoId對象進行查看:
$mongo = new mongoClient('192.168.97.200:27020');
$db = $mongo->cdn;
$collection = $db->selectCollection('fs.files');
$doc = $collection->findOne(['_id' => new MongoId('5a73e86f2ff2de2207a4bd2d')]);
echo '
';
print_r($doc);
打印出來是這樣的:
(若是利用storeBytes()方法進行存儲並且添加了額外信息的,’filename’字段將不復存在,被添加的額外信息字段取代)
下面直接讀取這個文件,在瀏覽器上輸出來看看:
$mongo = new mongoClient('mongodb://localhost:27017');
$db = $mongo->cdn;//選擇存儲文件的數據庫
$gridfs = $db->getGridFS();
$doc = $gridfs->findOne(['_id' => new MongoId('5a73e86f2ff2de2207a4bd2d')]);
header('Content-type: image/jpg');//輸出圖片頭
echo $doc->getBytes();//輸出數據流
以上代碼運行即可在瀏覽器上看到存儲的圖片了。
所以,使用php操作GridFS存儲和讀取大文件就是這么簡單,你只需要調用MongoGridFS類提供的存儲文件的方法進行存儲,然后將返回的MongoId對象的字符串記錄下來,作為讀取文件方法的參數進行文件讀取就可以了。你甚至完全不必關心文件在fs.files和fs.chunks兩個集合中是怎么存儲的,讀取文件的時候又需要怎么關聯查詢,這些統統不用你操心!
當然,如果你不嫌麻煩,完全可以自己查詢fs.chunks集合進行文件讀取:
$mongo = new mongoClient('mongodb://localhost:27017');
$db = $mongo->cdn;
$collection = $db->selectCollection('fs.chunks');
$cursor = $collection->find(['files_id' => new MongoId('5a74206f2ff2de1c07a4bd2d')])->sort(['n' => 1]);
header('Content-type: application/pdf');
while($chunk = $cursor->getNext()) {//逐個chunk輸出,避免內存超出
echo $chunk['data']->bin;
}
這種讀取方式雖然麻煩了些,但是性能上比使用MongoGridFS類進行查詢要好一些,我自己進行了一下測試,讀取並在瀏覽器顯示一個56.9M的pdf文檔,使用MongoGridFS類讀取花費8秒多,而使用下面這種方法只要6秒多,節省了2秒鍾的時間!
3. 刪除文件:
刪除MongoDB中存儲的文件使用delete()方法:
$mongo = new mongoClient('mongodb://localhost:27017');
$db = $mongo->cdn;
$gridfs = $db->getGridFS();
$ret = $gridfs->delete(new MongoId('5a73e86f2ff2de2207a4bd2d'));
echo '
';
print_r($ret);
當然,還有remove()方法也可以進行文件的刪除。