一、NoSQL簡述
CAP(Consistency,Availabiity,Partition tolerance)理論告訴我們,一個分布式系統不可能滿足一緻性,可用性和分區容錯性這三個需求,最多隻能同時滿足兩個。關系型資料庫通過把更新操作寫到事務型日志裡實作了部分耐用性,但帶來的是寫性能的下降。MongoDB等NoSQL資料庫背後蘊涵的哲學是不同的平台應該使用不同類型的資料庫,MongoDB通過降低一些特性來達到性能的提高,這在很多大型站點中是可行的。因為MongoDB是非原子性的,是以如果如果應用需要事務,還是需要選擇MySQL等關系資料庫。
NoSQL資料庫,顧名思義就是打破了傳統關系型資料庫的範式限制。很多NoSQL資料庫從資料存儲的角度看也不是關系型資料庫,而是key-value資料格式的hash資料庫。由于放棄了關系資料庫強大的SQL查詢語言和事務一緻性以及範式限制,NoSQL資料庫在很大程度上解決了傳統關系型資料庫面臨的諸多挑戰。
在社群中,NoSQL是指“not only sql”,其特點是非關系型,分布式,開源,可水準擴充,模式自由,支援replication,簡單的API,最終一緻性(相對于即時一緻性,最終一緻性允許有一個“不一緻性視窗”,但能保證最終的客戶都能看到最新的值)。
二、MongoDB簡介
mongo取自“humongous”(海量的),是開源的文檔資料庫──nosql資料庫的一種。
MongoDB是一種面向集合(collection)的,模式自由的文檔(document)資料庫。
是一個高性能,開源,無模式的文檔型資料庫,它在許多場景下可用于替代傳統的關系型資料庫或鍵/值存儲方式。Mongo使用C++開發。
面向集合是說資料被分成集合的形式,每個集合在資料庫中有惟一的名稱,集合可以包含不限數目的文檔。除了模式不是預先定義好的,集合與RDBMS中的表概念類似,雖然二者并不是完全對等。資料庫和集合的建立是“lazy”的,即隻有在第一個document被插入時集合和資料庫才真正建立——這時在磁盤的檔案系統裡才能看見。
模式自由是說資料庫不需要知道存放在集合中的文檔的結構,完全可以在同一個集合中存放不同結構的文檔,支援嵌入子文檔。
文檔類似于RDBMS中的記錄,以BSON(Binary Serialized dOcument Format)的格式儲存。BSON是Binary JSON的簡稱,是對JSON-like文檔的二進制編碼序列化。像JSON(JavaScript Object Notation)一樣,BSON支援在對象和數組内嵌入其它的對象和數組。有些資料類型在JSON裡不能表示,但可以在BSON裡表示,如Date類型和BinData(二進制資料),Python原生的類型都可以表示。與Protocal Buffers(Google開發的用以處理對索引伺服器請求/應答的協定)相比,BSON模式更自由,是以更靈活,但這樣也使得每個文檔都要儲存字段名,是以空間壓縮上不如Protocol Buffers。
BSON第一眼看上去像BLOB,但MongoDB了解BSON的内部機制,是以MongoDB可以深入BSON對象的内部,即使是嵌套的對象,這樣MongoDB就可以在頂層和嵌套的BSON對象上建立索引來應對各種查詢了。
MongoDB可運作在Linux、Windows和OS X平台,支援32位和64位應用,預設端口為27017。推薦運作在64位平台,因為MongoDB為了提高性能使用了記憶體映射檔案進行資料管理,而在32位模式運作時支援的最大檔案為2GB。
MongoDB查詢速度比MySQL要快,因為它cache了盡可能多的資料到RAM中,即使是non-cached資料也非常快。目前MongoDB官方支援的用戶端API語言就多達8種(C|C++|Java|Javascript|Perl|PHP|Python|Ruby),社群開發的用戶端API還有Erlang、Go、Haskell......
特點
高性能、易部署、易使用,存儲資料非常友善。主要功能特性有:
*面向集合存儲,易存儲對象類型的資料。
*模式自由。
*支援動态查詢。
*支援完全索引,包含内部對象。
*支援查詢。
*支援複制和故障恢複。
*使用高效的二進制資料存儲,包括大型對象(如視訊等)。
*自動處理切片,以支援雲計算層次的擴充性
*支援Python,PHP,Ruby,Java,C,C#,Javascript,Perl及C++語言的驅動程式,社群中也提供了對Erlang 及.NET等平台的驅動程式。
*檔案存儲格式為BSON(一種JSON的擴充)
*可通過網絡通路
功能
面向集合的存儲:适合存儲對象及JSON形式的資料。
動态查詢:Mongo支援豐富的查詢表達式。查詢指令使用JSON形式的标記,可輕易查詢文檔中内嵌的對象及數組。
完整的索引支援:包括文檔内嵌對象及數組。Mongo的查詢優化器會分析查詢表達式,并生成一個高效的查詢計劃。
查詢監視:Mongo包含一個監視工具用于分析資料庫操作的性能。
複制及自動故障轉移:Mongo資料庫支援伺服器之間的資料複制,支援主-從模式及伺服器之間的互相複制。複制的主要目标是提供備援及自動故障轉移。
高效的傳統存儲方式:支援二進制資料及大型對象(如照片或圖檔)
自動分片以支援雲級别的伸縮性:自動分片功能支援水準的資料庫叢集,可動态添加額外的機器.
适用場合
網站資料:Mongo非常适合實時的插入,更新與查詢,并具備網站實時資料存儲所需的複制及高度伸縮性。
緩存:由于性能很高,Mongo也适合作為資訊基礎設施的緩存層。在系統重新開機之後,由Mongo搭建的持久化緩存層可以避免下層的資料源 過載。
大尺寸,低價值的資料:使用傳統的關系型資料庫存儲一些資料時可能會比較昂貴,在此之前,很多時候程式員往往會選擇傳統的檔案進行存儲。
高伸縮性的場景:Mongo非常适合由數十或數百台伺服器組成的資料庫。Mongo的路線圖中已經包含對MapReduce引擎的内置支援。
用于對象及JSON資料的存儲:Mongo的BSON資料格式非常适合文檔化格式的存儲及查詢。
不适用場合
1.高度事務性的系統:例如銀行或會計系統。傳統的關系型資料庫目前還是更适用于需要大量原子性複雜事務的應用程式。
2.傳統的商業智能應用:針對特定問題的BI資料庫會對産生高度優化的查詢方式。對于此類應用,資料倉庫可能是更合适的選擇。
3.需要SQL的問題
三、術語介紹
資料庫、集合、文檔
每個MongoDB伺服器可以有多個資料庫,每個資料庫都有可選的安全認證。資料庫包括一個或多個集合,集合以命名空間的形式組織在一起,用“.”隔開(類似于JAVA/Python裡面的包),比如集合blog.posts和blog.authors都處于"blog"下,不會與bbs.authors有名稱上的沖突。集合裡的資料由多個BSON格式的文檔對象組成,document的命名有一些限定,如字段名不能以"$"開頭,不能有".",名稱"_id"被保留為主鍵。
如果插入的文檔沒有提供“_id”字段,資料庫會為文檔自動生成一個ObjectId對象作為“_id”的值插入到集合中。字段“_id”的值可以是任意類型,隻要能夠保證惟一性。BSON ObjectID是一個12位元組的值,包括4位元組的時間戳,3位元組的機器号,2位元組的程序id以及3位元組的自增計數。建議使用者還是使用有意義的“_id”值。
MongoDb相比于傳統的SQL關系型資料庫,最大的不同在于它們的模式設計(Schema Design)上的差别,正是由于這一層次的差别衍生出其它各方面的不同。
如果将關系資料庫簡單了解為由資料庫、表(table)、記錄(record)三個層次概念組成,而在建構一個關系型資料庫的時候,工作重點和難點都在資料庫表的劃分與組織上。一般而言,為了平衡提高存取效率與減少資料備援之間的沖突,設計的資料庫表都會盡量滿足所謂的第三範式。相應的,可以認為MongoDb由資料庫、集合(collection)、文檔對象(Document-oriented、BSON)三個層次組成。MongoDb裡的collection可以了解為關系型資料庫裡的表,雖然二者并不完全對等。當然,不要期望collection會滿足所謂的第三範式,因為它們根本就不在同一個概念讨論範圍之内。類似于表由多條記錄組成,集合也包含多個文檔對象,雖然說一般情況下,同一個集合内的文檔對象具有相同的格式定義,但這并不是必須的,即MongoDb的資料模式是自由的(schema-free、模式自由、無模式),collection中可以包含具有不同schema的文檔記錄,支援嵌入子文檔。
四、MongoDB資源消耗
考慮到性能的原因,mongo做了很多預配置設定,包括提前在檔案系統中為每個資料庫配置設定逐漸增長大小的檔案集。這樣可以有效地避免潛在的檔案系統碎片,使資料庫操作更高效。
一個資料庫的檔案集從序号0開始配置設定,0,1...,大小依次是64M,128M,256M,512M,1G,2G,然後就是一直2G的建立下去(32位系統最大到512M)。是以如果上一個檔案是1G,而資料量剛好超過1G,則下一個檔案(大小為2G)則可能有超過90%都是空的。
如果想使磁盤利用更有效率,下面是一些解決方法:
1. 隻建立一個資料庫,這樣最多隻會浪費2G。
2. 每個文檔使用自建的“_id”值而不要使用預設的ObjectId對象。
3. 由于每個document的每個字段名都會存放,是以如果字段名越長,document的資料占用就會越大,是以把字段名縮短會大大降低資料的占用量。如把“timeAdded”改為“tA”。
Mongo使用記憶體映射檔案來通路資料,在執行插入等操作時,觀察mongod程序的記憶體占用時會發現量很大,當使用記憶體映射檔案時是正常的。并且映射資料的大小隻出現在虛拟記憶體那一列,常駐記憶體量才反應出有多少資料cached在記憶體中。
【按照mongodb官方的說法,mongodb完全由系統核心進行記憶體管理,會盡可能的占用系統空閑記憶體,用free可以看到,大部分記憶體都是作為io cache被占用的,而這部分記憶體是可以釋放出來給應用使用的。】
五、互動式shell
mongo類似于MySQL中的mysql程序,但功能遠比mysql強大,它可以使用JavaScript文法的指令從互動式shell中直接操作資料庫。如檢視資料庫中的内容,使用遊标循環檢視查詢結果,建立索引,更改及删除資料等資料庫管理功能。下面是一個在mongo中使用遊标的例子:
> 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插入
用戶端把資料序列化為BSON格式傳給DB後被存儲在磁盤上,在讀取時資料庫幾乎不做什麼改動直接把對象傳回給用戶端,由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],正規表達式比對,全文搜尋,......。還有distinct(),sort(),count(),skip()[3],group()[4],......。這裡清單的查詢中很多用法都和一般的RDBMS不同。
[1] 比對一個有size個元素的數組。如db.things.find({a: {$size: 1}})能夠比對文檔{a: ["foo"]}。
[2] 根據類型比對。db.things.find({a : {$type : 16}})能夠比對所有a為int類型的文檔。BSON協定中規定了各種類型對應的枚舉值。
[3] 指定跳過多少個文檔後開始傳回結果,可以用在分頁中。如:db.students.find().skip((pageNumber-1)*nPerPage).limit(nPerPage).forEach( function(student) { print(student.name + "<p>"); } )。
[4] 在sharded MongoDB配置環境中應該應該使用map/reduce來代替group()。
6.3删除
可以像查詢一樣指定條件來删除特定的文檔。
6.4索引
可以像在一般的RDBMS中一樣使用索引。提供了建立(一般、惟一、組合)索引、删除索引、重建索引等各種方法。索引資訊儲存在集合“system.indexes”中。
6.5map/reduce
MongoDB提供了map/reduce方法來進行資料的批處理及聚集操作。和Hadoop的使用類似,從集合中接收輸入,結果輸出到另一個集合。如果你需要使用group,map/reduce會是個不錯的選擇。但MongoDB中的索引和标準查詢不是使用map/reduce,而是與MySQL相似。
七、模式設計
Mongo式的模式設計
使用Mongo有很多種方式,你本能上可能會像使用關系型資料庫一樣去使用。當然這樣也可以工作得很好,但卻沒能發揮出Mongo的真正威力。Monog是專門設計為富對象模型(rich object model)使用的。
例如:如果你建立了一個簡單的線上商店并且把産品資訊存儲在關系型資料庫中,那你可能會有兩個像這樣的表:
item
title | price | sku |
item_features
sku | feature_name | feature_value |
你進行了範式處理因為不同的物品有不同的特征,這樣你不用建立一個包含所有特征的表了。在Mongo中你也可以像上面那樣建立兩個集合,但像下面這樣存儲每種物品會更有效。
item : {
"title" : <title> ,
"price" : <price> ,
"sku" : <sku> ,
"features" : {
"optical zoom" : <value> ,
...
}
}
因為隻要查詢一個集合就能取得一件物品的所有資訊,而這些資訊都儲存在磁盤上同一個地方,是以大大提高了查詢的速度。如果你想插入或更新一種特征,如db.items.update( { sku : 123 } , { "$set" : { "features.zoom" : "5" } } ),也不必在磁盤上移動整個對象,因為Mongo為每個對象在磁盤上預留了空間來适應對象的增長。
八、嵌入與引用
以一執行個體來說,假設需要設計一個小型資料庫來存儲“學生、位址、科目、成績”這些資訊,那麼關系型資料庫的設計如圖1所示,而key-value型資料庫的設計則可能如圖2所示。
圖1 關系型的資料庫設計
圖2 key-value型的資料庫設計
對比圖1和圖2,在關系型的資料庫設計裡劃分出了4個表,而在key-value型的資料庫設計裡卻隻有兩個集合。如果說集合與表一一對應的話,那麼圖2中應該也有4個集合才對,把本應該是集合的address和scores直接合入了集合students中,原因在于在key-value型的資料庫裡,資料模式是自由的。
以scores來說,在關系型的資料庫設計中将其單獨成一個表是因為student與score是一對多的關系,如果将score合入student表,那麼就必須預留最多可能的字段,這會存在浪費,并且當以後新增一門課程時擴充困難,是以一般都會将score表單獨出來。而對于key-value型的資料庫就不同了,其scores字段就是一個BSON,該BSON可以隻有一個for_course,也可以有任意多個for_course,其固有的模式自由特性使得它可以将score包含在内而無需另建一個score集合。
對于與student為一對一關系的address表也可以直接合入student,無需擔心address的擴充性,當以後需要給address新增一個province字段,直接在資料插入時加上這個值即可。
當然,對于與student成多對多關系course表,為了減少資料備援,可以将course建立為一個集合,同關系型的資料庫設計中類似。
students文檔中嵌入了address文檔和scores文檔,scores文檔的“for_course”字段的值是指向courses集合的文檔的引用。如果是關系型資料庫,需要把“scores”作為一個單獨的表,然後在students表中建立一個指向“scores”的外鍵。是以Mongo模式設計中的一個關鍵問題就是“是值得為這個對象建立一個集合呢,還是把這個對象嵌入到其它的集合中”。在關系型資料庫中為了範式的要求,每個子項都要建一個單獨的表,但在Mongo中使用嵌入式對象更有效,是以你應該給出不使用嵌入式對象而單獨建一個集合的理由。
為什麼說引用要慢些呢,以上面的students集合為例,比如執行:
print( student.scores[0].for_course.name );
如果這是第一次通路scores[0],那些用戶端必須執行:
student.scores[0].for_course = db.courses.findOne({_id:_course_id_to_find_}); //僞代碼
是以每一次周遊引用都要對資料庫進行一次這樣的查詢,即使所有的資料都在記憶體中。再考慮到從用戶端到伺服器端的種種延遲,這個時間也不會低。
有一些規則可以決定該用嵌入還是引用:
1. 第一個類對象,也就是處于頂層的,往往應該有自己的集合。
2. 排列項詳情對象應該用嵌入。
3. 處于被包含關系的應該用嵌入。
4. 多對多的關系通常應該用引用。
5. 資料量小的集合可以放心地做成一個單獨的集合,因為整個集合可以很快地cached。
6. 要想獲得嵌入式對象的系統級視圖會更困難一些。如上面的“Scores”如果不做成嵌入式對象可以更容易地查詢出分數排名前100的學生。
7. 如果嵌入的是大對象,需要留意到BSON對象的4M大小限定(後面會講到)。
8. 如果性能是關鍵就用嵌入。
下面是一些示例:
1. Customer/Order/Order Line-Item
cutomers和orders應該做成一個集合,line-items應該以數組的形式嵌入在order中。
2. 部落格系統
posts應該是一個集合;author可以是一個單獨的集合,如果隻需記錄作者的email位址也可以以字段的方式存在于posts中;comments應該做成嵌入的對象。
九、GridFS
GridFS是MongoDB中用來存儲大檔案而定義的一種檔案系統。MongoDB預設是用BSON格式來對資料進行存儲和網絡傳輸。但由于BSON文檔對象在MongoDB中最大為4MB,無法存儲大的對象。即使沒有大小限制,BSON也無法滿足對大資料集的快速範圍查詢,是以MongoDB引進了GridFS。
9.1GridFS表示的對象資訊
1. 檔案對象(類GridFSFile 的對象)的中繼資料資訊。結構如下
{
"_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進去的一個檔案例子:
{
_id: ObjId(4bbdf6200459d967be9d8e98),
filename: "/home/hjgong/source_file/wnwb.svg",
length: 7429,
chunkSize: 262144,
uploadDate: new Date(1270740513127),
md5: "ccd93f05e5b9912c26e68e9955bbf8b9"
}
2. 資料的二進制塊以及一些統計資訊。結構如下:
{
"_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可以儲存富媒體檔案,同時存入任意的附加資訊,因為這些資訊實際上也是一個普通的collection。以前,如果要存儲一個附件,通常的做法是,在主資料庫中存放檔案的屬性同時記錄檔案的path,當查詢某個檔案時,需要首先查詢資料庫,獲得該檔案的path,然後從存儲系統中獲得相應的檔案。在使用GridFS時則非常簡單,可以直接将這些資訊直接存儲到檔案中。比如下面的Java代碼,将檔案file(file可以是圖檔、音頻、視訊等檔案)儲存到db中:
其中該方法的第一個參數的類型還可以是InputStream,byte[],進而實作多個重載的方法。
9.2GridFS管理
MongoDB提供的工具mongofiles可以從指令行操作GridFS。如:
./mongofiles -host localhost:1727 -u navygong -p 111 put ~/source_file/wnwb.svg
每種語言提供的MongoDB用戶端API都提供了一套方法,可以像操作普通檔案一樣對GridFS檔案進行操作,包括read(),write(),tell(),seek()等。
十、Replication(複制)
Mongo提供了兩種方式的複制:簡單的master-slave配置及replica pair的概念。
如果安全認證被enable,不管哪種replicate方式,都要在master/slave中建立一個能為各個database識别的使用者名/密碼。認證步驟如下:
slave先在local.system.users裡查找一個名為"repl"的使用者,找到後用它去認證master。如果"repl"使用者沒有找到,則使用local.system.users中的第一個使用者去認證。local資料庫和admin資料庫一樣,local中的使用者可以通路整個db server。
10.1master-slave模式
一個server可以同時為master和slave。一個slave可以有多個master,這種方式并不推薦,因為可能會産生不可預期的結果。
在該模式中,一般是在兩個不同的機器上各部署一個MongDB執行個體,一個為master,另一作為slave。将MongoDB作為master啟動,隻需要在指令行輸入:
./mongod --master |
然後主服務程序将會在資料庫中建立一個集合local.oplog.$main,該collection主要記錄了事務日志,即需要在slave執行的操作。
而将MongoDB作為slave啟動,隻需要在指令行輸入:
./mongod --slave --source <masterhostname>[:<port>] |
port不指定時即使用預設端口,masterhostname是master的IP或master機器的FQDN。
其他配置選項:
--autoresync:自動sync,但在10分鐘内最多隻會進行一次。
--oplogSize:指定master上用于存放更改的資料量,如果不指定,在32位機上最少為50M,在64位機上最少為 1G,最大為磁盤空間的5%。
10.2replica pairs模式
以這種方式啟動後,資料庫會自動協商誰是master誰是slave。一旦一個資料庫伺服器斷電,另一個會自動接管,并從那一刻起起為master。萬一另一個将來也出錯了,那麼master狀态将會轉回給第一個伺服器。以這種複制方式啟動本地MongoDB的指令如下:
./mongod --pairwith <remoteserver> --arbiter <arbiterserver> |
其中remoteserver是pair裡的另一個server,arbiterserver是一個起仲裁作用的Mongo資料庫伺服器,用來協商pair中哪一個是master。arbiter運作在第三個機器上,利用“平分決勝制”決定在pair中的兩台機器不能聯系上對方時讓哪一個做master,一般是能同arbiter通話的那台機器做master。如果不加--arbiter選項,出現網絡問題時兩台機器都作為master。指令db.$cmd.findOne({ismaster:1})可以檢查目前哪一個database是master。
pair中的兩台機器隻能滿足最終一緻性。當replica pair中的一台機器完全挂掉時,需要用一台新的來代替。如(n1, n2)中的n2挂掉,這時用n3來代替n2。步驟如下:
1. 告訴n1用n3來代替n2:db.$cmd.findOne({replacepeer:1});
2. 重新開機n1讓它同n3對話:./mongod --pairwith n3 --arbiter <arbiterserver>
3. 啟動n3:./mongod --pairwith n1 --arbiter <arbiterserver>。
在n3的資料沒有同步到n1前n3還不能做master,這個過程長短由資料量的多少決定。
10.3受限的master-master複制
Mongo不支援完全的master-master複制,通常情況下不推薦使用master-master模式,但在一些特定的情況下master-master也可用。master-master也隻支援最終一緻性。配置master-master隻需運作mongod時同時加上--master選項和--slave選項。如下:
$ 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 & |
這種模式對插入、查詢及根據_id進行的删除操作都是安全的。但對同一對象的并發更新無法進行。
十一、Sharding(分片)
11.1sharding介紹
MongoDB包括一個自動分片的的子產品(“mongos”),進而可以建構一個大的水準可擴充的資料庫叢集,可以動态地添加和移走機器。如下是一個資料庫叢集的示意圖:
mongod:資料庫伺服器程序,類似于mysqld。
shards:每個shard有一個或多個mongod,通常是一個master,多個slave組成replication。資料由集合按一個預定的順序劃分,某一個範圍的資料被放到一個特定的shard中,這樣可以通過shard的key進行有效的範圍查詢。
shard keys:用于劃分集合,格式類似于索引的定義,也是把一個或多個字段作為key,以key來分布資料。如:{ name : 1 (1代表升序,-1代表降序)}、{ _id : 1 }、{ lastname : 1, firstname : 1 }、{ tag : 1, timestamp : -1 }。如果有100萬人同名,可能還需要劃分,因為放到一個塊裡太大了,這時定義的shar key不能隻有一個name字段了。劃分能夠保證相鄰的資料存儲在一個server(當然也在相同的塊上)。
chunks:是一個集合裡某一範圍的資料,(collection, minkey, maxkey)描述了一個chunk。塊的大小有限定,當塊裡的資料超過最大值,塊會一分為二。如果一個shard裡的資料過多(添加shard時,可以指定這個shard上可以存放的最大資料量maxSize),就會有塊遷移到其它的shard。同樣,當添加新的server時,為了平衡各個server的負載,也會遷移chunk過去。
config server(配置伺服器):存儲了叢集的元資訊,包括每一個shard、一個shard裡的server、以及每一個chunk的基本資訊。其中主要是chunk的資訊,每個config server中都有一份所有chunk資訊的完全拷貝。使用兩階段送出協定來保證配置資訊在config server間的一緻。mongos:可以認為是一個“資料庫路由器”,用以協調叢集的各個部分,使它們看起來像一個系統。mongos沒有固定的狀态,可以在 server需要的時候運作。mongos啟動後會從config server裡取出元資訊,然後接收客戶請求,把請求路由到合适的server,得到結果後送回客戶。一個系統可以有多個mongos例程,每個例程都需要記憶體來存儲元資訊。例程間不需協同工作,每個mongos隻需要協同shard servers和config servers工作即可。當然shard servers間也會彼此對話,也會同config servers對話。
11.2sharding的配置和管理
mongod的啟動選項中也包含了與sharding相關的參數,如--shardsvr(聲明這是一個shard db),--configsvr(聲明這是一個config db)。mongos的啟動選項--configdb指定config server的位置。下面的連結位址是一個簡單的sharding配置例子:http://www.mongodb.org/display/DOCS/A+Sample+Configuration+Session。
像安全和認證一樣,如果要sharding,先要允許一個資料庫sharding,然後要指定資料庫裡集合的分片方式,這些都有相應的指令可以完成。
十二、Java API簡介
要使用Java操作MongoDB,在官網上下載下傳jar包,目前最新的版本是:mongo-2.0.jar。首先介紹一下比較常用的幾個類:
Mongo:連接配接伺服器,執行一些資料庫操作的選項,如建立立一個資料庫等;
DB:對應一個資料庫,可以用來建立集合等操作;
DBCollection:對應一個集合(類似表),可能是我們用得最多的,可以添加删除記錄等;
DBObject接口和BasicDBObject對象:表示一個具體的記錄,BasicDBObject實作了DBObject,因為是key-value的資料結構,是以用起來其實和HashMap是基本一緻的;
DBCursor:用來周遊取得的資料,實作了Iterable和Iterator。
下面以一段簡單的例子說明:
十三、MongoDB執行個體分析
下面通過一個執行個體說明如何用MongoDB作為資料庫。該執行個體中有一個user實體,包含一個name屬性,每個user對應一到多個圖檔image。按照關系型資料庫設計,可以設計一個user表和一個image表,其中image表中有一個關聯到user表的外鍵。如果将這兩個表對應為兩個collection,即image對應的collection中的每一個document都有一個key,其value是該image關聯的user。但為了展現MongoDB的效率,即MongoDB是schema-free的,而且支援嵌入子文檔,是以在實作時,将一個user釋出的image作為該user的子文檔嵌入其中,這樣隻需要定義一個collection,即userCollection。如下圖所示:
對于圖檔等檔案,可以存儲在檔案系統中,也可以存儲在資料庫中。是以下面分兩種情況實作。
13.1圖檔儲存在檔案系統中
這種情況下,圖檔實體中需要記錄圖檔的路徑uri,是以Image類的定義如下:
因為在MongoDB中,當儲存的對象沒有設定ID時,mongoDB會預設給該條記錄設定一個ID("_id"),是以在類中沒有定義id屬性(下同)。
因為一個user對應多個image,是以在user實體中需要記錄對應的image。如下:
在main函數中實作如下功能:首先定義一個user(假設id為1),其對應3張圖檔,然後将該user插入userCollection中。然後,通過查詢查找到該user(根據id),再釋出第4張圖檔,更新該user,然後列印出其資訊。部分代碼如下:
程式運作後,在控制台列印出的資訊如下:
從該結果容易看出,使用者user有兩個屬性“_id”和“Name”,而且ImageList作為其子文檔(數組)嵌入其中,該數組中是3個圖檔,每個圖檔仍然是bson格式。
13.2圖檔儲存在資料庫中
這種情況下,圖檔實體隻需要存儲檔案名即可,是以Image2類的定義如下:
User2類和上面類似,如下所示:
實作了類MongoTest2,其功能仍然是一個user對應3個圖檔,存入資料庫中後,通過查詢得到該user後,再插入第4幅圖檔,然後列印出資訊。同時為了示範檔案的查詢,對存入MongoDB中的圖檔進行了查詢并列印出其部分中繼資料資訊。部分代碼如下所示:
運作程式,控制台列印出的結果如下:
十四、MongoDB常用API總結
Ø 類轉換
當把一個類對象存到mongoDB後,從mongoDB取出來時使用setObjectClass()将其轉換回原來的類。
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);
//轉換
collection.setObjectClass(Tweet.class);
Tweet myTweet = (Tweet)collection.findOne();
Ø 預設ID
當儲存的對象沒有設定ID時,mongoDB會預設給該條記錄設定一個ID("_id")。
當然你也可以設定自己指定的ID,如:(在mongoDB中執行用db.users.save({_id:1,name:'bruce'});)
BasicDBObject bo = new BasicDBObject();
bo.put('_id', 1);
bo.put('name', 'bruce');
collection.insert(bo);
Ø 權限
判斷是否有mongoDB的通路權限,有就傳回true,否則傳回false。
boolean auth = db.authenticate(myUserName, myPassword);
Ø 檢視mongoDB資料庫清單
Mongo m = new Mongo();
for (String s : m.getDatabaseNames()) {
System.out.println(s);
}
Ø 檢視目前庫下所有的表名,等于在mongoDB中執行show tables;
Set<String> colls = db.getCollectionNames();
for (String s : colls) {
System.out.println(s);
}
Ø 檢視一個表的索引
List<DBObject> list = coll.getIndexInfo();
for (DBObject o : list) {
System.out.println(o);
}
Ø 删除一個資料庫
Mongo m = new Mongo();
m.dropDatabase("myDatabaseName");
Ø 建立mongoDB的連結
Mongo m = new Mongo("localhost", 27017); //有多個重載方法,可根據需要選擇
DB db = m.getDB("myDatabaseName"); //相當于庫名
DBCollection coll = db.getCollection("myUsersTable");//相當于表名
查詢資料
Ø 查詢第一條記錄
DBObject firstDoc = coll.findOne();
findOne()傳回一個記錄,而find()傳回的是DBCursor遊标對象。
Ø 查詢全部資料
DBCursor cur = coll.find();
while(cur.hasNext()) {
System.out.println(cur.next());
}
Ø 查詢記錄數量
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);
Ø 查詢部分資料塊
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));
插入資料
Ø 批量插入
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);
}
Ø 正規表達式
查詢所有名字比對 /joh?n/i 的記錄
Pattern pattern = Pattern.compile("joh?n", CASE_INSENSITIVE);
BasicDBObject query = new BasicDBObject("name", pattern);
DBCursor cursor = coll.find(query);