轉自:
http://blog.csdn.net/yczz/article/details/5974235
一、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
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啟動,只需要在命令行輸入:
然后主服務進程將會在數據庫中創建一個集合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);