天天看點

MongoDB簡單調研

背景

        一直受傳統rdb的影響,對于資料庫表的設計可能大多數開發者都形成了思維定勢。在雲計算和大資料背景下,rdbms正在接近極限,kv存儲将受到越來越多的關注。學習nosql,不求能革rdbms的命,但希望在設計思路上能得到一些拓寬,很多場景裡,sql表的設計和計算語句其實蠻難受的。

       rdbms天生不是分布式的,因其保持着acid的特性發展至今,非常重視資料完整性,但在機器規模增長的情況下,acid是不可擴充的。同時,随着資料量和通路頻率增加,acid所要維護的開銷在增大。分割資料庫,無論水準還是垂直,都是在分散總資料和讀取需求,達到優化目的,維護代價和難度也随之上升。而kv的查找本質是散清單,且資料量無論如何增大,查找時間幾乎固定不變,即非常适合大規模資料。acid很注重cap中的c,而參考現實世界中很多事務,比如快遞,從你下單、付款到取貨,資金和物品的流轉并不嚴格一緻,隻要在一段時間内整個交易的最後結果滿足一緻性就可以了。同樣,nosql和rdbms比,更偏向于base(basically

available, soft-state, eventually consistent)的折中,重視可用性,但不追求狀态的嚴密性,且滿足最終一緻性。下面我以mongodb為例,展現一些他的特性和場景,期待nosql在當下能被更多的開發者拿來一顯身手。

mongodb與rdbms

        mongodb是面向文檔的nosql,couchdb則是這一類資料庫的元祖。從總體上看,

1. mongodb是最親和rdbms的一個nosql,能解決大部分關系型資料庫解決的問題

2. 跟面向列存儲的hbase相比,面向文檔存儲和面向行存儲更接近,比如在沒有索引的情況下,掃描整個表内記錄,同樣是掃描全文檔,及文檔的每個字段

3. mongodb的索引同樣也是b樹,在一些索引的優化和設計上會和mysql比較相似(當然需要遵循mongo的設計來做,不完全劃等号)

4. 你可以把mongodb拿rdbms一樣來使用(當然不推薦這麼做),無非是将一行記錄變成mongodb裡的json對,在document(相當于mysql的table)之間,也可以做類似外鍵一樣的引用

5. mongodb雖然沒有嚴格的事務性操作,但是開發者自己可以做到類似事務的效果(詳見下文)。這一點也算是mongodb貼近rdbms的一個表現吧。

6. mongo的查詢中也有mysql中的in, where, group等字段,而且想group那樣的查詢會比mysql更強大(見下文)

MongoDB簡單調研

        以下會從各個主要關注點來展開mongo的特性,展現角度更偏向于想要調研使用mongodb的人,看看mongodb是否符合自己的業務場景,也希望我的分析會有所幫助。

存儲結構怎麼樣

        mongodb的存儲類似json,每個db内有多個collection,相當于table,每個collection内是許許多多的document,這個document的schemeless的。本質上,他的面向文檔指的是key-value中的value,而這個value可以是一個值(引用id或基本類型),可以是一個數組,也可以是一個文檔(嵌套的json對)。

        一對多是最常遇到的場景,mysql中要使用兩張或以上表的關聯甚至join進行查詢,在mongo中直接使用嵌套型或引用型(用id)就可以了。沒有特殊需求的話,嵌套的方式隻要一張"表"就可以實作。比如我這樣建立一個人的資訊:

        上述這樣的結構中,展現了無模式、value為數組、嵌套、引用等。

        處理好多對多的關系可謂是nosql的精髓所在。理論上,可以在一個集合中完成存儲。不過實際上這樣的情況非常罕見。這是由于查詢的多樣性所導緻的,若是隻有一種類型的查詢,則這種多對多的關系放在一個良好設計的集合中,雖然會有大量的備援,但是效率一定是最高的。如何設計這種資料庫的關鍵就是看你有多少種查詢,每一種的頻率是多少,使用的其他要求是什麼樣的。對于不同的查詢,同樣的資料庫設計的性能也是大不一樣。還有一點,一般不要拆成三個集合,這是傳統的關系型資料庫的思維方式。而常見的情況就是拆成兩個集合,然後有一部分備援,對最常用的查詢做一個索引。

        總結就是兩張表,一張裡面存了另外一張裡的id集合,有備援存放,主要是根據查詢場景設計和建索引,不要和rdbms一樣變三張。此外還有個好處是可以進行正反向查詢,在各自的字段裡加上id數組。

創新、更新、删除文檔

為避免不良設計,doc大小有限制,mongodb 1.8支援16m

批量插入 mongoimport

更新:文檔替換:模式結構有大變化時,update(find_the_doc, new_doc)

          修改器:$inc 操作整數,雙精度浮點數 小範圍改動 速度很快,如果不存在則建立 {"$inc" : {"keyname" : 100 }}

                       $set $unset 修改某個鍵,如果不存在則建立

                       $push $pop 操作數組  $ne來判斷某個數組裡是否存在某值 $addtoset和$each組合

          修改器速度:$inc屬于就地修改  而數組修改器可能更改了文檔大小,會慢

                             mongodb給文檔預留了補白,來适應大小變化,如果超出了則重新配置設定空間,就會慢了

                             是以要自己設計。不然$push可能會成為瓶頸,就考慮把内嵌的數組獨立出來,單獨放到集合裡

          特殊的更新 upsert (update第三個參數)是原子性的,比如db.math.update({"count":25}, {"$inc":{"count":3}}, true) 如果沒有count會生成,然後繼續加3

           update的時候,第一個參數是找符合的doc,第二個參數是更新操作,第三個是否upsert,第四個參數是是否更新多個文檔

          執行完後運作getlasterror看有沒有更新出錯

          findandmodify,等待資料庫相應,在操作查詢、取值和指派整個原子性操作時很友善,速度慢些 

查詢(簡單例舉一下)

find()指定傳回的鍵

查詢條件 $lt $gt $lte $gte 别的查詢鍵 $or $in $nin $not等,不具體舉例了,不是特性了解就好

查詢數組相關:$all $size $slice

正規表達式:支援perl相容的正規表達式(pcre)

對查詢結果的處理 $limit $skip $sort (其中$skip适合跳過少量文檔,否則性能影響)

以上不能滿足,還有$where,更複雜的查詢是mapreduce(下文介紹)

聚合(mongodb本身的聚合操作可能可以好好依賴一下,比如olap裡複雜的查詢和本地聚合操作可以大量借用mapreduce?)

count()  distinct()  

group() 類似 group by,且可以附帶一個finalize函數對結果修剪

mapreduce可以做複雜的聚合查詢,并行化到多個伺服器,當然mapreduce和group都不适合實時場景

簡單示範一下mongodb的group和mapreduce的基本寫法,不深究細節,但求了解個大概,進階查詢可以做些什麼。

group:

mapreduce:

函數分為map, reduce,還有finalize,query,sort,scope等函數輔助mr。

利用好以上的查詢和聚合方法,加上合理設計,應該滿足大部分查詢場景。

索引怎麼樣

和傳統rdbms幾乎一樣,好處是同樣的索引優化方法适用于mongodb

被索引之後,會按照索引排序

查詢一半以上的結果,不用索引,表掃描就可以了,比如查某個布爾或存在某個鍵

預設最大索引個數64,每次插入更新删除都會産生額外開銷

用ensureindex:1 or -1來控制索引方向

對内嵌文檔中的鍵建索引和普通索引沒有什麼差別

用.explain()看查找情況,有沒有用索引,掃描了多少文檔等等

還有基于地理空間的索引,$near :[-40, 73]  $within $box / $center

索引補充:對沒有索引的鍵使用sort,mongo會把所有資料拿到記憶體排序,是以有上限,不能做t級别sort。是以可以為排序而建索引。

mongodb與js

        cmd環境下是js引擎,記得使用的是v8引擎(可能随着版本更新引擎會改變)。所有的執行語句都有一套js的api。同時,db.eval()可以執行js。更令人驚喜的是,system.js裡可以存儲js變量,是以也可以直接存儲js函數,然後用db.eval()執行調用。比如:

        然後調用它,

        輸出結果是:

        我對mongodb的這一特性充滿遐想,可能很多腳本和日常操作,包括運維操作,都可以用js寫好并存儲,而且js本身可以做一切mongodb的操作,這個特性可以給我們帶來更多的什麼呢?我覺得mongodb這樣的設計,和以前我們接觸的mysql這樣的資料庫有一個很本質的差別。我們通常意義中的資料庫隻負責存儲資料,除了sql提供一些查詢和增删改查外,其餘複雜的資料處理都需要通過各自語言的driver來連接配接資料庫并自己用代碼實作一些處理邏輯,最後再傳回到資料庫中。而mongodb,不但提供了可以任意執行js的環境,已經mapreduce這樣複雜的查詢設計,而且還支援儲存js代碼在system.js中,其實僅僅靠mongodb本身,就可以做任何你想做的資料操作和處理。這樣的好處是,使用mongodb的應用層可以完全解耦對mongodb資料處理的依賴,可能mongodb的dba可以除了專職維護外,用一些内置的js代碼完成複雜的處理邏輯,讓應用層可以放心使用預備好的資料。我覺得這在易用的同時做到了真正的強大。此外,可以想象前端開發人員如果單獨設計一套系統或搭建一個網站,會很喜歡使用mongodb。

事務性怎麼樣(沒有寫要求的可以忽略這段)

        mongo的事務性操作其實蠻巧妙的,他支援一個findandmodify的操作,是一個保證原子性的操作。顧名思義,在一個操作裡先find目标文檔,然後進行修改,整個過程由mongo保證原子。具體使用方法不介紹了。

        此外還有一套樂觀的并發控制。樂觀并發控制其實是update本身,基于認為“同一個文檔基本上不會被同時修改”這一樂觀的事務機制。整個事務的過程會分拆為以下幾個步驟,需要開發者自己來實作:

       通過查詢獲得文檔

      儲存文檔原始值

      更新得到一個新的文檔

       用原始文檔當作arg1,更新文檔當作arg2進行一次update操作

       若更新失敗則重複1

        這個并發控制其實就是一次文檔自身的替換,即update的目标是整個文檔。詳細的例子還有實作兩階段送出 http://docs.mongodb.org/manual/tutorial/perform-two-phase-commits/,即用一個記錄員collection來記錄一個事務(比如a和b之間彙款)進行過程中每一步的情況,包括initial, pending, commit, done等,進而可以進行恢複和确認事務進行情況。

 分區分庫怎麼樣

        有這樣的情況,同部門各個項目中,各組的dba會各自做各自的分庫分區,對于mysql的分區分庫不夠透明,,而在jdbc的sql語句層(不論使用的是mybatis還是spring jdbc等),不能完全将讀取資料庫路由與上層應用解耦。這應該是一個常見的問題。mongo的分片讀取都由路由來完成,應用程式隻要連接配接一個mongos路由進行即可,路由會從配置服務mongod裡獲得各個db和collection的情況以及分片片鍵,一般推薦一個mongos對應一個應用。

MongoDB簡單調研

        上圖是一種健壯的分片部署方式。黃色mongod為配置伺服器,綠色mongos為路由,藍色為真實存儲資料的分片。且每個shard裡有三個mongod,構成的是一個副本集(自動故障恢複的主從叢集,且沒有固定的主節點)。藍色mongod是實際存儲資料的分片,且shard之間需要手動設定負載均衡依據的片鍵,mongodb會自動做負載均衡,相關配置資訊會記錄在黃色mongod配置伺服器上。分片可以動态增加和減少,隻要啟動一個mongod,用指令添加到分片叢集裡,他就可以一起做負載了。而每個mongod其實都是一個單獨的mongodb執行個體,也可以不依賴叢集單獨啟動使用。應用程式通過連接配接路由mongos來和mongodb打交道,應該說讀取資料是比較透明。分片叢集的搭建,擴容與增減分片,副本集的搭建,應用層對分片叢集的使用接入等基本概念,具體可以參看我之前一篇部落格http://blog.csdn.net/pelick/article/details/8644116。總之,擴容、部

署、搭建等操作是非常友善的,這也是mongodb易用性高的一個重要原因。

gridfs存儲檔案

mongo自帶存儲大的二進制檔案。基本思想是把打檔案分成很多塊,每塊單獨作為一個文檔存。使用方面和存取一般的document一樣,也有分片機制,不産生磁盤碎片。視覺中國用gridfs存過大量圖檔。

管理和運維

./mongod --port xxx --logpath xxx 的啟動方式,也可以./mongod --config ~/.mongodb.conf 的配置檔案方式啟動

比啟動端口大1000号的端口有頁面版的管理資訊檢視

mongodump作備份,mongorestore恢複備份

(待補充)