標籤:
在各類系統應用服務端開發中,我們經常會遇到檔案儲存體的問題。 常見的磁碟檔案系統,DBMS傳統檔案流儲存。今天我們看一下基於NoSQL資料庫MongoDb的儲存方案。筆者環境 以CentOS 6.5,MongoDb 2.6.3, Nginx-1.4.7 為例,您需要瞭解Linux常用命令。
先來回顧一下MongoDb的內部檔案結構
- MongoDB在資料存放區上按命名空間來劃分,一個collection是一個命名空間,一個索引也是一個命名空間
- 同一個命名空間的資料被分成很多個Extent,Extent之間使用雙向鏈表串連
- 在每一個Extent中,儲存了具體每一行的資料,這些資料也是通過雙向連結串連的
- 每一行資料存放區空間不僅包括資料佔用空間,還可能包含一部分附加空間,這使得在資料update變大後可以不移動位置
- 索引以BTree結構實現
然後是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...") //檔案的位元據,這裡省略了具體內容}
檔案存入到GridFS過程中,如果檔案大於chunksize,則把檔案分割成多個chunk,再把這些chunk儲存到fs.chunks中,最後再把檔案資訊存入到fs.files中。
在讀取檔案的時候,先據查詢的條件,在fs.files中找到一個合適的記錄,得到“_id”的值,再據這個值到fs.chunks中尋找所有“files_id”為“_id”的chunk,並按“n”排序,最後依次讀取chunk中“data”對象的內容,還原成原來的檔案。
安裝Install與配置
1.安裝mongoDb
增加MongoDB Repository,不清楚vim,請參考VIM
vim /etc/yum.repos.d/mongodb.repo
如果是64bit的
[mongodb]
name=MongoDB Repository
baseurl=http://downloads-distro.mongodb.org/repo/redhat/os/x86_64/
gpgcheck=0
enabled=1
32bit的系統:
[mongodb]
name=MongoDB Repository
baseurl=http://downloads-distro.mongodb.org/repo/redhat/os/i686/
gpgcheck=0
enabled=1
然後安裝,會提示Y/N:
yum install mongo-10gen mongo-10gen-server
啟動:
service mongod start
查看狀態
service mongod status
停止
service mongod stop
更多,關於3.0以上版本,請參考官網。
2.安裝nginx及nginx-gridfs
依賴庫、工具
# yum -y install pcre-devel openssl-devel zlib-devel
# yum -y install gcc gcc-c++
下載nginx-gridfs源碼
# git clone https://github.com/mdirolf/nginx-gridfs.git
# cd nginx-gridfs
# git checkout v0.8
# git submodule init
# git submodule update
下載nginx源碼,編譯安裝。(高版本支援不好)
# wget http://nginx.org/download/nginx-1.4.7.tar.gz
# tar zxvf nginx-1.4.7.tar.gz
# cd nginx-1.4.7
# ./configure --with-openssl=/usr/include/openssl --add-module=../nginx-gridfs/
# make -j8 && make install –j8
注意藍色字元配置成對應nginx-gridfs的路徑
3. 配置nginx-gridfs
vim /usr/local/nginx/conf/nginx.conf
在 server 節點中添加 location 節點
location /img/ {
gridfs testdb
field=filename
type=string;
mongo 192.168.0.159:27017;
}
location /files/ {
gridfs testdb
field=_id
type=objectid;
mongo 192.168.0.159:27017;
}
這裡我們的mongo服務在IP 192.168.0.159。
如果不指定 field,預設為 MongoDB 的自增ID,且type為int
配置參數介紹:
gridfs:nginx識別外掛程式的關鍵字
testdb:db名
[root_collection]: 選擇collection,如root_collection=blog, mongod就會去找blog.files與blog.chunks兩個塊,預設是fs
[field]: 查詢欄位,保證mongdb裡有這個欄位名,支援_id, filename, 可省略, 預設是_id
[type]: 解釋field的資料類型,支援objectid, int, string, 可省略, 預設是int
[user]: 使用者名稱, 可省略
[pass]: 密碼, 可省略
mongo: mongodb url
啟動nginx服務
# /usr/local/nginx/sbin/nginx
可能出現:
Nginx [emerg]: bind() to 0.0.0.0:80 failed (98: Address already in use)
這時可用使用命令關閉佔用80連接埠的程式
sudo fuser -k 80/tcp
簡單測試
用原生的命令列上傳一個檔案
mongofiles put 937910.jpg --local ~/937910_100.jpg --host 192.168.0.159 --port 27017 --db testdb --type jpg
937910.jpg是我們提前下載好一個圖片檔案,注意我們沒有指定collection,預設是fs
從http://www.robomongo.org/安裝robomongo管理工具, 查看剛剛上傳的檔案
最後我們在瀏覽器訪問,如果看到圖片就OK了
http://192.168.0.159/img/937910.jpg
對於.net環境下mongodb CSharpDriver 1.10.0 從Nuget:
Install-Package mongocsharpdriver -Version 1.10.0
我們使用如下片段代碼:
int nFileLen = fileUploadModel.FileBytes.Length; MongoGridFSSettings fsSetting = new MongoGridFSSettings() { Root = CollectionName }; MongoGridFS fs = new MongoGridFS(mongoServer, MongoDatabaseName, fsSetting); //調用Write、WriteByte、WriteLine函數時需要手動設定上傳時間 //通過Metadata 添加附加資訊 MongoGridFSCreateOptions option = new MongoGridFSCreateOptions(); option.Id = ObjectId.GenerateNewId(); var currentDate = DateTime.Now; option.UploadDate = currentDate; option.Aliases = alias; BsonDocument doc = new BsonDocument(); //文檔附加資訊儲存 if(fileUploadModel.DocExtraInfo!=null&&fileUploadModel.DocExtraInfo.Count>0) { foreach(var obj in fileUploadModel.DocExtraInfo) { if (!doc.Elements.Any(p => p.Name == obj.Key)) { doc.Add(obj.Key, obj.Value); } } } option.Metadata = doc; //建立檔案,檔案並儲存資料 using (MongoGridFSStream gfs = fs.Create(fileUploadModel.FileName, option)) { gfs.Write(fileUploadModel.FileBytes, 0, nFileLen); gfs.Close(); } log.ErrorFormat("附件標識:{0} 檔案名稱:{1} 上傳成功", alias, fileUploadModel.FileName); return option.Id.ToString();
注意,目前gridfs-ngnix不支援_id類型是GUID的,關於ObjectId參考官網,如:
mongodb產生objectid還有一個更大的優勢,就是mongodb可以通過自身的服務來產生objectid,也可以通過用戶端的驅動程式來產生。
什麼時候使用Gridfs
來自官方2.6.10版本 手冊內容
For documents in a MongoDB collection, you should always use GridFS for storing files larger than 16 MB. In some situations, storing large files may be more efficient in a MongoDB database than on a system-level filesystem.
? If your filesystem limits the number of files in a directory, you can use GridFS to store as many files as needed.
? When you want to keep your files and metadata automatically synced and deployed across a number of systems and facilities. When using geographically distributed replica sets MongoDB can distribute files and their metadata automatically to a number of mongod instances and facilities.
? When you want to access information from portions of large files without having to load whole files into memory, you can use GridFS to recall sections of files without reading the entire file into memory.
Do not use GridFS if you need to update the content of the entire file atomically. As an alternative you can store multiple versions of each file and specify the current version of the file in the metadata. You can update the metadata field that indicates “latest” status in an atomic update after uploading the new version of the file, and later remove previous versions if needed.
Furthermore, if your files are all smaller the 16 MB BSON Document Size limit, consider storing the file manually within a single document. You may use the BinData data type to store the binary data. See your drivers documentation for details on using BinData.
資料庫主從同步
原理圖
是MongoDB採用Replica Sets模式的同步流程
- 紅色箭頭表示寫操作寫到Primary上,然後非同步同步到多個Secondary上
- 藍色箭頭表示讀操作可以從Primary或Secondary任意一個上讀
- 各個Primary與Secondary之間一直保持心跳同步檢測,用於判斷Replica Sets的狀態
資料分區機制
- MongoDB的分區是指定一個分區key來進行,資料按範圍分成不同的chunk,每個chunk的大小有限制
- 有多個分區節點儲存這些chunk,每個節點儲存一部分的chunk
- 每一個分區節點都是一個Replica Sets,這樣保證資料的安全性
- 當一個chunk超過其限制的最大體積時,會分裂成兩個小的chunk
- 當chunk在分區節點中分布不均衡時,會引發chunk遷移操作
分區時伺服器角色
上面講了分區的標準,下面是具體在分區時的幾種節點角色
- 用戶端訪問路由節點mongos來進行資料讀寫
- config伺服器儲存了兩個映射關係,一個是key值的區間對應哪一個chunk的映射關係,另一個是chunk存在哪一個分區節點的映射關係
- 路由節點通過config伺服器擷取資料資訊,通過這些資訊,找到真正存放資料的分區節點進行對應操作
- 路由節點還會在寫操作時判斷當前chunk是否超出限定大小,如果超出,就分列成兩個chunk
- 對於按分區key進行的查詢和update操作來說,路由節點會查到具體的chunk然後再進行相關的工作
- 對於不按分區key進行的查詢和update操作來說,mongos會對所有下屬節點發送請求然後再對返回結果進行合并
其它關於mongodb 的一些小提示:
MongoDB的32位版本也是不建議被使用的,因為你只能處理2GB大小的資料。還記得第一個限制嗎?這是MongoDB關於該限制的說明。
讓我感到驚訝的是,很少有人會查詢關於他們將要使用的工具的限制。幸好,MongoDB的開發人員發布了一篇MongoDB所有限制的部落格,你可以提前瞭解相關資訊,避免在使用過程中難堪。
儘管已經不建議被使用了,不過MongoDB還是提供了另外一種複製策略,即主從複製。它解決了12個節點限制問題,不過卻產生了新的問題:如果需要改變叢集的主節點,那麼你必須得手工完成,感到驚訝?看看這個連結吧。
MongoDB中資料複製的複製集策略非常棒,很容易配置並且使用起來確實不錯。但如果叢集的節點有12個以上,那麼你就會遇到問題。MongoDB中的複製集有12個節點的限制,這裡是問題的描述,你可以追蹤這個問題看看是否已經被解決了。
結論
Gridfs最適合大檔案儲存體 ,特別是視頻,音頻,大型圖片超過16MB大小的檔案。小型檔案也可以儲存,不過需要付出2次查詢代價(metadata與file content) [Tip#18 50 Tips and Tricks for MongoDB Developers]。不要修改隱藏檔的內容,而是更新檔案中繼資料如版本,或上傳新版本的檔案,刪除老版本的檔案。對於大量檔案儲存體時,需要多個資料節點,複製,資料分區等。別基於nginx訪問圖片檔案,瀏覽器沒有緩衝。 從互連網儲存圖片案例來看,圖片大都是jpg, png與縮圖檔案,分存式檔案系統(DFS)會是更好的解決方案。
資源:
GridFS官方
Building MongoDB Applications with Binary Files Using GridFS
希望對您軟體開發有協助。 其它您可能感興趣的文章:
能力素質模型諮詢工具(Part1)
公司專屬應用程式之效能即時度量系統演變
雲端運算參考架構幾例
智能移動導遊解決方案簡介
人力資源管理系統的演化
如有想瞭解更多軟體,系統 IT,公司資訊化 資訊,請關注我的訂閱號:
Petter Liu
出處:http://www.cnblogs.com/wintersun/
本文著作權歸作者和部落格園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文串連,否則保留追究法律責任的權利。
該文章也同時發布在我的獨立部落格中-Petter Liu Blog。
MongoDb gridfs-ngnix檔案儲存體方案