本實驗將按以下幾步進行:實驗環境monodevelop ,ubuntu10.04,實驗目的:探索分布式文件存儲方案
1、單機小文件的存儲,逐步增加上傳文件的大小,觀察mongoDB中文件對磁盤分配大小的變化。
2、采用分片的方式存儲大量的數據
實驗一:
首先建立一個數據庫gywdb,上傳一個574.5kB大小的文件,代碼如下:

using System; using System.Collections; using System.Collections.Generic; using MongoDB.Bson; using MongoDB.Driver; using MongoDB.Driver.GridFS; namespace mongoDBClient { class MainClass { public static void Main (string[] args) { //mongoDb服務實例連接字符串 string con="mongodb://localhost:27017"; //得到一個於mongoDB服務器連接的實例 MongoServer server=MongoServer.Create(con); //獲得一個與具體數據庫連接對象,數據庫名為gywdb MongoDatabase mydb=server.GetDatabase("gywdb"); //定義一個本地文件的路徑字符串 string localFileName="/home/guoyuanwei/學習資料/Google三大論文中文版.pdf"; //定義mongoDB數據庫中文件的名稱 string mongoDBFileName="Google三大論文中文版"; //設置GridFS文件中對應的集合前綴名 MongoGridFSSettings fsSetting=new MongoGridFSSettings(){Root="gyw"}; //實例化一個GridFS MongoGridFS gridfs=new MongoGridFS(mydb,fsSetting); //將本地文件上傳到mongoDB中去,以默認塊的大小256KB對文件進行分塊 gridfs.Upload(localFileName,mongoDBFileName); } } }
運行命令:> show collections
gyw.chunks
gyw.files
可以看到得到了一個前綴為gyw的文件元數據存儲的集合gyw.files和存儲文件數據塊的gyw.chunks
運行命令> db.gyw.chunks.find({},{"_id":1,"n":1})
{ "_id" : ObjectId("4fc0a6d91d41c808f45cbfec"), "n" : 0 }
{ "_id" : ObjectId("4fc0a6d91d41c808f45cbfed"), "n" : 1 }
{ "_id" : ObjectId("4fc0a6da1d41c808f45cbfee"), "n" : 2 }
得到3個塊數據,因為文件總大小為574.5KB,每個塊采用的是默認大小256KB,所以得到了3個塊。此時磁盤文件系統中,mongoDB自動生成了3個文件分別是:
gywdb.0(大小為64MB),gywdb.1(大小為128MB),gywdb.ns(存儲命名空間源數據),這里分配機制體現了mongoDB的文件分配策略,每個數據庫有一個.ns文件和若干個數據文件
,數據文件以遞增的數字結尾。每個新的以數字結尾的數據文件大小會加倍,直到達到最大值2GB,這是為了讓小數據庫不浪費太多的磁盤空間,同時讓大數據使用磁盤上連續的空間。
MongoDB為了保證性能還會預分配數據文件,這意味着MongoDB服務器總是試圖為每一個數據庫保留一個額外的空數據文件,來避免文件分配所產生的阻塞。
接着利用上面的代碼再上傳一個大小為2.4MB大小的文件到mongoDB的文件系統中
運行命令:> db.gyw.chunks.find({},{"_id":1,"n":1,"files_id":1})
{ "_id" : ObjectId("4fc0a6d91d41c808f45cbfec"), "files_id" : ObjectId("4fc0a6d91d41c808f45cbfe9"), "n" : 0 }
{ "_id" : ObjectId("4fc0a6d91d41c808f45cbfed"), "files_id" : ObjectId("4fc0a6d91d41c808f45cbfe9"), "n" : 1 }
{ "_id" : ObjectId("4fc0a6da1d41c808f45cbfee"), "files_id" : ObjectId("4fc0a6d91d41c808f45cbfe9"), "n" : 2 }
{ "_id" : ObjectId("4fc0a8cf1d41c80910191105"), "files_id" : ObjectId("4fc0a8cf1d41c80910191102"), "n" : 0 }
{ "_id" : ObjectId("4fc0a8cf1d41c80910191106"), "files_id" : ObjectId("4fc0a8cf1d41c80910191102"), "n" : 1 }
{ "_id" : ObjectId("4fc0a8cf1d41c80910191107"), "files_id" : ObjectId("4fc0a8cf1d41c80910191102"), "n" : 2 }
{ "_id" : ObjectId("4fc0a8cf1d41c80910191108"), "files_id" : ObjectId("4fc0a8cf1d41c80910191102"), "n" : 3 }
{ "_id" : ObjectId("4fc0a8cf1d41c80910191109"), "files_id" : ObjectId("4fc0a8cf1d41c80910191102"), "n" : 4 }
{ "_id" : ObjectId("4fc0a8cf1d41c8091019110a"), "files_id" : ObjectId("4fc0a8cf1d41c80910191102"), "n" : 5 }
{ "_id" : ObjectId("4fc0a8cf1d41c8091019110b"), "files_id" : ObjectId("4fc0a8cf1d41c80910191102"), "n" : 6 }
{ "_id" : ObjectId("4fc0a8cf1d41c8091019110c"), "files_id" : ObjectId("4fc0a8cf1d41c80910191102"), "n" : 7 }
{ "_id" : ObjectId("4fc0a8cf1d41c8091019110d"), "files_id" : ObjectId("4fc0a8cf1d41c80910191102"), "n" : 8 }
{ "_id" : ObjectId("4fc0a8cf1d41c8091019110e"), "files_id" : ObjectId("4fc0a8cf1d41c80910191102"), "n" : 9 }
可以看到塊中多了10個塊,而且第一次上傳的文件塊中“files_id" 為 ObjectId("4fc0a6d91d41c808f45cbfe9"),第二次上傳的文件為ObjectId("4fc0a8cf1d41c80910191102"),
這個files_id代表了這個塊是屬於那個文件的。如下命令顯示了文件系統中文件元數據信息
> db.gyw.files.find()
{ "_id" : ObjectId("4fc0a6d91d41c808f45cbfe9"), "filename" : "百度大規模數據處理", "length" : 588276, "chunkSize" : 262144, "uploadDate" : ISODate("2012-05-26T09:48:09.357Z"), "md5" : "5e55fb7496d41a52eb90daeac9e06936" }
{ "_id" : ObjectId("4fc0a8cf1d41c80910191102"), "filename" : "Google三大論文中文版", "length" : 2526950, "chunkSize" : 262144, "uploadDate" : ISODate("2012-05-26T09:56:31.143Z"), "md5" : "605f1deec1a277e3d878dfc6f3491cce" }
這個里面的"_id"值正好對應了上面塊中的“files_id”
這時在觀察磁盤文件系統中mongoDB自動生成的文件,發現和第一次一樣,說明數據還沒達到第一個數據文件gywdb.0的總大小(64MB)。
接着再利用上面的代碼上傳一個大小為112.2MB大小的文件到mongoDB的文件系統中,利用命令:
> db.gyw.files.find()
{ "_id" : ObjectId("4fc0a6d91d41c808f45cbfe9"), "filename" : "百度大規模數據處理", "length" : 588276, "chunkSize" : 262144, "uploadDate" : ISODate("2012-05-26T09:48:09.357Z"), "md5" : "5e55fb7496d41a52eb90daeac9e06936" }
{ "_id" : ObjectId("4fc0a8cf1d41c80910191102"), "filename" : "Google三大論文中文版", "length" : 2526950, "chunkSize" : 262144, "uploadDate" : ISODate("2012-05-26T09:56:31.143Z"), "md5" : "605f1deec1a277e3d878dfc6f3491cce" }
{ "_id" : ObjectId("4fc0b3a31d41c8099f80a9ac"), "filename" : "微軟官方2010年寬屏PPT圖表全集400張銳普PPT論壇首發", "length" : 117652480, "chunkSize" : 262144, "uploadDate" : ISODate("2012-05-26T10:42:43.773Z"), "md5" : "506810e215c773addc3ce10a035695d9" }
發現多了一個文件微軟官方2010年寬屏PPT圖表全集400張銳普PPT論壇首發,證明上傳成功了,再觀察磁盤文件系統中mongoDB自動生成的文件,發現多了一個gywdb.2(大小為256MB),這證明了mongoDB文件分配機制。
實驗二:數據的分布式存儲即分片測試
准備:兩台分片服務器(在同一台主機上,通過端口來區分,分別為:20000,20001),一台路由服務器(端口為:40000,所有的讀寫請求都必須經過它),一台配置服務器(端口為:30000)
root@ubuntu:/# mkdir -p /data/shard/s0
root@ubuntu:/# mkdir -p /data/shard/s1
root@ubuntu:/# mkdir -p /data/shard/log
上面的命令為建立兩個分片服務器的數據存放路徑以及日志文件路徑
root@ubuntu:/usr/local/mongoDB/bin# ./mongod --shardsvr --port 20000 --dbpath /data/shard/s0 --fork --logpath /data/shard/log/s0.log --directoryperdb
root@ubuntu:/usr/local/mongoDB/bin# ./mongod --shardsvr --port 20001 --dbpath /data/shard/s1 --fork --logpath /data/shard/log/s1.log --directoryperdb
上面的命令為啟動兩個分片服務器,注意其中各參數的意義
root@ubuntu:/# mkdir -p /data/shard/config
root@ubuntu:/usr/local/mongoDB/bin# ./mongod --configsvr --port 30000 --dbpath /data/shard/config --fork --logpath /data/shard/log/config.log --directoryperdb
上面的命令為啟動配置服務器端口為3000
# ./mongos --port 40000 --configdb localhost:30000 --fork --logpath /data/shard/log/route.log
上面的命令是啟動路由服務器,注意里面的參數configdb表示配置服務器的位置,因為路由服務器啟動時需要從配置服務器上獲取相應的信息
下面的命令是連接路由服務器並做一些配置:
root@ubuntu:/usr/local/mongoDB/bin# ./mongo admin --port 40000
MongoDB shell version: 2.0.4
connecting to: 127.0.0.1:40000/admin
mongos> db.runCommand({addshard:"localhost:20000"})
{ "shardAdded" : "shard0000", "ok" : 1 }
mongos> db.runCommand({addshard:"localhost:20001"})
{ "shardAdded" : "shard0001", "ok" : 1 }
mongos> db.runCommand({enablesharding:"userDB"})
{ "ok" : 1 }
上面的命令完成了將兩個服務器添加到分片集群中,在端口為40000的路由服務器上建立了一個數據庫userDB,並將此數據庫配置為可以進行分片。
接下來通過C#編寫代碼,完成文件的上傳,觀察數據被分片存儲的情況,代碼如下:

using System; using System.Collections; using System.Collections.Generic; using MongoDB.Bson; using MongoDB.Driver; using MongoDB.Driver.GridFS; namespace mongoDBClient { class MainClass { public static void Main (string[] args) { //mongoDb服務實例連接字符串 string con="mongodb://localhost:40000"; //得到一個於mongoDB服務器連接的實例 MongoServer server=MongoServer.Create(con); //獲得一個與具體數據庫連接對象,數據庫名為gywdb MongoDatabase mydb=server.GetDatabase("userDB"); //定義一個本地文件的路徑字符串 string localFileName="/home/guoyuanwei/學習資料/微軟官方2010年寬屏PPT圖表全集400張銳普PPT論壇首發.ppt"; //定義mongoDB數據庫中文件的名稱 string mongoDBFileName="微軟官方2010年寬屏PPT圖表全集400張銳普PPT論壇首發"; //設置GridFS文件中對應的集合前綴名 MongoGridFSSettings fsSetting=new MongoGridFSSettings(){Root="userdata"}; //實例化一個GridFS MongoGridFS gridfs=new MongoGridFS(mydb,fsSetting); //將本地文件上傳到mongoDB中去,以默認塊的大小256KB對文件進行分塊 gridfs.Upload(localFileName,mongoDBFileName); } } }
上傳了一個112.2MB的文件到路由服務器中。通過以下命令觀察下此時數據庫中集合情況
mongos> use userDB
switched to db userDB
mongos> show collections
system.indexes
userdata.chunks
userdata.files
此時數據庫中有新增加了兩個集合userdata.chunks和userdata.files,其中userdata.chunks是真正存儲數據的地方。以下命令觀察此集合的概況
mongos> db.userdata.chunks.stats()
{
"sharded" : false,
"primary" : "shard0000",
"ns" : "userDB.userdata.chunks",
"count" : 449,
"size" : 117711616,
"avgObjSize" : 262163.95545657014,
"storageSize" : 135131136,
"numExtents" : 12,
"nindexes" : 2,
"lastExtentSize" : 26034176,
"paddingFactor" : 1,
"flags" : 1,
"totalIndexSize" : 57232,
"indexSizes" : {
"_id_" : 24528,
"files_id_1_n_1" : 32704
},
"ok" : 1
}
其中 "sharded" : false表明此表還沒進行分片存儲,路由服務器此時將數據全部保存到分片0所對應的服務器中了,文件的大小為117711616字節,在linux下通過文件系統的命令:
root@ubuntu:/usr/local/mongoDB/bin# cd /data/shard/s0/userDB
root@ubuntu:/data/shard/s0/userDB# ls -l
總用量 475156
drwxr-xr-x 2 root root 4096 2012-05-27 16:57 _tmp
-rw------- 1 root root 67108864 2012-05-27 16:57 userDB.0
-rw------- 1 root root 134217728 2012-05-27 16:57 userDB.1
-rw------- 1 root root 268435456 2012-05-27 16:57 userDB.2
-rw------- 1 root root 16777216 2012-05-27 16:57 userDB.ns
發現文件確實被存在分片0所對應的服務器中。
繼續上傳更大的文件到路由服務器,突破分片設置中的默認塊的大小200MB,看是什么情況?代碼仍然如上,只是修改上傳的文件,上傳的文件大小為:393.0MB

using System; using System.Collections; using System.Collections.Generic; using MongoDB.Bson; using MongoDB.Driver; using MongoDB.Driver.GridFS; namespace mongoDBClient { class MainClass { public static void Main (string[] args) { //mongoDb服務實例連接字符串 string con="mongodb://localhost:40000"; //得到一個於mongoDB服務器連接的實例 MongoServer server=MongoServer.Create(con); //獲得一個與具體數據庫連接對象,數據庫名為gywdb MongoDatabase mydb=server.GetDatabase("userDB"); //定義一個本地文件的路徑字符串 string localFileName="/home/guoyuanwei/學習資料/Platform5.24.rar"; //定義mongoDB數據庫中文件的名稱 string mongoDBFileName="軟件源代碼"; //設置GridFS文件中對應的集合前綴名 MongoGridFSSettings fsSetting=new MongoGridFSSettings(){Root="userdata"}; //實例化一個GridFS MongoGridFS gridfs=new MongoGridFS(mydb,fsSetting); //將本地文件上傳到mongoDB中去,以默認塊的大小256KB對文件進行分塊 gridfs.Upload(localFileName,mongoDBFileName); } } }
首先通過以下命令觀察root@ubuntu:/data/shard/s0/userDB# ls -l
總用量 2048028
drwxr-xr-x 2 root root 4096 2012-05-27 17:22 _tmp
-rw------- 1 root root 67108864 2012-05-27 17:22 userDB.0
-rw------- 1 root root 134217728 2012-05-27 17:22 userDB.1
-rw------- 1 root root 268435456 2012-05-27 17:22 userDB.2
-rw------- 1 root root 536870912 2012-05-27 17:22 userDB.3
-rw------- 1 root root 1073741824 2012-05-27 17:22 userDB.4
-rw------- 1 root root 16777216 2012-05-27 17:22 userDB.ns
發現文件還是被存到默認的分片服務器0上。分片1上的數據還沒有。盡管文件的總大小已經達到了需要分片存儲的條件,即塊的大小200MB
因此下面需要重新設置路由服務器,使其對集合userDB.userdata.chunks(這個里面存儲了用戶上傳的文件)進行分片。
要想實現海量數據的分布式存儲,那么就要對集合進行分片,因此片鍵的選擇是至關重要的,它直接決定了集群中數據分布是否均衡、集群性能是否合理。那么我們究竟該選擇什么樣的字段來作為分片Key呢?這是個需要反復實踐總結的地方。由於這里利用了分布式文件系統GridFS,因此有幾點要說明(碰到錯誤,在網上查到的http://blog.csdn.net/zhangzhaokun/article/details/6324389):GridFS
根據需求的不同,GridFS有幾種不同的分片方法。基於預先存在的索引是慣用的分片辦法:
1)“files”集合(Collection)不會分片,所有的文件記錄都會位於一個分片上,高度推薦使該分片保持高度靈活(至少使用由3個節點構成的replica set)。
2)“chunks”集合(Collection)應該被分片,並且用索引”files_id:1”。已經存在的由MongoDB的驅動來創建的“files_id,n”索引不能用作分片Key(這個是一個分片約束,后續會被修復),所以不得不創建一個獨立的”files_id”索引。使用“files_id”作為分片Key的原因是一個特定的文件的所有Chunks都是在相同的分片上,非常安全並且允許運行“filemd5”命令(要求特定的驅動)。
前面已經分析過,在userDB.userdata.chunks集合中每一個記錄都有一個字段file_id,代表了此塊屬於哪個文件,因此這里建一個以file_id的索引,以此字段鍵做為片鍵,同一個文件的塊會被分配到同一個分片下
mongos> db.userdata.chunks.ensureIndex({files_id:1})
設置片鍵的命令如下:
mongos> db.runCommand({shardcollection:"userDB.userdata.chunks",key:{files_id:1}}
{ "collectionsharded" : "userDB.userdata.chunks", "ok" : 1 }
再次查看此表的狀態: "sharded" : true說明此集合被分片了。
mongos> db.userdata.chunks.stats()
{
"sharded" : true,
"flags" : 1,
"ns" : "userDB.userdata.chunks",
"count" : 2022,
"numExtents" : 19,
"size" : 529953820,
"storageSize" : 538714112,
"totalIndexSize" : 245280,
"indexSizes" : {
"_id_" : 73584,
"files_id_1" : 73584,
"files_id_1_n_1" : 98112
},
"avgObjSize" : 262093.87734915924,
"nindexes" : 3,
"nchunks" : 2,
"shards" : {
"shard0000" : {
"ns" : "userDB.userdata.chunks",
"count" : 2022,
"size" : 529953820,
"avgObjSize" : 262093.87734915924,
"storageSize" : 538714112,
"numExtents" : 19,
"nindexes" : 3,
"lastExtentSize" : 93306880,
"paddingFactor" : 1,
"flags" : 1,
"totalIndexSize" : 245280,
"indexSizes" : {
"_id_" : 73584,
"files_id_1_n_1" : 98112,
"files_id_1" : 73584
},
"ok" : 1
}
},
"ok" : 1
}
多次執行如下代碼上傳文件后:執行如下命令觀看系統中存在的文件
mongos> db.userdata.files.find()
{ "_id" : ObjectId("4fc1ec6c1d41c809ed4230d3"), "filename" : "微軟官方2010年寬屏PPT圖表全集400張銳普PPT論壇首發", "length" : 117652480, "chunkSize" : 262144, "uploadDate" : ISODate("2012-05-27T08:57:16.676Z"), "md5" : "506810e215c773addc3ce10a035695d9" }
{ "_id" : ObjectId("4fc1f2451d41c80b0f29eda1"), "filename" : "軟件源代碼", "length" : 412116413, "chunkSize" : 262144, "uploadDate" : ISODate("2012-05-27T09:22:13.443Z"), "md5" : "28cadbb180093dbcd15b25f0e741ae0a" }
{ "_id" : ObjectId("4fc1fe7d1d41c80b4d4a17cb"), "filename" : "新的軟件源代碼", "length" : 412116413, "chunkSize" : 262144, "uploadDate" : ISODate("2012-05-27T10:14:21.845Z"), "md5" : "28cadbb180093dbcd15b25f0e741ae0a" }
{ "_id" : ObjectId("4fc212541d41c80ba8e1180f"), "filename" : "新微軟PPT", "length" : 117652480, "chunkSize" : 262144, "uploadDate" : ISODate("2012-05-27T11:39:00.598Z"), "md5" : "506810e215c773addc3ce10a035695d9" }
{ "_id" : ObjectId("4fc21b9e1d41c80747e207e8"), "filename" : "新微軟PPT1", "length" : 117652480, "chunkSize" : 262144, "uploadDate" : ISODate("2012-05-27T12:18:38.183Z"), "md5" : "506810e215c773addc3ce10a035695d9" }
可以看到實驗上傳了5個文件
執行以下命令觀察文件分塊后在集群中的部署情況:
mongos> db.userdata.chunks.stats()
{
"sharded" : true,
"flags" : 0,
"ns" : "userDB.userdata.chunks",
"count" : 4493,
"numExtents" : 35,
"size" : 1177568872,
"storageSize" : 1274904576,
"totalIndexSize" : 531440,
"indexSizes" : {
"_id_" : 163520,
"files_id_1" : 155344,
"files_id_1_n_1" : 212576
},
"avgObjSize" : 262089.66659247718,
"nindexes" : 3,
"nchunks" : 3,
"shards" : {
"shard0000" : {
"ns" : "userDB.userdata.chunks",
"count" : 4044,
"size" : 1059857256,
"avgObjSize" : 262081.4183976261,
"storageSize" : 1139773440,
"numExtents" : 23,
"nindexes" : 3,
"lastExtentSize" : 193486848,
"paddingFactor" : 1,
"flags" : 0,
"totalIndexSize" : 449680,
"indexSizes" : {
"_id_" : 138992,
"files_id_1_n_1" : 179872,
"files_id_1" : 130816
},
"ok" : 1
},
"shard0001" : {
"ns" : "userDB.userdata.chunks",
"count" : 449,
"size" : 117711616,
"avgObjSize" : 262163.95545657014,
"storageSize" : 135131136,
"numExtents" : 12,
"nindexes" : 3,
"lastExtentSize" : 26034176,
"paddingFactor" : 1,
"flags" : 1,
"totalIndexSize" : 81760,
"indexSizes" : {
"_id_" : 24528,
"files_id_1_n_1" : 32704,
"files_id_1" : 24528
},
"ok" : 1
}
},
"ok" : 1
}
執行命令觀察分片的概況
printShardingStatus()
--- Sharding Status ---
sharding version: { "_id" : 1, "version" : 3 }
shards:
{ "_id" : "shard0000", "host" : "localhost:20000" }
{ "_id" : "shard0001", "host" : "localhost:20001" }
databases:
{ "_id" : "admin", "partitioned" : false, "primary" : "config" }
{ "_id" : "userDB", "partitioned" : true, "primary" : "shard0000" }
userDB.userdata.chunks chunks:
shard0000 2
shard0001 1
{ "files_id" : { $minKey : 1 } } -->> { "files_id" : ObjectId("4fc1f2451d41c80b0f29eda1") } on : shard0000 { "t" : 2000, "i" : 1 }
{ "files_id" : ObjectId("4fc1f2451d41c80b0f29eda1") } -->> { "files_id" : ObjectId("4fc21b9e1d41c80747e207e8") } on : shard0000 { "t" : 1000, "i" : 2 }
{ "files_id" : ObjectId("4fc21b9e1d41c80747e207e8") } -->> { "files_id" : { $maxKey : 1 } } on : shard0001 { "t" : 2000, "i" : 0 }
至此一個簡單的分布式文件存儲模型實驗完畢!