轉(zhuǎn)自:
http://blog.csdn.net/yczz/article/details/5974235
一、NoSQL簡(jiǎn)述
CAP(Consistency,Availabiity,Partition tolerance)理論告訴我們,一個(gè)分布式系統(tǒng)不可能滿足一致性,可用性和分區(qū)容錯(cuò)性這三個(gè)需求,最多只能同時(shí)滿足兩個(gè)。關(guān)系型數(shù)據(jù)庫(kù)通過把更新操作寫到事務(wù)型日志里實(shí)現(xiàn)了部分耐用性,但帶來的是寫性能的下降。MongoDB等NoSQL數(shù)據(jù)庫(kù)背后蘊(yùn)涵的哲學(xué)是不同的平臺(tái)應(yīng)該使用不同類型的數(shù)據(jù)庫(kù),MongoDB通過降低一些特性來達(dá)到性能的提高,這在很多大型站點(diǎn)中是可行的。因?yàn)?/span>MongoDB是非原子性的,所以如果如果應(yīng)用需要事務(wù),還是需要選擇MySQL等關(guān)系數(shù)據(jù)庫(kù)。
NoSQL數(shù)據(jù)庫(kù),顧名思義就是打破了傳統(tǒng)關(guān)系型數(shù)據(jù)庫(kù)的范式約束。很多NoSQL數(shù)據(jù)庫(kù)從數(shù)據(jù)存儲(chǔ)的角度看也不是關(guān)系型數(shù)據(jù)庫(kù),而是key-value數(shù)據(jù)格式的hash數(shù)據(jù)庫(kù)。由于放棄了關(guān)系數(shù)據(jù)庫(kù)強(qiáng)大的SQL查詢語言和事務(wù)一致性以及范式約束,NoSQL數(shù)據(jù)庫(kù)在很大程度上解決了傳統(tǒng)關(guān)系型數(shù)據(jù)庫(kù)面臨的諸多挑戰(zhàn)。
在社區(qū)中,NoSQL是指“not only sql”,其特點(diǎn)是非關(guān)系型,分布式,開源,可水平擴(kuò)展,模式自由,支持replication,簡(jiǎn)單的API,最終一致性(相對(duì)于即時(shí)一致性,最終一致性允許有一個(gè)“不一致性窗口”,但能保證最終的客戶都能看到最新的值)。
二、MongoDB簡(jiǎn)介
mongo取自“humongous”(海量的),是開源的文檔數(shù)據(jù)庫(kù)──nosql數(shù)據(jù)庫(kù)的一種。
MongoDB是一種面向集合(collection)的,模式自由的文檔(document)數(shù)據(jù)庫(kù)。
是一個(gè)高性能,開源,無模式的文檔型數(shù)據(jù)庫(kù),它在許多場(chǎng)景下可用于替代傳統(tǒng)的關(guān)系型數(shù)據(jù)庫(kù)或鍵/值存儲(chǔ)方式。Mongo使用C++開發(fā)。
面向集合是說數(shù)據(jù)被分成集合的形式,每個(gè)集合在數(shù)據(jù)庫(kù)中有惟一的名稱,集合可以包含不限數(shù)目的文檔。除了模式不是預(yù)先定義好的,集合與RDBMS中的表概念類似,雖然二者并不是完全對(duì)等。數(shù)據(jù)庫(kù)和集合的創(chuàng)建是“lazy”的,即只有在第一個(gè)document被插入時(shí)集合和數(shù)據(jù)庫(kù)才真正創(chuàng)建——這時(shí)在磁盤的文件系統(tǒng)里才能看見。
模式自由是說數(shù)據(jù)庫(kù)不需要知道存放在集合中的文檔的結(jié)構(gòu),完全可以在同一個(gè)集合中存放不同結(jié)構(gòu)的文檔,支持嵌入子文檔。
文檔類似于RDBMS中的記錄,以BSON(Binary Serialized dOcument Format)的格式保存。BSON是Binary JSON的簡(jiǎn)稱,是對(duì)JSON-like文檔的二進(jìn)制編碼序列化。像JSON(JavaScript Object Notation)一樣,BSON支持在對(duì)象和數(shù)組內(nèi)嵌入其它的對(duì)象和數(shù)組。有些數(shù)據(jù)類型在JSON里不能表示,但可以在BSON里表示,如Date類型和BinData(二進(jìn)制數(shù)據(jù)),Python原生的類型都可以表示。與Protocal Buffers(Google開發(fā)的用以處理對(duì)索引服務(wù)器請(qǐng)求/應(yīng)答的協(xié)議)相比,BSON模式更自由,所以更靈活,但這樣也使得每個(gè)文檔都要保存字段名,所以空間壓縮上不如Protocol Buffers。
BSON第一眼看上去像BLOB,但MongoDB理解BSON的內(nèi)部機(jī)制,所以MongoDB可以深入BSON對(duì)象的內(nèi)部,即使是嵌套的對(duì)象,這樣MongoDB就可以在頂層和嵌套的BSON對(duì)象上建立索引來應(yīng)對(duì)各種查詢了。
MongoDB可運(yùn)行在Linux、Windows和OS X平臺(tái),支持32位和64位應(yīng)用,默認(rèn)端口為27017。推薦運(yùn)行在64位平臺(tái),因?yàn)?/span>MongoDB為了提高性能使用了內(nèi)存映射文件進(jìn)行數(shù)據(jù)管理,而在32位模式運(yùn)行時(shí)支持的最大文件為2GB。
MongoDB查詢速度比MySQL要快,因?yàn)樗?/span>cache了盡可能多的數(shù)據(jù)到RAM中,即使是non-cached數(shù)據(jù)也非常快。當(dāng)前MongoDB官方支持的客戶端API語言就多達(dá)8種(C|C++|Java|Javascript|Perl|PHP|Python|Ruby),社區(qū)開發(fā)的客戶端API還有Erlang、Go、Haskell......
特點(diǎn)
高性能、易部署、易使用,存儲(chǔ)數(shù)據(jù)非常方便。主要功能特性有:
*面向集合存儲(chǔ),易存儲(chǔ)對(duì)象類型的數(shù)據(jù)。
*模式自由。
*支持動(dòng)態(tài)查詢。
*支持完全索引,包含內(nèi)部對(duì)象。
*支持查詢。
*支持復(fù)制和故障恢復(fù)。
*使用高效的二進(jìn)制數(shù)據(jù)存儲(chǔ),包括大型對(duì)象(如視頻等)。
*自動(dòng)處理切片,以支持云計(jì)算層次的擴(kuò)展性
*支持Python,PHP,Ruby,Java,C,C#,Javascript,Perl及C++語言的驅(qū)動(dòng)程序,社區(qū)中也提供了對(duì)Erlang 及.NET等平臺(tái)的驅(qū)動(dòng)程序。
*文件存儲(chǔ)格式為BSON(一種JSON的擴(kuò)展)
*可通過網(wǎng)絡(luò)訪問
功能
面向集合的存儲(chǔ):適合存儲(chǔ)對(duì)象及JSON形式的數(shù)據(jù)。
動(dòng)態(tài)查詢:Mongo支持豐富的查詢表達(dá)式。查詢指令使用JSON形式的標(biāo)記,可輕易查詢文檔中內(nèi)嵌的對(duì)象及數(shù)組。
完整的索引支持:包括文檔內(nèi)嵌對(duì)象及數(shù)組。Mongo的查詢優(yōu)化器會(huì)分析查詢表達(dá)式,并生成一個(gè)高效的查詢計(jì)劃。
查詢監(jiān)視:Mongo包含一個(gè)監(jiān)視工具用于分析數(shù)據(jù)庫(kù)操作的性能。
復(fù)制及自動(dòng)故障轉(zhuǎn)移:Mongo數(shù)據(jù)庫(kù)支持服務(wù)器之間的數(shù)據(jù)復(fù)制,支持主-從模式及服務(wù)器之間的相互復(fù)制。復(fù)制的主要目標(biāo)是提供冗余及自動(dòng)故障轉(zhuǎn)移。
高效的傳統(tǒng)存儲(chǔ)方式:支持二進(jìn)制數(shù)據(jù)及大型對(duì)象(如照片或圖片)
自動(dòng)分片以支持云級(jí)別的伸縮性:自動(dòng)分片功能支持水平的數(shù)據(jù)庫(kù)集群,可動(dòng)態(tài)添加額外的機(jī)器.
適用場(chǎng)合
網(wǎng)站數(shù)據(jù):Mongo非常適合實(shí)時(shí)的插入,更新與查詢,并具備網(wǎng)站實(shí)時(shí)數(shù)據(jù)存儲(chǔ)所需的復(fù)制及高度伸縮性。
緩存:由于性能很高,Mongo也適合作為信息基礎(chǔ)設(shè)施的緩存層。在系統(tǒng)重啟之后,由Mongo搭建的持久化緩存層可以避免下層的數(shù)據(jù)源 過載。
大尺寸,低價(jià)值的數(shù)據(jù):使用傳統(tǒng)的關(guān)系型數(shù)據(jù)庫(kù)存儲(chǔ)一些數(shù)據(jù)時(shí)可能會(huì)比較昂貴,在此之前,很多時(shí)候程序員往往會(huì)選擇傳統(tǒng)的文件進(jìn)行存儲(chǔ)。
高伸縮性的場(chǎng)景:Mongo非常適合由數(shù)十或數(shù)百臺(tái)服務(wù)器組成的數(shù)據(jù)庫(kù)。Mongo的路線圖中已經(jīng)包含對(duì)MapReduce引擎的內(nèi)置支持。
用于對(duì)象及JSON數(shù)據(jù)的存儲(chǔ):Mongo的BSON數(shù)據(jù)格式非常適合文檔化格式的存儲(chǔ)及查詢。
不適用場(chǎng)合
1.高度事務(wù)性的系統(tǒng):例如銀行或會(huì)計(jì)系統(tǒng)。傳統(tǒng)的關(guān)系型數(shù)據(jù)庫(kù)目前還是更適用于需要大量原子性復(fù)雜事務(wù)的應(yīng)用程序。
2.傳統(tǒng)的商業(yè)智能應(yīng)用:針對(duì)特定問題的BI數(shù)據(jù)庫(kù)會(huì)對(duì)產(chǎn)生高度優(yōu)化的查詢方式。對(duì)于此類應(yīng)用,數(shù)據(jù)倉(cāng)庫(kù)可能是更合適的選擇。
3.需要SQL的問題
三、術(shù)語介紹
數(shù)據(jù)庫(kù)、集合、文檔
每個(gè)MongoDB服務(wù)器可以有多個(gè)數(shù)據(jù)庫(kù),每個(gè)數(shù)據(jù)庫(kù)都有可選的安全認(rèn)證。數(shù)據(jù)庫(kù)包括一個(gè)或多個(gè)集合,集合以命名空間的形式組織在一起,用“.”隔開(類似于JAVA/Python里面的包),比如集合blog.posts和blog.authors都處于"blog"下,不會(huì)與bbs.authors有名稱上的沖突。集合里的數(shù)據(jù)由多個(gè)BSON格式的文檔對(duì)象組成,document的命名有一些限定,如字段名不能以"$"開頭,不能有".",名稱"_id"被保留為主鍵。
如果插入的文檔沒有提供“_id”字段,數(shù)據(jù)庫(kù)會(huì)為文檔自動(dòng)生成一個(gè)ObjectId對(duì)象作為“_id”的值插入到集合中。字段“_id”的值可以是任意類型,只要能夠保證惟一性。BSON ObjectID是一個(gè)12字節(jié)的值,包括4字節(jié)的時(shí)間戳,3字節(jié)的機(jī)器號(hào),2字節(jié)的進(jìn)程id以及3字節(jié)的自增計(jì)數(shù)。建議用戶還是使用有意義的“_id”值。
MongoDb相比于傳統(tǒng)的SQL關(guān)系型數(shù)據(jù)庫(kù),最大的不同在于它們的模式設(shè)計(jì)(Schema Design)上的差別,正是由于這一層次的差別衍生出其它各方面的不同。
如果將關(guān)系數(shù)據(jù)庫(kù)簡(jiǎn)單理解為由數(shù)據(jù)庫(kù)、表(table)、記錄(record)三個(gè)層次概念組成,而在構(gòu)建一個(gè)關(guān)系型數(shù)據(jù)庫(kù)的時(shí)候,工作重點(diǎn)和難點(diǎn) 都在數(shù)據(jù)庫(kù)表的劃分與組織上。一般而言,為了平衡提高存取效率與減少數(shù)據(jù)冗余之間的矛盾,設(shè)計(jì)的數(shù)據(jù)庫(kù)表都會(huì)盡量滿足所謂的第三范式。相應(yīng)的,可以認(rèn)為 MongoDb由數(shù)據(jù)庫(kù)、集合(collection)、文檔對(duì)象(Document-oriented、BSON)三個(gè)層次組成。MongoDb里的 collection可以理解為關(guān)系型數(shù)據(jù)庫(kù)里的表,雖然二者并不完全對(duì)等。當(dāng)然,不要期望collection會(huì)滿足所謂的第三范式,因?yàn)樗鼈兏揪筒?在同一個(gè)概念討論范圍之內(nèi)。類似于表由多條記錄組成,集合也包含多個(gè)文檔對(duì)象,雖然說一般情況下,同一個(gè)集合內(nèi)的文檔對(duì)象具有相同的格式定義,但這并不是 必須的,即MongoDb的數(shù)據(jù)模式是自由的(schema-free、模式自由、無模式),collection中可以包含具有不同schema的文檔 記錄,支持嵌入子文檔。
四、MongoDB資源消耗
考慮到性能的原因,mongo做了很多預(yù)分配,包括提前在文件系統(tǒng)中為每個(gè)數(shù)據(jù)庫(kù)分配逐漸增長(zhǎng)大小的文件集。這樣可以有效地避免潛在的文件系統(tǒng)碎片,使數(shù)據(jù)庫(kù)操作更高效。
一個(gè)數(shù)據(jù)庫(kù)的文件集從序號(hào)0開始分配,0,1...,大小依次是64M,128M,256M,512M,1G,2G,然后就是一直2G的創(chuàng)建下去(32位系統(tǒng)最大到512M)。所以如果上一個(gè)文件是1G,而數(shù)據(jù)量剛好超過1G,則下一個(gè)文件(大小為2G)則可能有超過90%都是空的。
如果想使磁盤利用更有效率,下面是一些解決方法:
1. 只建立一個(gè)數(shù)據(jù)庫(kù),這樣最多只會(huì)浪費(fèi)2G。
2. 每個(gè)文檔使用自建的“_id”值而不要使用默認(rèn)的ObjectId對(duì)象。
3. 由于每個(gè)document的每個(gè)字段名都會(huì)存放,所以如果字段名越長(zhǎng),document的數(shù)據(jù)占用就會(huì)越大,因此把字段名縮短會(huì)大大降低數(shù)據(jù)的占用量。如把“timeAdded”改為“tA”。
Mongo使用內(nèi)存映射文件來訪問數(shù)據(jù),在執(zhí)行插入等操作時(shí),觀察mongod進(jìn)程的內(nèi)存占用時(shí)會(huì)發(fā)現(xiàn)量很大,當(dāng)使用內(nèi)存映射文件時(shí)是正常的。并且映射數(shù)據(jù)的大小只出現(xiàn)在虛擬內(nèi)存那一列,常駐內(nèi)存量才反應(yīng)出有多少數(shù)據(jù)cached在內(nèi)存中。
【按照mongodb官方的說法,mongodb完全由系統(tǒng)內(nèi)核進(jìn)行內(nèi)存管理,會(huì)盡可能的占用系統(tǒng)空閑內(nèi)存,用free可以看到,大部分內(nèi)存都是作為io cache被占用的,而這部分內(nèi)存是可以釋放出來給應(yīng)用使用的。】
五、交互式shell
mongo類似于MySQL中的mysql進(jìn)程,但功能遠(yuǎn)比mysql強(qiáng)大,它可以使用JavaScript語法的命令從交互式shell中直接操作數(shù)據(jù)庫(kù)。如查看數(shù)據(jù)庫(kù)中的內(nèi)容,使用游標(biāo)循環(huán)查看查詢結(jié)果,創(chuàng)建索引,更改及刪除數(shù)據(jù)等數(shù)據(jù)庫(kù)管理功能。下面是一個(gè)在mongo中使用游標(biāo)的例子:
> for(var cur = db.posts.find(); cur.hasNext();) {
... print(tojson(cur.next()));
... }
輸出:
{
"_id" : ObjectId("4bb311164a4a1b0d84000000"),
"date" : "Wed Mar 31 17:05:23 2010",
"content" : "blablablabla",
"author" : "navygong",
"title" : "the first blog"
}
...其它的documents。
六、一般功能
6.1插入
客戶端把數(shù)據(jù)序列化為BSON格式傳給DB后被存儲(chǔ)在磁盤上,在讀取時(shí)數(shù)據(jù)庫(kù)幾乎不做什么改動(dòng)直接把對(duì)象返回給客戶端,由client完成unserialized。如:
> doc = {'author': 'joe', 'created': new Date('2010, 6, 21'), 'title':'Yet another blog post', 'text': 'Here is the text...', 'tags': ['example', 'joe'], 'comments': [{'author': 'jim', 'comment': 'I disgree'}, {'author': 'navy', 'comment': 'Good post'}], '_id': 'test_id'}
> db.posts.insert(doc)
6.2查詢
基本上你能想到的查詢種類MongoDB都支持,如等值匹配,<,<=,>, >=,$ne,$in,$mod,$all,$size[1],$exists,$type[2],正則表達(dá)式匹配,全文搜索,......。還有distinct(),sort(),count(),skip()[3],group()[4],......。這里列表的查詢中很多用法都和一般的RDBMS不同。
[1] 匹配一個(gè)有size個(gè)元素的數(shù)組。如db.things.find({a: {$size: 1}})能夠匹配文檔{a: ["foo"]}。
[2] 根據(jù)類型匹配。db.things.find({a : {$type : 16}})能夠匹配所有a為int類型的文檔。BSON協(xié)議中規(guī)定了各種類型對(duì)應(yīng)的枚舉值。
[3] 指定跳過多少個(gè)文檔后開始返回結(jié)果,可以用在分頁(yè)中。如:db.students.find().skip((pageNumber-1)*nPerPage).limit(nPerPage).forEach( function(student) { print(student.name + "<p>"); } )。
[4] 在sharded MongoDB配置環(huán)境中應(yīng)該應(yīng)該使用map/reduce來代替group()。
6.3刪除
可以像查詢一樣指定條件來刪除特定的文檔。
6.4索引
可以像在一般的RDBMS中一樣使用索引。提供了建立(一般、惟一、組合)索引、刪除索引、重建索引等各種方法。索引信息保存在集合“system.indexes”中。
6.5map/reduce
MongoDB提供了map/reduce方法來進(jìn)行數(shù)據(jù)的批處理及聚集操作。和Hadoop的使用類似,從集合中接收輸入,結(jié)果輸出到另一個(gè)集合。如果你需要使用group,map/reduce會(huì)是個(gè)不錯(cuò)的選擇。但MongoDB中的索引和標(biāo)準(zhǔn)查詢不是使用map/reduce,而是與MySQL相似。
七、模式設(shè)計(jì)
Mongo式的模式設(shè)計(jì)
使用Mongo有很多種方式,你本能上可能會(huì)像使用關(guān)系型數(shù)據(jù)庫(kù)一樣去使用。當(dāng)然這樣也可以工作得很好,但卻沒能發(fā)揮出Mongo的真正威力。Monog是專門設(shè)計(jì)為富對(duì)象模型(rich object model)使用的。
例如:如果你建立了一個(gè)簡(jiǎn)單的在線商店并且把產(chǎn)品信息存儲(chǔ)在關(guān)系型數(shù)據(jù)庫(kù)中,那你可能會(huì)有兩個(gè)像這樣的表:
item
item_features
sku | feature_name | feature_value |
你進(jìn)行了范式處理因?yàn)椴煌奈锲酚胁煌奶卣?,這樣你不用建立一個(gè)包含所有特征的表了。在Mongo中你也可以像上面那樣建立兩個(gè)集合,但像下面這樣存儲(chǔ)每種物品會(huì)更有效。
item : {
"title" : <title> ,
"price" : <price> ,
"sku" : <sku> ,
"features" : {
"optical zoom" : <value> ,
...
}
}
因?yàn)橹灰樵円粋€(gè)集合就能取得一件物品的所有信息,而這些信息都保存在磁盤上同一個(gè)地方,因此大大提高了查詢的速度。如果你想插入或更新一種特征,如db.items.update( { sku : 123 } , { "$set" : { "features.zoom" : "5" } } ),也不必在磁盤上移動(dòng)整個(gè)對(duì)象,因?yàn)?/span>Mongo為每個(gè)對(duì)象在磁盤上預(yù)留了空間來適應(yīng)對(duì)象的增長(zhǎng)。
八、嵌入與引用
以一實(shí)例來說,假設(shè)需要設(shè)計(jì)一個(gè)小型數(shù)據(jù)庫(kù)來存儲(chǔ)“學(xué)生、地址、科目、成績(jī)”這些信息,那么關(guān)系型數(shù)據(jù)庫(kù)的設(shè)計(jì)如圖1所示,而key-value型數(shù)據(jù)庫(kù)的設(shè)計(jì)則可能如圖2所示。
圖1 關(guān)系型的數(shù)據(jù)庫(kù)設(shè)計(jì)
圖2 key-value型的數(shù)據(jù)庫(kù)設(shè)計(jì)
對(duì)比圖1和圖2,在關(guān)系型的數(shù)據(jù)庫(kù)設(shè)計(jì)里劃分出了4個(gè)表,而在key-value型的數(shù)據(jù)庫(kù)設(shè)計(jì)里卻只有兩個(gè)集合。如果說集合與表一一對(duì)應(yīng)的話,那 么圖2中應(yīng)該也有4個(gè)集合才對(duì),把本應(yīng)該是集合的address和scores直接合入了集合students中,原因在于在key-value型的數(shù)據(jù) 庫(kù)里,數(shù)據(jù)模式是自由的。
以scores來說,在關(guān)系型的數(shù)據(jù)庫(kù)設(shè)計(jì)中將其單獨(dú)成一個(gè)表是因?yàn)閟tudent與score是一對(duì)多的關(guān)系,如果將score合入 student表,那么就必須預(yù)留最多可能的字段,這會(huì)存在浪費(fèi),并且當(dāng)以后新增一門課程時(shí)擴(kuò)展困難,因此一般都會(huì)將score表單獨(dú)出來。而對(duì)于 key-value型的數(shù)據(jù)庫(kù)就不同了,其scores字段就是一個(gè)BSON,該BSON可以只有一個(gè)for_course,也可以有任意多個(gè) for_course,其固有的模式自由特性使得它可以將score包含在內(nèi)而無需另建一個(gè)score集合。
對(duì)于與student為一對(duì)一關(guān)系的address表也可以直接合入student,無需擔(dān)心address的擴(kuò)展性,當(dāng)以后需要給address新增一個(gè)province字段,直接在數(shù)據(jù)插入時(shí)加上這個(gè)值即可。
當(dāng)然,對(duì)于與student成多對(duì)多關(guān)系course表,為了減少數(shù)據(jù)冗余,可以將course建立為一個(gè)集合,同關(guān)系型的數(shù)據(jù)庫(kù)設(shè)計(jì)中類似。
students文檔中嵌入了address文檔和scores文檔,scores文檔的“for_course”字段的值是指向courses集合的文檔的引用。如果是關(guān)系型數(shù)據(jù)庫(kù),需要把“scores”作為一個(gè)單獨(dú)的表,然后在students表中建立一個(gè)指向“scores”的外鍵。所以Mongo模式設(shè)計(jì)中的一個(gè)關(guān)鍵問題就是“是值得為這個(gè)對(duì)象新建一個(gè)集合呢,還是把這個(gè)對(duì)象嵌入到其它的集合中”。在關(guān)系型數(shù)據(jù)庫(kù)中為了范式的要求,每個(gè)子項(xiàng)都要建一個(gè)單獨(dú)的表,但在Mongo中使用嵌入式對(duì)象更有效,所以你應(yīng)該給出不使用嵌入式對(duì)象而單獨(dú)建一個(gè)集合的理由。
為什么說引用要慢些呢,以上面的students集合為例,比如執(zhí)行:
print( student.scores[0].for_course.name );
如果這是第一次訪問scores[0],那些客戶端必須執(zhí)行:
student.scores[0].for_course = db.courses.findOne({_id:_course_id_to_find_}); //偽代碼
所以每一次遍歷引用都要對(duì)數(shù)據(jù)庫(kù)進(jìn)行一次這樣的查詢,即使所有的數(shù)據(jù)都在內(nèi)存中。再考慮到從客戶端到服務(wù)器端的種種延遲,這個(gè)時(shí)間也不會(huì)低。
有一些規(guī)則可以決定該用嵌入還是引用:
1. 第一個(gè)類對(duì)象,也就是處于頂層的,往往應(yīng)該有自己的集合。
2. 排列項(xiàng)詳情對(duì)象應(yīng)該用嵌入。
3. 處于被包含關(guān)系的應(yīng)該用嵌入。
4. 多對(duì)多的關(guān)系通常應(yīng)該用引用。
5. 數(shù)據(jù)量小的集合可以放心地做成一個(gè)單獨(dú)的集合,因?yàn)檎麄€(gè)集合可以很快地cached。
6. 要想獲得嵌入式對(duì)象的系統(tǒng)級(jí)視圖會(huì)更困難一些。如上面的“Scores”如果不做成嵌入式對(duì)象可以更容易地查詢出分?jǐn)?shù)排名前100的學(xué)生。
7. 如果嵌入的是大對(duì)象,需要留意到BSON對(duì)象的4M大小限定(后面會(huì)講到)。
8. 如果性能是關(guān)鍵就用嵌入。
下面是一些示例:
1. Customer/Order/Order Line-Item
cutomers和orders應(yīng)該做成一個(gè)集合,line-items應(yīng)該以數(shù)組的形式嵌入在order中。
2. 博客系統(tǒng)
posts應(yīng)該是一個(gè)集合;author可以是一個(gè)單獨(dú)的集合,如果只需記錄作者的email地址也可以以字段的方式存在于posts中;comments應(yīng)該做成嵌入的對(duì)象。
九、GridFS
GridFS是MongoDB中用來存儲(chǔ)大文件而定義的一種文件系統(tǒng)。MongoDB默認(rèn)是用BSON格式來對(duì)數(shù)據(jù)進(jìn)行存儲(chǔ)和網(wǎng)絡(luò)傳輸。但由于BSON文檔對(duì)象在MongoDB中最大為4MB,無法存儲(chǔ)大的對(duì)象。即使沒有大小限制,BSON也無法滿足對(duì)大數(shù)據(jù)集的快速范圍查詢,所以MongoDB引進(jìn)了GridFS。
9.1GridFS表示的對(duì)象信息
1. 文件對(duì)象(類GridFSFile 的對(duì)象)的元數(shù)據(jù)信息。結(jié)構(gòu)如下
{
"_id" : <unspecified>, // unique ID for this file
"filename" : data_string, // human name for the file
"contentType" : data_string, // valid mime type for the object
"length" : data_number, // size of the file in bytes
"chunkSize" : data_number, // size of each of the chunks. Default is 256k
"uploadDate" : data_date, // date when object first stored
"aliases" : data_array of data_string, // optional array of alias strings
"metadata" : data_object, // anything the user wants to store
"md5" : data_string //result of running "filemd5" command on the file's chunks
}
如下是put進(jìn)去的一個(gè)文件例子:
{
_id: ObjId(4bbdf6200459d967be9d8e98),
filename: "/home/hjgong/source_file/wnwb.svg",
length: 7429,
chunkSize: 262144,
uploadDate: new Date(1270740513127),
md5: "ccd93f05e5b9912c26e68e9955bbf8b9"
}
2. 數(shù)據(jù)的二進(jìn)制塊以及一些統(tǒng)計(jì)信息。結(jié)構(gòu)如下:
{
"_id": <unspecified>, // object id of the chunk in the _chunks collection
"files_id": <unspecified>, // _id value of the owning {{files}} collection entry
"n": data_number, // "chunk number" - starting with 0
"data": data_binary (type 0x02), // binary data for chunk
}
因此使用GridFS可以儲(chǔ)存富媒體文件,同時(shí)存入任意的附加信息,因?yàn)檫@些信息實(shí)際上也是一個(gè)普通的collection。以前,如果要存儲(chǔ)一個(gè)附件,通常的做法是,在主數(shù)據(jù)庫(kù)中存放文件的屬性同時(shí)記錄文件的path,當(dāng)查詢某個(gè)文件時(shí),需要首先查詢數(shù)據(jù)庫(kù),獲得該文件的path,然后從存儲(chǔ)系統(tǒng)中獲得相應(yīng)的文件。在使用GridFS時(shí)則非常簡(jiǎn)單,可以直接將這些信息直接存儲(chǔ)到文件中。比如下面的Java代碼,將文件file(file可以是圖片、音頻、視頻等文件)儲(chǔ)存到db中:
其中該方法的第一個(gè)參數(shù)的類型還可以是InputStream,byte[],從而實(shí)現(xiàn)多個(gè)重載的方法。
9.2GridFS管理
MongoDB提供的工具mongofiles可以從命令行操作GridFS。如:
./mongofiles -host localhost:1727 -u navygong -p 111 put ~/source_file/wnwb.svg
每種語言提供的MongoDB客戶端API都提供了一套方法,可以像操作普通文件一樣對(duì)GridFS文件進(jìn)行操作,包括read(),write(),tell(),seek()等。
十、Replication(復(fù)制)
Mongo提供了兩種方式的復(fù)制:簡(jiǎn)單的master-slave配置及replica pair的概念。
如果安全認(rèn)證被enable,不管哪種replicate方式,都要在master/slave中創(chuàng)建一個(gè)能為各個(gè)database識(shí)別的用戶名/密碼。認(rèn)證步驟如下:
slave先在local.system.users里查找一個(gè)名為"repl"的用戶,找到后用它去認(rèn)證master。如果"repl"用戶沒有找到,則使用local.system.users中的第一個(gè)用戶去認(rèn)證。local數(shù)據(jù)庫(kù)和admin數(shù)據(jù)庫(kù)一樣,local中的用戶可以訪問整個(gè)db server。
10.1master-slave模式
一個(gè)server可以同時(shí)為master和slave。一個(gè)slave可以有多個(gè)master,這種方式并不推薦,因?yàn)榭赡軙?huì)產(chǎn)生不可預(yù)期的結(jié)果。
在該模式中,一般是在兩個(gè)不同的機(jī)器上各部署一個(gè)MongDB實(shí)例,一個(gè)為master,另一作為slave。將MongoDB作為master啟動(dòng),只需要在命令行輸入:
然后主服務(wù)進(jìn)程將會(huì)在數(shù)據(jù)庫(kù)中創(chuàng)建一個(gè)集合local.oplog.$main,該collection主要記錄了事務(wù)日志,即需要在slave執(zhí)行的操作。
而將MongoDB作為slave啟動(dòng),只需要在命令行輸入:
./mongod --slave --source <masterhostname>[:<port>] |
port不指定時(shí)即使用默認(rèn)端口,masterhostname是master的IP或master機(jī)器的FQDN。
其他配置選項(xiàng):
--autoresync:自動(dòng)sync,但在10分鐘內(nèi)最多只會(huì)進(jìn)行一次。
--oplogSize:指定master上用于存放更改的數(shù)據(jù)量,如果不指定,在32位機(jī)上最少為50M,在64位機(jī)上最少為 1G,最大為磁盤空間的5%。
10.2replica pairs模式
以這種方式啟動(dòng)后,數(shù)據(jù)庫(kù)會(huì)自動(dòng)協(xié)商誰是master誰是slave。一旦一個(gè)數(shù)據(jù)庫(kù)服務(wù)器斷電,另一個(gè)會(huì)自動(dòng)接管,并從那一刻起起為master。萬一另一個(gè)將來也出錯(cuò)了,那么master狀態(tài)將會(huì)轉(zhuǎn)回給第一個(gè)服務(wù)器。以這種復(fù)制方式啟動(dòng)本地MongoDB的命令如下:
./mongod --pairwith <remoteserver> --arbiter <arbiterserver> |
其中remoteserver是pair里的另一個(gè)server,arbiterserver是一個(gè)起仲裁作用的Mongo數(shù)據(jù)庫(kù)服務(wù)器,用來協(xié)商pair中哪一個(gè)是master。arbiter運(yùn)行在第三個(gè)機(jī)器上,利用“平分決勝制”決定在pair中的兩臺(tái)機(jī)器不能聯(lián)系上對(duì)方時(shí)讓哪一個(gè)做master,一般是能同arbiter通話的那臺(tái)機(jī)器做master。如果不加--arbiter選項(xiàng),出現(xiàn)網(wǎng)絡(luò)問題時(shí)兩臺(tái)機(jī)器都作為master。命令db.$cmd.findOne({ismaster:1})可以檢查當(dāng)前哪一個(gè)database是master。
pair中的兩臺(tái)機(jī)器只能滿足最終一致性。當(dāng)replica pair中的一臺(tái)機(jī)器完全掛掉時(shí),需要用一臺(tái)新的來代替。如(n1, n2)中的n2掛掉,這時(shí)用n3來代替n2。步驟如下:
1. 告訴n1用n3來代替n2:db.$cmd.findOne({replacepeer:1});
2. 重啟n1讓它同n3對(duì)話:./mongod --pairwith n3 --arbiter <arbiterserver>
3. 啟動(dòng)n3:./mongod --pairwith n1 --arbiter <arbiterserver>。
在n3的數(shù)據(jù)沒有同步到n1前n3還不能做master,這個(gè)過程長(zhǎng)短由數(shù)據(jù)量的多少?zèng)Q定。
10.3受限的master-master復(fù)制
Mongo不支持完全的master-master復(fù)制,通常情況下不推薦使用master-master模式,但在一些特定的情況下master-master也可用。master-master也只支持最終一致性。配置master-master只需運(yùn)行mongod時(shí)同時(shí)加上--master選項(xiàng)和--slave選項(xiàng)。如下:
$ nohup mongod --dbpath /data1/db --port 27017 --master --slave --source localhost:27018 > /tmp/dblog1 & $ nohup mongod --dbpath /data2/db --port 27018 --master --slave --source localhost:27017 > /tmp/dblog2 & |
這種模式對(duì)插入、查詢及根據(jù)_id進(jìn)行的刪除操作都是安全的。但對(duì)同一對(duì)象的并發(fā)更新無法進(jìn)行。
十一、Sharding(分片)
11.1sharding介紹
MongoDB包括一個(gè)自動(dòng)分片的的模塊(“mongos”),從而可以構(gòu)建一個(gè)大的水平可擴(kuò)展的數(shù)據(jù)庫(kù)集群,可以動(dòng)態(tài)地添加和移走機(jī)器。如下是一個(gè)數(shù)據(jù)庫(kù)集群的示意圖:
mongod:數(shù)據(jù)庫(kù)服務(wù)器進(jìn)程,類似于mysqld。
shards:每個(gè)shard有一個(gè)或多個(gè)mongod,通常是一個(gè)master,多個(gè)slave組成replication。數(shù)據(jù)由集合按一個(gè)預(yù)定的順序劃分,某一個(gè)范圍的數(shù)據(jù)被放到一個(gè)特定的shard中,這樣可以通過shard的key進(jìn)行有效的范圍查詢。
shard keys:用于劃分集合,格式類似于索引的定義,也是把一個(gè)或多個(gè)字段作為key,以key來分布數(shù)據(jù)。如:{ name : 1 (1代表升序,-1代表降序)}、{ _id : 1 }、{ lastname : 1, firstname : 1 }、{ tag : 1, timestamp : -1 }。如果有100萬人同名,可能還需要?jiǎng)澐?,因?yàn)榉诺揭粋€(gè)塊里太大了,這時(shí)定義的shar key不能只有一個(gè)name字段了。劃分能夠保證相鄰的數(shù)據(jù)存儲(chǔ)在一個(gè)server(當(dāng)然也在相同的塊上)。
chunks:是一個(gè)集合里某一范圍的數(shù)據(jù),(collection, minkey, maxkey)描述了一個(gè)chunk。塊的大小有限定,當(dāng)塊里的數(shù)據(jù)超過最大值,塊會(huì)一分為二。如果一個(gè)shard里的數(shù)據(jù)過多(添加shard時(shí),可以指定這個(gè)shard上可以存放的最大數(shù)據(jù)量maxSize),就會(huì)有塊遷移到其它的shard。同樣,當(dāng)添加新的server時(shí),為了平衡各個(gè)server的負(fù)載,也會(huì)遷移chunk過去。
config server(配置服務(wù)器):存儲(chǔ)了集群的元信息,包括每一個(gè)shard、一個(gè)shard里的server、以及每一個(gè)chunk的基本信息。其中主要是chunk的信息,每個(gè)config server中都有一份所有chunk信息的完全拷貝。使用兩階段提交協(xié)議來保證配置信息在config server間的一致。mongos:可以認(rèn)為是一個(gè)“數(shù)據(jù)庫(kù)路由器”,用以協(xié)調(diào)集群的各個(gè)部分,使它們看起來像一個(gè)系統(tǒng)。mongos沒有固定的狀態(tài),可以在 server需要的時(shí)候運(yùn)行。mongos啟動(dòng)后會(huì)從config server里取出元信息,然后接收客戶請(qǐng)求,把請(qǐng)求路由到合適的server,得到結(jié)果后送回客戶。一個(gè)系統(tǒng)可以有多個(gè)mongos例程,每個(gè)例程都需要內(nèi)存來存儲(chǔ)元信息。例程間不需協(xié)同工作,每個(gè)mongos只需要協(xié)同shard servers和config servers工作即可。當(dāng)然shard servers間也會(huì)彼此對(duì)話,也會(huì)同config servers對(duì)話。
11.2sharding的配置和管理
mongod的啟動(dòng)選項(xiàng)中也包含了與sharding相關(guān)的參數(shù),如--shardsvr(聲明這是一個(gè)shard db),--configsvr(聲明這是一個(gè)config db)。mongos的啟動(dòng)選項(xiàng)--configdb指定config server的位置。下面的鏈接地址是一個(gè)簡(jiǎn)單的sharding配置例子:http://www.mongodb.org/display/DOCS/A+Sample+Configuration+Session。
像安全和認(rèn)證一樣,如果要sharding,先要允許一個(gè)數(shù)據(jù)庫(kù)sharding,然后要指定數(shù)據(jù)庫(kù)里集合的分片方式,這些都有相應(yīng)的命令可以完成。
十二、Java API簡(jiǎn)介
要使用Java操作MongoDB,在官網(wǎng)上下載jar包,目前最新的版本是:mongo-2.0.jar。首先介紹一下比較常用的幾個(gè)類:
Mongo:連接服務(wù)器,執(zhí)行一些數(shù)據(jù)庫(kù)操作的選項(xiàng),如新建立一個(gè)數(shù)據(jù)庫(kù)等;
DB:對(duì)應(yīng)一個(gè)數(shù)據(jù)庫(kù),可以用來建立集合等操作;
DBCollection:對(duì)應(yīng)一個(gè)集合(類似表),可能是我們用得最多的,可以添加刪除記錄等;
DBObject接口和BasicDBObject對(duì)象:表示一個(gè)具體的記錄,BasicDBObject實(shí)現(xiàn)了DBObject,因?yàn)槭?/span>key-value的數(shù)據(jù)結(jié)構(gòu),所以用起來其實(shí)和HashMap是基本一致的;
DBCursor:用來遍歷取得的數(shù)據(jù),實(shí)現(xiàn)了Iterable和Iterator。
下面以一段簡(jiǎn)單的例子說明:
十三、MongoDB實(shí)例分析
下面通過一個(gè)實(shí)例說明如何用MongoDB作為數(shù)據(jù)庫(kù)。該實(shí)例中有一個(gè)user實(shí)體,包含一個(gè)name屬性,每個(gè)user對(duì)應(yīng)一到多個(gè)圖片image。按照關(guān)系型數(shù)據(jù)庫(kù)設(shè)計(jì),可以設(shè)計(jì)一個(gè)user表和一個(gè)image表,其中image表中有一個(gè)關(guān)聯(lián)到user表的外鍵。如果將這兩個(gè)表對(duì)應(yīng)為兩個(gè)collection,即image對(duì)應(yīng)的collection中的每一個(gè)document都有一個(gè)key,其value是該image關(guān)聯(lián)的user。但為了體現(xiàn)MongoDB的效率,即MongoDB是schema-free的,而且支持嵌入子文檔,因此在實(shí)現(xiàn)時(shí),將一個(gè)user發(fā)布的image作為該user的子文檔嵌入其中,這樣只需要定義一個(gè)collection,即userCollection。如下圖所示:
對(duì)于圖片等文件,可以存儲(chǔ)在文件系統(tǒng)中,也可以存儲(chǔ)在數(shù)據(jù)庫(kù)中。因此下面分兩種情況實(shí)現(xiàn)。
13.1圖片保存在文件系統(tǒng)中
這種情況下,圖片實(shí)體中需要記錄圖片的路徑uri,因此Image類的定義如下:
因?yàn)樵?span style="font-family:Times New Roman">MongoDB中,當(dāng)保存的對(duì)象沒有設(shè)置ID時(shí),mongoDB會(huì)默認(rèn)給該條記錄設(shè)置一個(gè)ID("_id"),因此在類中沒有定義id屬性(下同)。
因?yàn)橐粋€(gè)user對(duì)應(yīng)多個(gè)image,所以在user實(shí)體中需要記錄對(duì)應(yīng)的image。如下:
在main函數(shù)中實(shí)現(xiàn)如下功能:首先定義一個(gè)user(假設(shè)id為1),其對(duì)應(yīng)3張圖片,然后將該user插入userCollection中。然后,通過查詢查找到該user(根據(jù)id),再發(fā)布第4張圖片,更新該user,然后打印出其信息。部分代碼如下:
程序運(yùn)行后,在控制臺(tái)打印出的信息如下:
從該結(jié)果容易看出,用戶user有兩個(gè)屬性“_id”和“Name”,而且ImageList作為其子文檔(數(shù)組)嵌入其中,該數(shù)組中是3個(gè)圖片,每個(gè)圖片仍然是bson格式。
13.2圖片保存在數(shù)據(jù)庫(kù)中
這種情況下,圖片實(shí)體只需要存儲(chǔ)文件名即可,因此Image2類的定義如下:
User2類和上面類似,如下所示:
實(shí)現(xiàn)了類MongoTest2,其功能仍然是一個(gè)user對(duì)應(yīng)3個(gè)圖片,存入數(shù)據(jù)庫(kù)中后,通過查詢得到該user后,再插入第4幅圖片,然后打印出信息。同時(shí)為了演示文件的查詢,對(duì)存入MongoDB中的圖片進(jìn)行了查詢并打印出其部分元數(shù)據(jù)信息。部分代碼如下所示:
運(yùn)行程序,控制臺(tái)打印出的結(jié)果如下:
十四、MongoDB常用API總結(jié)
Ø 類轉(zhuǎn)換
當(dāng)把一個(gè)類對(duì)象存到mongoDB后,從mongoDB取出來時(shí)使用setObjectClass()將其轉(zhuǎn)換回原來的類。
public class Tweet implements DBObject {
/* ... */
}
Tweet myTweet = new Tweet();
myTweet.put("user", "bruce");
myTweet.put("message", "fun");
myTweet.put("date", new Date());
collection.insert(myTweet);
//轉(zhuǎn)換
collection.setObjectClass(Tweet.class);
Tweet myTweet = (Tweet)collection.findOne();
Ø 默認(rèn)ID
當(dāng)保存的對(duì)象沒有設(shè)置ID時(shí),mongoDB會(huì)默認(rèn)給該條記錄設(shè)置一個(gè)ID("_id")。
當(dāng)然你也可以設(shè)置自己指定的ID,如:(在mongoDB中執(zhí)行用db.users.save({_id:1,name:'bruce'});)
BasicDBObject bo = new BasicDBObject();
bo.put('_id', 1);
bo.put('name', 'bruce');
collection.insert(bo);
Ø 權(quán)限
判斷是否有mongoDB的訪問權(quán)限,有就返回true,否則返回false。
boolean auth = db.authenticate(myUserName, myPassword);
Ø 查看mongoDB數(shù)據(jù)庫(kù)列表
Mongo m = new Mongo();
for (String s : m.getDatabaseNames()) {
System.out.println(s);
}
Ø 查看當(dāng)前庫(kù)下所有的表名,等于在mongoDB中執(zhí)行show tables;
Set<String> colls = db.getCollectionNames();
for (String s : colls) {
System.out.println(s);
}
Ø 查看一個(gè)表的索引
List<DBObject> list = coll.getIndexInfo();
for (DBObject o : list) {
System.out.println(o);
}
Ø 刪除一個(gè)數(shù)據(jù)庫(kù)
Mongo m = new Mongo();
m.dropDatabase("myDatabaseName");
Ø 建立mongoDB的鏈接
Mongo m = new Mongo("localhost", 27017); //有多個(gè)重載方法,可根據(jù)需要選擇
DB db = m.getDB("myDatabaseName"); //相當(dāng)于庫(kù)名
DBCollection coll = db.getCollection("myUsersTable");//相當(dāng)于表名
查詢數(shù)據(jù)
Ø 查詢第一條記錄
DBObject firstDoc = coll.findOne();
findOne()返回一個(gè)記錄,而find()返回的是DBCursor游標(biāo)對(duì)象。
Ø 查詢?nèi)繑?shù)據(jù)
DBCursor cur = coll.find();
while(cur.hasNext()) {
System.out.println(cur.next());
}
Ø 查詢記錄數(shù)量
coll.find().count();
coll.find(new BasicDBObject("age", 26)).count();
Ø 條件查詢
BasicDBObject condition = new BasicDBObject();
condition.put("name", "bruce");
condition.put("age", 26);
coll.find(condition);
Ø 查詢部分?jǐn)?shù)據(jù)塊
DBCursor cursor = coll.find().skip(0).limit(10);
while(cursor.hasNext()) {
System.out.println(cursor.next());
}
Ø 比較查詢(age > 50)
BasicDBObject condition = new BasicDBObject();
condition.put("age", new BasicDBObject("$gt", 50));
coll.find(condition);
比較符
"$gt": 大于
"$gte":大于等于
"$lt": 小于
"$lte":小于等于
"$in": 包含
//以下條件查詢20<age<=30
condition.put("age", new BasicDBObject("$gt", 20).append("$lte", 30));
插入數(shù)據(jù)
Ø 批量插入
List datas = new ArrayList();
for (int i=0; i < 100; i++) {
BasicDBObject bo = new BasicDBObject();
bo.put("name", "bruce");
bo.append("age", i);
datas.add(bo);
}
coll.insert(datas);
又如:
DBCollection coll = db.getCollection("testCollection");
for(int i=1; i<=100; i++) {//插入100條記錄
User user = new User();
user.setName("user_"+i);
user.setPoint(i);
coll.insert(user);
}
Ø 正則表達(dá)式
查詢所有名字匹配 /joh?n/i 的記錄
Pattern pattern = Pattern.compile("joh?n", CASE_INSENSITIVE);
BasicDBObject query = new BasicDBObject("name", pattern);
DBCursor cursor = coll.find(query);