日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

MongoDB 表设计

發(fā)布時間:2023/12/31 编程问答 31 豆豆
生活随笔 收集整理的這篇文章主要介紹了 MongoDB 表设计 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

12月12日上午,TJ在開源中國的年終盛典會上分享了文檔模型設(shè)計的進(jìn)階技巧,就讓我們來回顧一下吧: —————————————————————————————————————————————————————————-

從很久以前,我就開始接觸開源產(chǎn)品:從最開始的使用、受益者到后來的貢獻(xiàn)者,到現(xiàn)在的熱情推廣者?,F(xiàn)在,我是MongoDB的技術(shù)顧問。我的職責(zé)是為MongoDB的客戶和用戶提供MongoDB使用的一些最佳實踐,包括模式設(shè)計、性能優(yōu)化和集群部署方案等方面。

今天的話題是進(jìn)階模式,所以我假設(shè)在坐各位至少是已經(jīng)對MongoDB有了一些基本的了解。 不過每次總有一些同學(xué)以為這里有水果吃才坐進(jìn)來的,所以在這里我簡單介紹一下:MongoDB 不是芒果(mango),它在拉丁文中的原意是巨大的意思。如果用一句話來概括的話,mongo是一個高可用、分布式、無模式的文檔數(shù)據(jù)庫。等一下,這里我故意用錯了一個詞: 不是無模式,而是“靈活模式”。 如果真的是無模式,今天我就不用站在這里了。沒有模式何來模式設(shè)計之說。在你開始用mongo做一些 prototype的時候,確實不用考慮太多的模式。MongoDB內(nèi)存數(shù)據(jù)庫的一些特性,讓你在前期不會遇到什么問題。但是一旦涉及到幾千萬幾十億的數(shù)據(jù)量,或者是數(shù)千數(shù)萬的并發(fā)量,模式設(shè)計就是個你必須提前面對的問題。

在我們談mongo的模式設(shè)計之前,我們很有必要來了解一下MongoDB的數(shù)據(jù)模型。大家都知道,無論你從哪個角度來看,MongoDB都是目前NoSQL,或者說非關(guān)系型的數(shù)據(jù)庫中的領(lǐng)頭羊。那么,mongo和傳統(tǒng)關(guān)系數(shù)據(jù)庫的最本質(zhì)的區(qū)別在那里呢?我們說是它的文檔模型。

關(guān)系模型和文檔模型的區(qū)別在哪里?

  • 關(guān)系模型需要你把一個數(shù)據(jù)對象,拆分成零部件,然后存到各個相應(yīng)的表里,需要的是最后把它拼起來。舉例子來說,假設(shè)我們要做一個CRM應(yīng)用,那么要管理客戶的基本信息,包括客戶名字、地址、電話等。由于每個客戶可能有多個電話,那么按照第三范式,我們會把電話號碼用單獨(dú)的一個表來存儲,并在顯示客戶信息的時候通過關(guān)聯(lián)把需要的信息取回來。
  • 而MongoDB的文檔模式,與這個模式大不相同。由于我們的存儲單位是一個文檔,可以支持?jǐn)?shù)組和嵌套文檔,所以很多時候你直接用一個這樣的文檔就可以涵蓋這個客戶相關(guān)的所有個人信息。關(guān)系型數(shù)據(jù)庫的關(guān)聯(lián)功能不一定就是它的優(yōu)勢,而是它能夠工作的必要條件。 而在MongoDB里面,利用富文檔的性質(zhì),很多時候,關(guān)聯(lián)是個偽需求,可以通過合理建模來避免做關(guān)聯(lián)。

雖然MongoDB的模型和關(guān)系型截然不同,但是關(guān)系型數(shù)據(jù)庫的一些必不可少的功能如動態(tài)查詢、二級索引、聚合等在MongoDB中也有非常完善的支持。

這里我介紹一下文檔模型的優(yōu)點(diǎn):

  • 讀寫效率高-由于文檔模型把相關(guān)數(shù)據(jù)集中在一塊,在普通機(jī)械盤上讀數(shù)據(jù)的時候不用花太多時間去定位磁頭,因此在IO性能上有先天獨(dú)厚的優(yōu)勢;
  • 可擴(kuò)展能力強(qiáng)-關(guān)系型數(shù)據(jù)庫很難做分布式的原因就是多節(jié)點(diǎn)海量數(shù)據(jù)關(guān)聯(lián)有巨大的性能問題。如果不考慮關(guān)聯(lián),數(shù)據(jù)分區(qū)分庫,水平擴(kuò)展就比較簡單;
  • 動態(tài)模式-文檔模型支持可變的數(shù)據(jù)模式,不要求每個文檔都具有完全相同的結(jié)構(gòu)。對很多異構(gòu)數(shù)據(jù)場景支持非常好;
  • 模型自然-文檔模型最接近于我們熟悉的對象模型。從內(nèi)存到存儲,無需經(jīng)過ORM的雙向轉(zhuǎn)換,性能上和理解上都很自然易懂。

那么我們?nèi)绾慰紤]MongoDB 文檔模式設(shè)計的基本策略呢?

  • 其實很簡單,我們一般建議的是先考慮內(nèi)嵌, 直接按照你的對象模型來設(shè)計你的數(shù)據(jù)模型。如果你的對象模型數(shù)量不多,關(guān)系不是很復(fù)雜,那么恭喜你,可能直接一種對象對應(yīng)一個集合就可以了。
  • 內(nèi)嵌是文檔模型的特色,可以充分利用MongoDB的富文檔功能來享受我們剛才談到的一些文檔模型的性能和擴(kuò)展性等特性。一般的一對一、一對多關(guān)系,比如說一個人多個地址多個電話等等都可以放在一個文檔里用內(nèi)嵌來完成。
  • 但是有一些時候,使用引用則難以避免。比如說, 一個明星的博客可能有幾十萬或者幾百萬的回復(fù),這個時候如果把comments放到一個數(shù)組里,可能會超出16M的限制。這個時候你可以考慮使用引用的方式,在主表里存儲一個id值,指向另一個表中的 id 值。使用引用要注意的就是:從性能上講,一般我們可能需要兩次以上才能把需要的數(shù)據(jù)取回來。更加重要的是:需要把數(shù)據(jù)存放到兩個集合里,但是目前為止MongoDB并不支持跨表的事務(wù)性,所以對于強(qiáng)事務(wù)的應(yīng)用場景要謹(jǐn)慎使用。

很多時候我們并不能很好地回答自己的問題,包括剛才的內(nèi)嵌還是引用的問題。那么這個時候有必要了解一下,MongoDB模式設(shè)計的終極原則。MongoDB的模式設(shè)計和關(guān)系型大不相同,我們說MongoDB是為應(yīng)用程序設(shè)計的,而不是為了存儲優(yōu)化的。如果可以達(dá)到最高性能的話,我們甚至可以做一些反范式的東西。 接下來我們來看幾個比較具體的設(shè)計案例,了解一下MongoDB的模式設(shè)計思路:

我這里準(zhǔn)備了4個比較經(jīng)典的MongoDB案例,從CMS 內(nèi)容管理到電商,社交到物聯(lián)網(wǎng)。 由于時間原因我就從第二個開始。

在電商方面MongoDB的應(yīng)用場景其實蠻多,比如說,大名鼎鼎的京東用mongo來存儲過億的商品信息,另外有一家著名的境外電商從頭到尾用的都是MongoDB,包括訂單管理等。這里我們就來看一下購物車這個場景。購物車的特點(diǎn)就是單個購物車數(shù)據(jù)項不會太大,一般來說不會超過100項目。雙十一的時候淘寶的購物車?yán)镒疃嗑椭荒芊?9件商品。在這里我要謝謝我的太太,是她讓我知道了這個限制。另外一點(diǎn)就是購物車的數(shù)據(jù)可能需要過期刪除。

我們說文檔模型在這種場景會是個很好的選擇:

大家看一下下面的參考數(shù)據(jù)模型,第一點(diǎn)注意我們可以使用MongoDB的TTL 索引來自動清理過期數(shù)據(jù)。TTL索引可以建立在任意一個時間字段上,在建立索引的時候可以指定文檔在過多少時間后會被自動清理掉。第二個大家注意的是什么呢?在這里我們把商品的一些主要信息放到購物車?yán)锪?#xff0c;比如說 name,price, quantity,為什么? 讀一次所有信息都拿到了:價格、數(shù)量等等,不需要再去查另一張表。這是一種比較常見的優(yōu)化手段,用冗余的方式來提供讀取性能。

接下來我們看一下使用這種模式的時候如何進(jìn)行一些購物車的操作。比如說,如果我們想要往購物車?yán)镌黾右粋€價值2元的面包,我們可以用下面的update語句。注意$push的用法。$push 類似于javascript的操作符,意思是往數(shù)組尾部增加一個元素。

如果需要更新購物車中某個產(chǎn)品的數(shù)量,你可以用update語句直接操作數(shù)組的某一個元素。在這里我們需要做的是更新item 4567的數(shù)量為5。 注意 items.$.quanity的使用,這里的$ 表示在查詢條件里匹配上的數(shù)組元素的序數(shù)。

如果需要統(tǒng)計一下在購物車內(nèi)某個商品的總數(shù),可以使用MongoDB的聚合功能。聚合運(yùn)算在MongoDB里面是對數(shù)據(jù)輸入源進(jìn)行一系列的運(yùn)算。在這里我們做的就是幾個步驟是:

  • $match: 在所有購物車中過濾掉其他商品,只選出id是8910的商品
  • $unwind: 把items 數(shù)組展開,每個數(shù)組元素變成一個文檔
  • $group: 用聚合運(yùn)算 $sum 把每一件商品的數(shù)量相加獲得總和
  • 下面我們來看一個社交網(wǎng)絡(luò)的例子。社交app最關(guān)鍵的一些場景就是維護(hù)朋友關(guān)系以及朋友圈或微博墻等。

    對于關(guān)系描述,使用文檔模型的內(nèi)嵌數(shù)組特性,我們可以很容易地把我關(guān)注的用戶(following)和關(guān)注我的用戶表示出來。下例表示TJ我的關(guān)注的用戶是mandy和bert,而oscar和mandy則在關(guān)注我。這種模式是文檔模型中最經(jīng)典的。但是有一個潛在問題就是如果TJ我是一個明星,他們關(guān)注我的人可能有千萬。一個千萬級的數(shù)組會有兩個問題:1) 有可能超出一個文檔最大16M的硬性限制; 2) MongoDB數(shù)組太大會嚴(yán)重影響性能。

    怎么辦?我們可以建立一個專門的集合來描述關(guān)注關(guān)系。這里就是一個內(nèi)嵌和引用的經(jīng)典選擇。我們希望用內(nèi)嵌,但是如果數(shù)組維度太大,就需要考慮用另外一個集合的方式來表示一對多的關(guān)系(用戶 1–N 關(guān)注者)

    另外一個要注意的是關(guān)注數(shù),我們在顯示關(guān)注和粉絲數(shù)量的時候,不希望去跑一次count 查詢再顯示。因為count操作一般來說會比較占資源。通常的做法可以再用戶對象里面加兩個字段,一個是關(guān)注數(shù)一個是粉絲數(shù)。每次有人關(guān)注或者關(guān)注別人時候就更新一下。

    下面我們來看看比較有趣的微博墻,或者微信朋友圈的實現(xiàn)有什么考量。

    在實現(xiàn)微博墻的時候,有兩種方式可以考慮:扇出讀?或者是扇出寫。

    扇出讀、扇出寫的說法是基于社交網(wǎng)絡(luò)的海量用戶、海量數(shù)據(jù)的應(yīng)用特征。這些大量的數(shù)據(jù)往往分布在各個分片服務(wù)器上。扇出讀是一種比較常規(guī)的做法,就是當(dāng)你需要去獲得所有你關(guān)注用戶的最新更新的時候,你就去到每一個你關(guān)注用戶的數(shù)據(jù)區(qū),把最新的一些數(shù)據(jù)取回來。因為需要去到不同的分片服務(wù)器去取,所以叫做扇出讀。大家可以想象,這種扇出讀的效率不會太高,基本上是最慢的那個服務(wù)器的響應(yīng)時間決定了總體的響應(yīng)時間。 當(dāng)然,這種方式是比較簡單的,不需要特殊處理。

    扇出寫,我稱之為土豪玩法。具體來說就是當(dāng)發(fā)布的時候,一條數(shù)據(jù)會寫多次,直接寫到每一個關(guān)注你的粉絲的墻上。這樣做的好處是當(dāng)你的粉絲讀他自己的微博墻的時候,他只需要去一個地方就可以把所有最新的更新連續(xù)取回來。由于一個用戶的數(shù)據(jù)可一般可以存儲在同一臺服務(wù)器上的同一個區(qū)域,通過這種方式可以實現(xiàn)快速的讀取微博墻數(shù)據(jù)。 代價當(dāng)然也是很明顯: 你的寫入需求會被放大幾十幾百倍,存儲也是相應(yīng)的擴(kuò)大幾十幾百倍。這個絕對不是關(guān)系型數(shù)據(jù)庫的玩法,但是在MongoD 模式設(shè)計,這個很正常。只要保證性能,什么事情都做得出來。

    下面這個例子,首先是mandy在發(fā)消息的時候會寫(push)到我的墻上(timeline)來。如果mandy有50個關(guān)注者,那么這個寫就會有50次,每個關(guān)注者一次。

    第二條語句就是我打開微博的時候,一條語句,一個地方就可以找到所有我朋友發(fā)的狀態(tài)更新。注意:這里還使用了bucket,這是另外一個控制文檔內(nèi)數(shù)組元素個數(shù)的有效方法。比如說我們定義bucket 大小是1000的話,超過1000 就把新的數(shù)據(jù)插入到下一個文檔并對bucket 序數(shù)遞增。

    好了,最后我們來看一下物聯(lián)網(wǎng)的應(yīng)用場景:

    各位還有多少人仍然記得MH370,去年在印度洋消失的客機(jī)?在該事故之后,許多人都在疑惑:在當(dāng)今的技術(shù)水平下,為什么我們不能跟蹤如此龐大的一個東西?

    讓我們來看看如果要監(jiān)控飛機(jī)數(shù)據(jù)有什么樣的挑戰(zhàn)。飛機(jī)上面的數(shù)據(jù)源眾多,光收集位置信息,就需要多個系統(tǒng)協(xié)作完成, 如ADS-C, EUROCONTROL等等。此外,收集的數(shù)據(jù)也是各種各樣:位置是2D、速度是數(shù)值、引擎參數(shù)則是多維度的。

    另一個挑戰(zhàn)就是海量數(shù)據(jù)。一個三小時的航班,每分鐘采集一次,少說點(diǎn),每次100條數(shù)據(jù),那就是每秒1萬8千個數(shù)據(jù)點(diǎn)。按每天100,000航班,一天的數(shù)據(jù)算下來有18億條,1.8TB 左右的數(shù)據(jù), 21,000 的QPS。 從哪個角度來看,這都是個經(jīng)典的大數(shù)據(jù)問題。

    這個問題在關(guān)系型數(shù)據(jù)庫解決的話,比較幼稚的方法就是設(shè)計一個超寬的表。所有需要采集的每一個值就是一個列。這種設(shè)計的問題比較明顯:

  • 容易造成空白浪費(fèi),不是每一條記錄都包含所有字段值
  • 可能會經(jīng)常需要改數(shù)據(jù)庫模式。對于海量數(shù)據(jù),改一次模式代價巨大。
  • 另一種改良方案是用EAV 設(shè)計模式。就是采用一個主表和一個屬性值表。在屬性值表里存放所有的參數(shù)鍵值對。這樣做的好處自然是靈活性:增加新的參數(shù)時無需修改模式。但是問題同樣存在:用來存儲值的那列METRIC_VALUE的字節(jié)大小必須定義成所有值的最大值 才可以放下所有的參數(shù)值。這個可能帶來空間浪費(fèi),但是更嚴(yán)重的問題是:將不太可能在此字段上建索引,進(jìn)而影響一些場景的使用。

    下面我們來看看文檔模型怎么做: 這里對于location 、speed 等不同數(shù)據(jù)類型的字段,在文檔模型下可以直接支持。下面的兩個文檔,第一個文檔和第二個文檔可以同屬一個集合,但是可以有完全不同的字段。 MongoDB對異構(gòu)數(shù)據(jù)的支持在這樣的場景下有得天獨(dú)厚的優(yōu)勢。如果我們希望對某一個metric如location建立索引,我們也可以使用mongoDB的稀疏索引 (Sparse Index)僅對有l(wèi)ocation字段的文檔建索引,在不造成索引空間浪費(fèi)的前提下提高檢索效率。當(dāng)需要增加新的字段的時候,也不需要對模式做任何修改,可以直接就在應(yīng)用中的JSON模型里添加需要的字段(elevation)。

    在IOT這個場景里,我們可以使用一個叫做分桶的設(shè)計方式來進(jìn)行幾十倍的性能增長。具體來說就是把采集的數(shù)據(jù)按小時為一個桶,把每小時的數(shù)據(jù)聚合到一個文檔里。如下面所示,每分鐘的值用子文檔的一個字段來表示。這樣做的好處就是大量減少文檔的數(shù)量,相應(yīng)的索引數(shù)量也會減少,總體寫入IO將會大幅度降低并得到性能提升。

    ?

    使用這種方式我們還可以把一些統(tǒng)計需要的數(shù)值,如每小時的平均值預(yù)先就作為一個字段存進(jìn)去,需要的時候不用現(xiàn)場計算,只要從文檔里讀出來即可。

    小結(jié)一下,冗余、扇出寫、分桶,這些都是mongodb 的一些常用優(yōu)化手段。 大家可以看到,通過減少額外查詢或者關(guān)聯(lián)的需求,通過使用冗余、額外存儲的非常規(guī)方式,我們希望做到的是性能上的最高提升。

    MongoDB 中國團(tuán)隊正在擴(kuò)張中。希望和一流的、創(chuàng)新的數(shù)據(jù)庫團(tuán)隊一起工作嗎?加入我們吧,我們在尋找有開發(fā)架構(gòu)或者數(shù)據(jù)庫相關(guān)經(jīng)驗的大牛們加入我們的技術(shù)顧問陣營。有興趣?加微信 tjtang826 私聊吧!

    總結(jié)

    以上是生活随笔為你收集整理的MongoDB 表设计的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。