從好久之前,我就開始接觸開源產品:從最開始的使用、受益者到後來的貢獻者,到如今的熱情推廣者。如今,我是MongoDB的技術顧問。個人職責是爲MongoDB的客戶和用戶提供MongoDB使用的一些最佳實踐,包括模式設計、性能優化和集羣部署方案等方面。javascript
今天的話題是進階模式,因此我假設在坐各位至少是已經對MongoDB有了一些基本的瞭解。 不過每次總有一些同窗覺得這裏有水果吃才坐進來的,因此在這裏我簡單介紹一下:MongoDB 不是芒果(mango),它在拉丁文中的原意是巨大的意思。若是用一句話來歸納的話,mongo是一個高可用、分佈式、無模式的文檔數據庫。等一下,這裏我故意用錯了一個詞: 不是無模式,而是「靈活模式」。 若是真的是無模式,今天我就不用站在這裏了。沒有模式何來模式設計之說。在你開始用mongo作一些 prototype的時候,確實不用考慮太多的模式。MongoDB內存數據庫的一些特性,讓你在前期不會遇到什麼問題。可是一旦涉及到幾千萬幾十億的數據量,或者是數千數萬的併發量,模式設計就是個你必須提早面對的問題。java
在咱們談mongo的模式設計以前,咱們頗有必要來了解一下MongoDB的數據模型。你們都知道,不管你從哪一個角度來看,MongoDB都是目前NoSQL,或者說非關係型的數據庫中的領頭羊。那麼,mongo和傳統關係數據庫的最本質的區別在那裏呢?咱們說是它的文檔模型。mongodb
關係模型和文檔模型的區別在哪裏?數據庫
雖然MongoDB的模型和關係型大相徑庭,可是關係型數據庫的一些必不可少的功能如動態查詢、二級索引、聚合等在MongoDB中也有很是完善的支持。設計模式
這裏我介紹一下文檔模型的優勢:數組
那麼咱們如何考慮MongoDB 文檔模式設計的基本策略呢?性能優化
不少時候咱們並不能很好地回答本身的問題,包括剛纔的內嵌仍是引用的問題。那麼這個時候有必要了解一下,MongoDB模式設計的終極原則。MongoDB的模式設計和關係型大不相同,咱們說MongoDB是爲應用程序設計的,而不是爲了存儲優化的。若是能夠達到最高性能的話,咱們甚至能夠作一些反範式的東西。 接下來咱們來看幾個比較具體的設計案例,瞭解一下MongoDB的模式設計思路:服務器
我這裏準備了4個比較經典的MongoDB案例,從CMS 內容管理到電商,社交到物聯網。 因爲時間緣由我就從第二個開始。微信
在電商方面MongoDB的應用場景其實蠻多,好比說,大名鼎鼎的京東用mongo來存儲過億的商品信息,另外有一家著名的境外電商從頭至尾用的都是MongoDB,包括訂單管理等。這裏咱們就來看一下購物車這個場景。購物車的特色就是單個購物車數據項不會太大,通常來講不會超過100項目。雙十一的時候淘寶的購物車裏最多就只能放99件商品。在這裏我要謝謝個人太太,是她讓我知道了這個限制。另一點就是購物車的數據可能須要過時刪除。網絡
咱們說文檔模型在這種場景會是個很好的選擇:
你們看一下下面的參考數據模型,第一點注意咱們可使用MongoDB的TTL 索引來自動清理過時數據。TTL索引能夠創建在任意一個時間字段上,在創建索引的時候能夠指定文檔在過多少時間後會被自動清理掉。第二個你們注意的是什麼呢?在這裏咱們把商品的一些主要信息放到購物車裏了,好比說 name,price, quantity,爲何? 讀一次全部信息都拿到了:價格、數量等等,不須要再去查另外一張表。這是一種比較常見的優化手段,用冗餘的方式來提供讀取性能。
接下來咱們看一下使用這種模式的時候如何進行一些購物車的操做。好比說,若是咱們想要往購物車裏增長一個價值2元的麪包,咱們能夠用下面的update語句。注意$push的用法。$push 相似於javascript的操做符,意思是往數組尾部增長一個元素。
若是須要更新購物車中某個產品的數量,你能夠用update語句直接操做數組的某一個元素。在這裏咱們須要作的是更新item 4567的數量爲5。 注意 items.$.quanity的使用,這裏的$ 表示在查詢條件裏匹配上的數組元素的序數。
若是須要統計一下在購物車內某個商品的總數,可使用MongoDB的聚合功能。聚合運算在MongoDB裏面是對數據輸入源進行一系列的運算。在這裏咱們作的就是幾個步驟是:
下面咱們來看一個社交網絡的例子。社交app最關鍵的一些場景就是維護朋友關係以及朋友圈或微博牆等。
對於關係描述,使用文檔模型的內嵌數組特性,咱們能夠很容易地把我關注的用戶(following)和關注個人用戶表示出來。下例表示TJ個人關注的用戶是mandy和bert,而oscar和mandy則在關注我。這種模式是文檔模型中最經典的。可是有一個潛在問題就是若是TJ我是一個明星,他們關注個人人可能有千萬。一個千萬級的數組會有兩個問題:1) 有可能超出一個文檔最大16M的硬性限制; 2) MongoDB數組太大會嚴重影響性能。
怎麼辦?咱們能夠創建一個專門的集合來描述關注關係。這裏就是一個內嵌和引用的經典選擇。咱們但願用內嵌,可是若是數組維度太大,就須要考慮用另一個集合的方式來表示一對多的關係(用戶 1–N 關注者)
另一個要注意的是關注數,咱們在顯示關注和粉絲數量的時候,不但願去跑一次count 查詢再顯示。由於count操做通常來講會比較佔資源。一般的作法能夠再用戶對象裏面加兩個字段,一個是關注數一個是粉絲數。每次有人關注或者關注別人時候就更新一下。
下面咱們來看看比較有趣的微博牆,或者微信朋友圈的實現有什麼考量。
在實現微博牆的時候,有兩種方式能夠考慮:扇出讀 或者是扇出寫。
扇出讀、扇出寫的說法是基於社交網絡的海量用戶、海量數據的應用特徵。這些大量的數據每每分佈在各個分片服務器上。扇出讀是一種比較常規的作法,就是當你須要去得到全部你關注用戶的最新更新的時候,你就去到每個你關注用戶的數據區,把最新的一些數據取回來。由於須要去到不一樣的分片服務器去取,因此叫作扇出讀。你們能夠想象,這種扇出讀的效率不會過高,基本上是最慢的那個服務器的響應時間決定了整體的響應時間。 固然,這種方式是比較簡單的,不須要特殊處理。
扇出寫,我稱之爲土豪玩法。具體來講就是當發佈的時候,一條數據會寫屢次,直接寫到每個關注你的粉絲的牆上。這樣作的好處是當你的粉絲讀他本身的微博牆的時候,他只須要去一個地方就能夠把全部最新的更新連續取回來。因爲一個用戶的數據可通常能夠存儲在同一臺服務器上的同一個區域,經過這種方式能夠實現快速的讀取微博牆數據。 代價固然也是很明顯: 你的寫入需求會被放大幾十幾百倍,存儲也是相應的擴大幾十幾百倍。這個絕對不是關係型數據庫的玩法,可是在MongoD 模式設計,這個很正常。只要保證性能,什麼事情都作得出來。
下面這個例子,首先是mandy在發消息的時候會寫(push)到個人牆上(timeline)來。若是mandy有50個關注者,那麼這個寫就會有50次,每一個關注者一次。
第二條語句就是我打開微博的時候,一條語句,一個地方就能夠找到全部我朋友發的狀態更新。注意:這裏還使用了bucket,這是另一個控制文檔內數組元素個數的有效方法。好比說咱們定義bucket 大小是1000的話,超過1000 就把新的數據插入到下一個文檔並對bucket 序數遞增。
好了,最後咱們來看一下物聯網的應用場景:
各位還有多少人仍然記得MH370,去年在印度洋消失的客機?在該事故以後,許多人都在疑惑:在當今的技術水平下,爲何咱們不能跟蹤如此龐大的一個東西?
讓咱們來看看若是要監控飛機數據有什麼樣的挑戰。飛機上面的數據源衆多,光收集位置信息,就須要多個系統協做完成, 如ADS-C, EUROCONTROL等等。此外,收集的數據也是各類各樣:位置是2D、速度是數值、引擎參數則是多維度的。
另外一個挑戰就是海量數據。一個三小時的航班,每分鐘採集一次,少說點,每次100條數據,那就是每秒1萬8千個數據點。按天天100,000航班,一天的數據算下來有18億條,1.8TB 左右的數據, 21,000 的QPS。 從哪一個角度來看,這都是個經典的大數據問題。
這個問題在關係型數據庫解決的話,比較幼稚的方法就是設計一個超寬的表。全部須要採集的每個值就是一個列。這種設計的問題比較明顯:
另外一種改良方案是用EAV 設計模式。就是採用一個主表和一個屬性值表。在屬性值表裏存放全部的參數鍵值對。這樣作的好處天然是靈活性:增長新的參數時無需修改模式。可是問題一樣存在:用來存儲值的那列METRIC_VALUE
的字節大小必須定義成全部值的最大值 才能夠放下全部的參數值。這個可能帶來空間浪費,可是更嚴重的問題是:將不太可能在此字段上建索引,進而影響一些場景的使用。
下面咱們來看看文檔模型怎麼作: 這裏對於location 、speed 等不一樣數據類型的字段,在文檔模型下能夠直接支持。下面的兩個文檔,第一個文檔和第二個文檔能夠同屬一個集合,可是能夠有徹底不一樣的字段。 MongoDB對異構數據的支持在這樣的場景下有得天獨厚的優點。若是咱們但願對某一個metric如location創建索引,咱們也可使用mongoDB的稀疏索引 (Sparse Index)僅對有location字段的文檔建索引,在不形成索引空間浪費的前提下提升檢索效率。當須要增長新的字段的時候,也不須要對模式作任何修改,能夠直接就在應用中的JSON模型裏添加須要的字段(elevation)。
在IOT這個場景裏,咱們可使用一個叫作分桶的設計方式來進行幾十倍的性能增加。具體來講就是把採集的數據按小時爲一個桶,把每小時的數據聚合到一個文檔裏。以下面所示,每分鐘的值用子文檔的一個字段來表示。這樣作的好處就是大量減小文檔的數量,相應的索引數量也會減小,整體寫入IO將會大幅度下降並獲得性能提高。
使用這種方式咱們還能夠把一些統計須要的數值,如每小時的平均值預先就做爲一個字段存進去,須要的時候不用現場計算,只要從文檔裏讀出來便可。
小結一下,冗餘、扇出寫、分桶,這些都是mongodb 的一些經常使用優化手段。 你們能夠看到,經過減小額外查詢或者關聯的需求,經過使用冗餘、額外存儲的很是規方式,咱們但願作到的是性能上的最高提高。
MongoDB 中國團隊正在擴張中。但願和一流的、創新的數據庫團隊一塊兒工做嗎?加入咱們吧,咱們在尋找有開發架構或者數據庫相關經驗的大牛們加入咱們的技術顧問陣營。有興趣?加微信 tjtang826 私聊吧!