• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>
            xiaoguozi's Blog
            Pay it forword - 我并不覺的自豪,我所嘗試的事情都失敗了······習(xí)慣原本生活的人不容易改變,就算現(xiàn)狀很糟,他們也很難改變,在過程中,他們還是放棄了······他們一放棄,大家就都是輸家······讓愛傳出去,很困難,也無法預(yù)料,人們需要更細(xì)心的觀察別人,要隨時(shí)注意才能保護(hù)別人,因?yàn)樗麄兾幢刂雷约阂裁础ぁぁぁぁ?/span>

            轉(zhuǎn)自:

            http://blog.csdn.net/yczz/article/details/5974235

            一、NoSQL簡(jiǎn)述 

            CAPConsistency,Availabiity,Partition tolerance)理論告訴我們,一個(gè)分布式系統(tǒng)不可能滿足一致性,可用性和分區(qū)容錯(cuò)性這三個(gè)需求,最多只能同時(shí)滿足兩個(gè)。關(guān)系型數(shù)據(jù)庫(kù)通過把更新操作寫到事務(wù)型日志里實(shí)現(xiàn)了部分耐用性,但帶來的是寫性能的下降。MongoDBNoSQL數(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中的記錄,以BSONBinary Serialized dOcument Format)的格式保存。BSONBinary JSON的簡(jiǎn)稱,是對(duì)JSON-like文檔的二進(jìn)制編碼序列化。像JSONJavaScript Object Notation)一樣,BSON支持在對(duì)象和數(shù)組內(nèi)嵌入其它的對(duì)象和數(shù)組。有些數(shù)據(jù)類型在JSON里不能表示,但可以在BSON里表示,如Date類型和BinData(二進(jìn)制數(shù)據(jù)),Python原生的類型都可以表示。與Protocal BuffersGoogle開發(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、WindowsOS 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、GoHaskell......

            特點(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,PHPRuby,Java,C,C#,Javascript,PerlC++語言的驅(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ǔ):MongoBSON數(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.postsblog.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,128M256M,512M,1G2G,然后就是一直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}})能夠匹配所有aint類型的文檔。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è)集合。如果你需要使用groupmap/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

            title

            price

            sku

            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

            cutomersorders應(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

             GridFSMongoDB中用來存儲(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代碼,將文件filefile可以是圖片、音頻、視頻等文件)儲(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í)為masterslave。一個(gè)slave可以有多個(gè)master,這種方式并不推薦,因?yàn)榭赡軙?huì)產(chǎn)生不可預(yù)期的結(jié)果。

            在該模式中,一般是在兩個(gè)不同的機(jī)器上各部署一個(gè)MongDB實(shí)例,一個(gè)為master,另一作為slave。將MongoDB作為master啟動(dòng),只需要在命令行輸入:

            ./mongod --master

            然后主服務(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)端口,masterhostnamemasterIPmaster機(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>

            其中remoteserverpair里的另一個(gè)serverarbiterserver是一個(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è)databasemaster。

            pair中的兩臺(tái)機(jī)器只能滿足最終一致性。當(dāng)replica pair中的一臺(tái)機(jī)器完全掛掉時(shí),需要用一臺(tái)新的來代替。如(n1, n2)中的n2掛掉,這時(shí)用n3來代替n2。步驟如下:

            1. 告訴n1n3來代替n2db.$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ù)沒有同步到n1n3還不能做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中,這樣可以通過shardkey進(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 serversconfig 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)了IterableIterator。

            下面以一段簡(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的效率,即MongoDBschema-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è)id1),其對(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); 


            posted on 2012-12-24 17:19 小果子 閱讀(2117) 評(píng)論(0)  編輯 收藏 引用 所屬分類: SQL
            日日狠狠久久偷偷色综合0| 国产午夜精品久久久久九九| 久久伊人精品青青草原高清| 亚州日韩精品专区久久久| 久久婷婷人人澡人人爽人人爱| 久久香蕉国产线看观看乱码| 欧洲国产伦久久久久久久| 国产精品99久久久久久www| 国产综合久久久久| 久久无码人妻精品一区二区三区| 99久久精品国产一区二区| 欧美精品一本久久男人的天堂| 久久99国产精品久久99| 久久久久18| 久久综合九色综合欧美就去吻| 伊人久久大香线蕉综合热线| 亚洲午夜久久久久久久久久| 国产精品久久久久久久久鸭| 久久国产热这里只有精品| 伊人久久综合精品无码AV专区| 亚洲精品午夜国产VA久久成人| 久久免费国产精品一区二区| 2021国内久久精品| 99久久精品国产免看国产一区| 久久久久久久人妻无码中文字幕爆| 72种姿势欧美久久久久大黄蕉| 青青草原综合久久大伊人精品| 一级女性全黄久久生活片免费 | 久久国产精品国语对白| 色综合久久无码五十路人妻| 一级做a爰片久久毛片免费陪 | 国产美女久久久| 精品无码久久久久久国产| 麻豆一区二区99久久久久| 99精品久久久久久久婷婷| 亚洲午夜精品久久久久久浪潮| AA级片免费看视频久久| 品成人欧美大片久久国产欧美| 久久婷婷国产麻豆91天堂| …久久精品99久久香蕉国产| 国内精品久久久久久99|