1NoSQL簡述mysql
CAP(Consistency,Availabiity,Partitiontolerance)理論告訴咱們,一個分佈式系統不可能知足一致性,可用性和分區容錯性這三個需求,最多隻能同時知足兩個。關係型數據庫經過把更新操做寫到事務型日誌裏實現了部分耐用性,但帶來的是寫性能的降低。MongoDB等NoSQL數據庫背後蘊涵的哲學是不一樣的平臺應該使用不一樣類型的數據庫,MongoDB經過下降一些特性來達到性能的提升,這在不少大型站點中是可行的。由於MongoDB是非原子性的,因此若是若是應用須要事務,仍是須要選擇MySQL等關係數據庫。正則表達式
NoSQL數據庫,顧名思義就是打破了傳統關係型數據庫的範式約束。不少NoSQL數據庫從數據存儲的角度看也不是關係型數據庫,而是key-value數據格式的hash數據庫。因爲放棄了關係數據庫強大的SQL查詢語言和事務一致性以及範式約束,NoSQL數據庫在很大程度上解決了傳統關係型數據庫面臨的諸多挑戰。redis
在社區中,NoSQL是指「notonly sql」,其特色是非關係型,分佈式,開源,可水平擴展,模式自由,支持replication,簡單的API,最終一致性(相對於即時一致性,最終一致性容許有一個「不一致性窗口」,但能保證最終的客戶都能看到最新的值)。sql
2MongoDB簡介mongodb
mongo取自「humongous」(海量的),是開源的文檔數據庫──nosql數據庫的一種。shell
MongoDB是一種面向集合(collection)的,模式自由的文檔(document)數據庫。數據庫
面向集合是說數據被分紅集合的形式,每一個集合在數據庫中有唯一的名稱,集合能夠包含不限數目的文檔。除了模式不是預先定義好的,集合與RDBMS中的表概念相似,雖然兩者並非徹底對等。數據庫和集合的建立是「lazy」的,即只有在第一個document被插入時集合和數據庫才真正建立——這時在磁盤的文件系統裏才能看見。編程
模式自由是說數據庫不須要知道存放在集合中的文檔的結構,徹底能夠在同一個集合中存放不一樣結構的文檔,支持嵌入子文檔。json
文檔相似於RDBMS中的記錄,以BSON的格式保存。BSON是BinaryJSON的簡稱,是對JSON-like文檔的二進制編碼序列化。像JSON(JavaScriptObject Notation)同樣,BSON支持在對象和數組內嵌入其它的對象和數組。有些數據類型在JSON裏不能表示,但能夠在BSON裏表示,如Date類型和BinData(二進制數據),Python原生的類型均可以表示。與ProtocalBuffers(Google開發的用以處理對索引服務器請求/應答的協議)相比,BSON模式更自由,因此更靈活,但這樣也使得每一個文檔都要保存字段名,因此空間壓縮上不如ProtocolBuffers。數組
BSON第一眼看上去像BLOB,但MongoDB理解BSON的內部機制,因此MongoDB能夠深刻BSON對象的內部,即便是嵌套的對象,這樣MongoDB就能夠在頂層和嵌套的BSON對象上創建索引來應對各類查詢了。
MongoDB可運行在Linux、Windows和OSX平臺,支持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......
3術語介紹
數據庫、集合、文檔
每一個MongoDB服務器能夠有多個數據庫,每一個數據庫都有可選的安全認證。數據庫包括一個或多個集合,集合以命名空間的形式組織在一塊兒,用「.」隔開(相似於JAVA/Python裏面的包),好比集合blog.posts和blog.authors都處於"blog"下,不會與bbs.authors有名稱上的衝突。集合裏的數據由多個BSON格式的文檔對象組成,document的命名有一些限定,如字段名不能以"$"開頭,不能有".",名稱"_id"被保留爲主鍵。
若是插入的文檔沒有提供「_id」字段,數據庫會爲文檔自動生成一個ObjectId對象做爲「_id」的值插入到集合中。字段「_id」的值能夠是任意類型,只要可以保證唯一性。BSONObjectID是一個12字節的值,包括4字節的時間戳,3字節的機器號,2字節的進程id以及3字節的自增計數。建議用戶仍是使用有意義的「_id」值。
MongoDb相比於傳統的SQL關係型數據庫,最大的不一樣在於它們的模式設計(SchemaDesign)上的差異,正是因爲這一層次的差異衍生出其它各方面的不一樣。
若是將關係數據庫簡單理解爲由數據庫、表(table)、記錄(record)三個層次概念組成,而在構建一個關係型數據庫的時候,工做重點和難點都在數據庫表的劃分與組織上。通常而言,爲了平衡提升存取效率與減小數據冗餘之間的矛盾,設計的數據庫表都會盡可能知足所謂的第三範式。相應的,能夠認爲MongoDb由數據庫、集合(collection)、文檔對象(Document-oriented、BSON)三個層次組成。MongoDb裏的collection能夠理解爲關係型數據庫裏的表,雖然兩者並不徹底對等。固然,不要指望collection會知足所謂的第三範式,由於它們根本就不在同一個概念討論範圍以內。相似於表由多條記錄組成,集合也包含多個文檔對象,雖說通常狀況下,同一個集合內的文檔對象具備相同的格式定義,但這並非必須的,即MongoDb的數據模式是自由的(schema-free、模式自由、無模式),collection中能夠包含具備不一樣schema的文檔記錄,支持嵌入子文檔。
4MongoDB資源消耗
考慮到性能的緣由,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能夠看到,大部份內存都是做爲iocache被佔用的,而這部份內存是能夠釋放出來給應用使用的。】
5交互式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 3117:05:23 2010", "content" :"blablablabla", "author" :"navygong", "title" : "the firstblog" } |
...其它的documents。
6通常功能
6.1插入
客戶端把數據序列化爲BSON格式傳給DB後被存儲在磁盤上,在讀取時數據庫幾乎不作什麼改動直接把對象返回給客戶端,由client完成unserialized。如:
> doc ={'author': 'joe', 'created': new Date('2010, 6, 21'), 'title':'Yet another blogpost', 'text': 'Here is the text...', 'tags': ['example', 'joe'], 'comments':[{'author': 'jim', 'comment': 'I disgree'}, {'author': 'navy', 'comment': 'Goodpost'}], '_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] 在shardedMongoDB配置環境中應該應該使用map/reduce來代替group()。
6.3刪除
能夠像查詢同樣指定條件來刪除特定的文檔。
6.4索引
能夠像在通常的RDBMS中同樣使用索引。提供了創建(通常、唯1、組合)索引、刪除索引、重建索引等各類方法。索引信息保存在集合「system.indexes」中。
6.5map/reduce
MongoDB提供了map/reduce方法來進行數據的批處理及彙集操做。和Hadoop的使用相似,從集合中接收輸入,結果輸出到另外一個集合。若是你須要使用group,map/reduce會是個不錯的選擇。但MongoDB中的索引和標準查詢不是使用map/reduce,而是與MySQL類似。
7模式設計
Mongo式的模式設計
使用Mongo有不少種方式,你本能上可能會像使用關係型數據庫同樣去使用。固然這樣也能夠工做得很好,但卻沒能發揮出Mongo的真正威力。Monog是專門設計爲富對象模型(richobject 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爲每一個對象在磁盤上預留了空間來適應對象的增加。
8嵌入與引用
以一實例來講,假設須要設計一個小型數據庫來存儲「學生、地址、科目、成績」這些信息,那麼關係型數據庫的設計如圖1所示,而key-value型數據庫的設計則可能如圖2所示。
圖1關係型的數據庫設計
圖2key-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應該作成嵌入的對象。
9GridFS
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 ofdata_string, // optional array ofalias strings "metadata" : data_object, // anything the user wants tostore "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: newDate(1270740513127), md5:"ccd93f05e5b9912c26e68e9955bbf8b9" } |
2. 數據的二進制塊以及一些統計信息。結構以下:
{ "_id": <unspecified>, // object id of the chunk in the _chunkscollection "files_id":<unspecified>, // _id value ofthe 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 -unavygong -p 111 put ~/source_file/wnwb.svg
每種語言提供的MongoDB客戶端API都提供了一套方法,能夠像操做普通文件同樣對GridFS文件進行操做,包括read(),write(),tell(),seek()等。
10Replication(複製)
Mongo提供了兩種方式的複製:簡單的master-slave配置及replicapair的概念。
若是安全認證被enable,無論哪一種replicate方式,都要在master/slave中建立一個能爲各個database識別的用戶名/密碼。認證步驟以下:
slave先在local.system.users裏查找一個名爲"repl"的用戶,找到後用它去認證master。若是"repl"用戶沒有找到,則使用local.system.users中的第一個用戶去認證。local數據庫和admin數據庫同樣,local中的用戶能夠訪問整個dbserver。
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.2replicapairs模式
以這種方式啓動後,數據庫會自動協商誰是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中的兩臺機器只能知足最終一致性。當replicapair中的一臺機器徹底掛掉時,須要用一臺新的來代替。如(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進行的刪除操做都是安全的。但對同一對象的併發更新沒法進行。
11Sharding(分片)
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萬人同名,可能還須要劃分,由於放到一個塊裏太大了,這時定義的sharkey不能只有一個name字段了。劃分可以保證相鄰的數據存儲在一個server(固然也在相同的塊上)。
chunks:是一個集合裏某一範圍的數據,(collection,minkey, maxkey)描述了一個chunk。塊的大小有限定,當塊裏的數據超過最大值,塊會一分爲二。若是一個shard裏的數據過多(添加shard時,能夠指定這個shard上能夠存放的最大數據量maxSize),就會有塊遷移到其它的shard。一樣,當添加新的server時,爲了平衡各個server的負載,也會遷移chunk過去。
config server(配置服務器):存儲了集羣的元信息,包括每個shard、一個shard裏的server、以及每個chunk的基本信息。其中主要是chunk的信息,每一個configserver中都有一份全部chunk信息的徹底拷貝。使用兩階段提交協議來保證配置信息在configserver間的一致。mongos:能夠認爲是一個「數據庫路由器」,用以協調集羣的各個部分,使它們看起來像一個系統。mongos沒有固定的狀態,能夠在server須要的時候運行。mongos啓動後會從configserver裏取出元信息,而後接收客戶請求,把請求路由到合適的server,獲得結果後送回客戶。一個系統能夠有多個mongos例程,每一個例程都須要內存來存儲元信息。例程間不需協同工做,每一個mongos只須要協同shardservers和config servers工做便可。固然shardservers間也會彼此對話,也會同config servers對話。
11.2sharding的配置和管理
mongod的啓動選項中也包含了與sharding相關的參數,如--shardsvr(聲明這是一個sharddb),--configsvr(聲明這是一個configdb)。mongos的啓動選項--configdb指定configserver的位置。下面的連接地址是一個簡單的sharding配置例子:http://www.mongodb.org/display/DOCS/A+Sample+Configuration+Session。
像安全和認證同樣,若是要sharding,先要容許一個數據庫sharding,而後要指定數據庫裏集合的分片方式,這些都有相應的命令能夠完成。
12JavaAPI簡介
要使用Java操做MongoDB,在官網上下載jar包,目前最新的版本是:mongo-2.0.jar。首先介紹一下比較經常使用的幾個類:
Mongo:鏈接服務器,執行一些數據庫操做的選項,如新創建一個數據庫等;
DB:對應一個數據庫,能夠用來創建集合等操做;
DBCollection:對應一個集合(相似表),多是咱們用得最多的,能夠添加刪除記錄等;
DBObject接口和BasicDBObject對象:表示一個具體的記錄,BasicDBObject實現了DBObject,由於是key-value的數據結構,因此用起來其實和HashMap是基本一致的;
DBCursor:用來遍歷取得的數據,實現了Iterable和Iterator。
下面以一段簡單的例子說明:
13MongoDB實例分析
下面經過一個實例說明如何用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中的圖片進行了查詢並打印出其部分元數據信息。部分代碼以下所示:
運行程序,控制檯打印出的結果以下:
14MongoDB經常使用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中執行showtables;
Set |
查看一個表的索引
List |
刪除一個數據庫
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); |
15Redis簡介
Redis是一個key-value類型的內存數據庫,每個key都與一個value關聯,使得Redis與其餘key-value數據庫不一樣是由於在Redis中的每個value都有一個類型(type),目前在Redis中支持5中數據類型:String、List、Set、ZSet和Hash。每一種類型決定了能夠賦予其上的操做(這些操做成爲命令command)。好比你可使用LPUSH或RPUSH命令在O(1)時間對一個list添加一個元素,而後你可使用LRANGE命令獲得list中的一部分元素或使用LTRIM對該list進行trim操做。集合set操做也是很靈活的,你能夠從set(無序的String的集合)中add或remove元素,還能夠進行交集、合集和差集運算。每個command都是服務端自動的操做。
Redis性能上和memcached同樣快但提供了更多的特性。和memcached同樣,Redis支持對key設置失效時間,所以當設定的時間事後會被自動刪除。
15.1Redis特性
速度快
Redis使用標準C編寫實現,並且將全部數據加載到內存中,因此速度很是快。官方提供的數據代表,在一個普通的Linux機器上,Redis讀寫速度分別達到81000/s和110000/s。
持久化
因爲全部數據保持在內存中(2.0版本開始能夠只將部分數據的value放在內存,見「虛擬內存」),因此對數據的更新將異步地保存到磁盤上,Redis提供了一些策略來保存數據,好比根據時間或更新次數。
數據結構
能夠將Redis看作「數據結構服務器」。目前,Redis支持5種數據結構。
自動操做
Redis對不一樣數據類型的操做是自動的,所以設置或增長key值,從一個集合中增長或刪除一個元素都能安全的操做。
支持多種語言
Redis支持多種語言,諸如Ruby,Python, Twisted Python, PHP, Erlang, Tcl, Perl, Lua, Java, Scala, Clojure等。對Java的支持,包括兩個。一是JDBC-Redis,是使用JDBC鏈接Redis數據庫的驅動。這個項目的目標並非徹底實現JDBC規範,由於Redis不是關係型數據庫,但給Java開發人員提供了一個像操做關係數據庫同樣操做Redis數據庫的接口。
另外一個是JRedis,是使用Redis做爲數據庫開發Java程序的開發包,主要提供了對數據結構的操做。
主-從複製
Redis支持簡單而快速的主-從複製。官方提供了一個數據,Slave在21秒即完成了對Amazon網站10Gkey set的複製。
Sharding
很容易將數據分佈到多個Redis實例中,但這主要看該語言是否支持。目前支持Sharding功能的語言只有PHP、Ruby和Scala。
16Redis數據類型
官方文檔上,將Redis成爲一個「數據結構服務器」(data structures server)是有必定道理的。Redis的全部功能就是以其固有的幾種數據結構保存,並提供用戶操做這幾種結構的接口。能夠對比在其餘語言中那些固有數據類型及其操做。
Redis目前提供四種數據類型:string、list、set和zset(sortedset)。
16.1String類型
String是最簡單的類型,一個key對應一個value。RedisString是安全的,String類型的數據最大1G。String類型的值能夠被視做integer,從而可讓「INCR」命令族操做,這種狀況下,該integer的值限制在64位有符號數。
在list、set和zset中包含的獨立的元素類型都是RedisString類型。
在Redis中,String類型由sds.c庫定義,它被封裝成Redis對象。和Java中的對象同樣,Redis對象也是使用「引用」,所以當一個Redis String被屢次使用時,Redis會盡可能使用同一個String對象而不是屢次分配。
從Redis 1.1版開始,String對象能夠編碼成數字,所以這樣能夠節省內存空間。
16.2List類型
鏈表類型,主要功能是push、pop、獲取一個範圍的全部值等。其中的key能夠理解爲鏈表的名字。在Redis中,list就是RedisString的列表,按照插入順序排序。好比使用LPUSH命令在list頭插入一個元素,使用RPUSH命令在list的尾插入一個元素。當這兩個命令之一做用於一個空的key時,一個新的list就建立出來了。好比:
最終在mylist中存儲的元素爲:」b」,」a」,」c」。
List的最大長度是2^32-1個元素。
16.3Set類型
集合,和數學中的集合概念類似。操做中的key理解爲集合的名字。在Redis中,set就是RedisString的無序集合,不容許有重複元素。對set操做的command通常都有返回值標識所操做的元素是否已經存在。好比SADD命令是往set中插入一個元素,若是set中已存在該元素,則命令返回0,不然返回1。
Set的最大元素數是2^32-1。
Redis中對set的操做還有交集、並集、差集等。
16.4ZSet類型
Zset是set的一個升級版本,在set的基礎上增長了一個順序屬性,這一屬性在添加修改元素時能夠指定,每次指定後zset會自動安裝指定值從新調整順序。能夠理解爲一張表,一列存value,一列存順序。操做中的key理解爲zset的名字。
好比ZADD命令是向zset中添加一個新元素,對該元素要指定一個score。若是對已經在zset中存在的元素施加ZADD命令同時指定了不一樣的score,則該元素的score將更新同時該元素將被移動到合適的位置以使集合保持有序。
使用ZRANGE命令能夠獲得zset的一部分元素,固然也可使用命令ZRANGEBYSCORE,根據score獲得或刪除一部分元素。
Zset的最大元素數是2^32-1。
對於已經有序的zset,仍然可使用SORT命令,經過指定ASC|DESC參數對其進行排序。
16.5Hash類型
Redis Hash類型對數據域和值提供了映射,這一結構很方便表示對象。此外,在Hash中能夠只保存有限的幾個「域」,而不是將全部的「域」做爲key,這能夠節省內存。這一特性的體現能夠參考下文的「虛擬內存」部分。
17Alldata in memory, but saved on disk
Redis在內存中加載並維護整個數據集,但這些數據是持久化的,由於它們在同一時間被保持在磁盤上,因此當Redis服務重啓時,這些數據可以從新被加載到內存中。
Redis支持兩種數據持久化的方法。一種稱爲snapsshotting,在這種模式下,Redis異步地將數據dump到磁盤上。Redis還能夠經過配置,根據更新操做次數或間隔時間,將數據dump到磁盤。好比你能夠設定發生1000個更新操做時,或距上次轉儲最多60s就要將數據dump到磁盤。
由於數據是異步dump的,因此當系統崩潰時,就會出現錯誤。由於,Redis提供了另一種模式,更安全的持久化模式,稱爲AppendOnly File,當出現修改數據命令的地方,這些命令就要寫到「appendonly file」—ASAP。當服務重啓時,這些命令會從新執行(replay)從而在內存中從新構建數據。
Redis的存儲能夠分爲:內存存儲、磁盤存儲和log文件三部分,配置文件(redis.conf)中有三個參數對其進行配置。
save<seconds> <changes>:save配置,指出在多長時間,有多少次更新操做,就將數據同步到數據文件。在默認的配置文件中設置就設置了三個條件。
appendonlyyes/no:appendonly配置,指出是否在每次更新操做後進行日誌記錄,若是不開啓,可能會在斷電時致使一段時間內的數據丟失。由於redis自己同步數據文件是按上面的save條件來同步的,因此有的數據會在一段時間內只存在於內存中。
appendfsyncno/always/everysec:appendfsync配置,no表示等操做系統進行數據緩存同步到磁盤,always表示每次更新操做後手動調用fsync()將數據寫到磁盤,everysec表示每秒同步一次。
18Redis的Master-Slave模式
Redis中配置Master-Slave模式很簡單,兩者會自動同步。配置方法是在從機的配置文件中指定slaveof參數爲主機的ip和port便可,好比:slaveof 192.168.2.2016379。從原理上來講,是從機請求主機的方式,按照tokyotyrant的作法,是能夠實現主-主,主-從等靈活形式的,而redis只能支持主-從,不能支持主-主。Redis中的複製具備如下特色:
slave鏈接master併發出SYNC命令開始複製或當鏈接關閉時與master同步數據。master在「後臺」進行存儲操做,同時會監聽全部對數據的更新操做。當存儲操做完成後,master開始向slave傳送數據並將數據保存到磁盤,加載到內存。這一過程當中,master將會向slave發送全部對數據有更新操做的命令。經過telnet能夠實驗這一過程。經過鏈接到Redis端口而且發出SYNC命令,在telnet的session中,能夠看到數據包的傳送,以及發送給master的命令都將從新發送給slave。
當master-slave鏈接斷掉時,slave能自動從新創建鏈接。當一個master併發地收到多個slave數據同步請求時,master也能自動處理。
19Redis虛擬內存管理
在Redis2.0開始(目前最新版本)第一次提出了Virtual Memory(VM)的特性。Redis是內存數據庫,所以一般狀況下Redis會將全部的key和value都放在內存中,但有時這並非最好的選擇,爲了查詢速度,能夠將全部的key放在內存中,而values能夠放在磁盤上,當用到時再交換到內存。
好比你的數據有100000個key都放在了內存中,而只有其中10%的key被常常訪問,那麼可進行VM配置的Redis會嘗試將不常常訪問的key關聯的value放到磁盤上,當這些value被請求時纔會交換到內存中。
何時使用VM配置是須要考慮的問題。由於Redis是以磁盤爲後備的內存數據庫,在大多數狀況下,只有RAM足夠大時才使用Redis。但實際狀況是,總會出現內存不夠的狀況:
須要注意的一點是,Redis不能交換key。即若是是由於數據的key太大,value過小而形成內存不夠,VM是不能解決這種狀況的。當出現這種狀況時,有時可使用Hash結構,將「manykeys with small values」的問題轉換成「less keys but withvery values」的問題(對key進行hash,從而對數據進行「分組」)。
啓用VM功能,只須要在redis.conf文件中使vm-enabled的值爲yes。
20Redis實例分析
仍然以用戶和圖片的例子進行說明。假設將圖片存儲在文件系統中,即類Image中只保存圖片的uri。類Image和類User的代碼參見MongoDB部分的例子。下面給出測試類的關鍵代碼:
在實現中,將類User的對象實例存放在一個set中,因此利用了SADD命令,在Java中即調用JRedis對象的sadd方法。smembers方法將返回指定key(這裏是userSet)的集合中的全部value,這樣就能夠對每個元素進行操做。
21Redis命令總結
Redis提供了豐富的命令(command)對數據庫和各類數據類型進行操做,這些command能夠在Linux終端使用。在編程時,好比使用Redis 的Java語言包,這些命令都有對應的方法,好比上面例子中使用的sadd方法,就是對集合操做中的SADD命令。下面將Redis提供的命令作一總結。
21.1鏈接操做相關的命令
21.2對value操做的命令
21.3對String操做的命令
21.4對List操做的命令
21.5對Set操做的命令
21.6對zset(sorted set)操做的命令
21.7對Hash操做的命令
21.8持久化
21.9遠程服務控制