MongoDB 是一款流行的開源文檔型數據庫,從它的命名來看,確實是有必定野心的。
MongoDB 的原名一開始來自於 英文單詞"Humongous", 中文含義是指"龐大",即命名者的意圖是能夠處理大規模的數據。mysql
但筆者更喜歡稱呼它爲 "芒果"數據庫,除了譯音更加相近以外,緣由還來自於這幾年使用 MongoDB 的兩層感受:算法
第一層感覺是"爽",使用這個文檔數據庫的特色是幾乎不受什麼限制,一方面Json文檔式的結構更容易理解,而無Schema約束也讓DDL管理更加簡單,一切均可以很快速的進行。sql
第二層感覺是"酸爽",這點相信幹運維或是支撐性工做的兄弟感覺會比較深入,MongoDB 因爲入門體驗"太過於友好",致使一些團隊認爲用好這個數據庫是個很簡單的事情,因此開發兄弟在存量系統上埋一些坑也是正常的事情。
所謂交付一時爽,維護火葬場.. 固然了,這句話可能有些過。 但這裏的潛臺詞是:與傳統的RDBMS數據庫同樣,MongoDB 在使用上也須要認真的考量和看護,否則的化,會遇到更多的坑。mongodb
那麼,儘管文檔數據庫在選型上會讓一些團隊望而卻步,仍然不阻礙該數據庫所得到的一些支持,好比 DB-Engine 上的排名:數據庫
圖-DBEngine排名json
在所有的排名中,MongoDB 長期排在第5位(文檔數據庫排名第1位),同時也是最受歡迎的 NoSQL 數據庫。
另外,MongoDB 的社區一直比較活躍,加上商業上的驅動(MongoDB於2017年在納斯達克上市),這些因素都推進了該開源數據庫的發展。數組
若是對於 MongoDB的發展史感興趣,能夠參考下沒有一個技術天生完美,MongoDB 十年發展全紀錄這篇文章。服務器
MongoDB 數據庫的一些特性:網絡
假定你是初次瞭解 MongoDB,下面的內容將能幫助你對該數據庫技術的全貌產生必定的瞭解。
數據結構對於一個軟件來講是相當重要的,MongoDB 在概念模型上參考了 SQL數據庫,但並不是徹底相同。
關於這點,也有人說,MongoDB 是 NoSQL中最像SQL的數據庫..
以下表所示:
SQL概念 | MongoDB概念 |
---|---|
database | database |
table | collection |
row | document |
column | field |
另外,SQL 還有一些其餘的概念,對應關係以下:
SQL概念 | MongoDB概念 |
---|---|
primary key | _id |
foreign key | reference |
view | view |
index | index |
join | $lookup |
transaction | trasaction |
group by | aggregation |
MongoDB 文檔可使用 Javascript 對象表示,從格式上講,是基於 JSON 的。
一個典型的文檔以下:
{ "_id": 1, "name" : { "first" : "John", "last" : "Backus" }, "contribs" : [ "Fortran", "ALGOL", "Backus-Naur Form", "FP" ], "awards" : [ { "award" : "W.W. McDowell Award", "year" : 1967, "by" : "IEEE Computer Society" }, { "award" : "Draper Prize", "year" : 1993, "by" : "National Academy of Engineering" } ] }
曾經,JSON 的出現及流行讓 Web 2.0 的數據傳輸變得很是簡單,因此使用 JSON 語法是很是容易讓開發者接受的。
可是 JSON 也有本身的短板,好比沒法支持像日期這樣的特定數據類型,所以 MongoDB 實際上使用的是一種擴展式的JSON,叫 BSON(Binary JSON)。
BSON 所支持的數據類型包括:
圖-BSON類型
在單機時代,大多數應用可使用數據庫自增式ID 來做爲主鍵。 傳統的 RDBMS 也都支持這種方式,好比 mysql 能夠經過聲明 auto_increment來實現自增的主鍵。 但一旦數據實現了分佈式存儲,這種方式就再也不適用了,緣由就在於沒法保證多個節點上的主鍵不出現重複。
爲了實現分佈式數據ID的惟一性保證,應用開發者提出了本身的方案,而大多數方案中都會將ID分段生成,如著名的 snowflake 算法中就同時使用了時間戳、機器號、進程號以及隨機數來保證惟一性。
MongoDB 採用 ObjectId 來表示主鍵的類型,數據庫中每一個文檔都擁有一個_id 字段表示主鍵。
_id 的生成規則以下:
圖-ObjecteID
其中包括:
值得一提的是 _id 的生成實質上是由客戶端(Driver)生成的,這樣能夠得到更好的隨機性,同時下降服務端的負載。
固然服務端也會檢測寫入的文檔是否包含_id 字段,若是沒有就生成一個。
除了文檔模型自己,對於數據的操做命令也是基於JSON/BSON 格式的語法。
好比插入文檔的操做:
db.book.insert( { title: "My first blog post", published: new Date(), tags: [ "NoSQL", "MongoDB" ], type: "Work", author : "James", viewCount: 25, commentCount: 2 } )
執行文檔查找:
db.book.find({author : "James"})
更新文檔的命令:
db.book.update( {"_id" : ObjectId("5c61301c15338f68639e6802")}, {"$inc": {"viewCount": 3} } )
刪除文檔的命令:
db.book.remove({"_id": ObjectId("5c612b2f15338f68639e67d5")})
在傳統的SQL語法中,能夠限定返回的字段,MongoDB可使用Projection來表示:
db.book.find({"author": "James"}, {"_id": 1, "title": 1, "author": 1})
實現簡單的分頁查詢:
db.book.find({}) .sort({"viewCount" : -1}) .skip(10).limit(5)
這種基於BSON/JSON 的語法格式並不複雜,它的表達能力或許要比SQL更增強大。
與 MongoDB 作法相似的還有 ElasticSearch,後者是搜索數據庫的佼佼者。
關於文檔操做與 SQL方式完整的對比,官方的文檔描述得比較詳細:
https://docs.mongodb.com/manual/reference/sql-comparison/
那麼,一個有趣的問題是 MongoDB 能不能用 SQL進行查詢?
固然是能夠!
但須要注意這些功能並非 MongoDB 原生自帶的,而須要藉由第三方工具平臺實現:
無疑,索引是一個數據庫的關鍵能力,MongoDB 支持很是豐富的索引類型。
利用這些索引,能夠實現快速的數據查找,而索引的類型和特性則是針對不一樣的應用場景設計的。
索引的技術實現依賴於底層的存儲引擎,在當前的版本中 MongoDB 使用 wiredTiger 做爲默認的引擎。
在索引的實現上使用了 B+樹的結構,這與其餘的傳統數據庫並無什麼不一樣。
因此這是個好消息,大部分基於SQL數據庫的一些索引調優技巧在 MongoDB 上仍然是可行的。
圖-B+樹
使用 ensureIndexes 能夠爲集合聲明一個普通的索引:
db.book.ensureIndex({author: 1})
author後面的數字 1 表明升序,若是是降序則是 -1
實現複合式(compound)的索引,以下:
db.book.ensureIndex({type: 1, published: 1})
只有對於複合式索引時,索引鍵的順序才變得有意義
若是索引的字段是數組類型,該索引就自動成爲數組(multikey)索引:
db.book.ensureIndex({tags: 1})
MongoDB 能夠在複合索引上包含數組的字段,但最多隻能包含一個
在聲明索引時,還能夠經過一些參數化選項來爲索引賦予必定的特性,包括:
除了普通索引以外,MongoDB 支持的類型還包括:
使用 explain() 命令能夠用於查詢計劃分析,進一步評估索引的效果。
以下:
> db.test.explain().find( { a : 5 } ) { "queryPlanner" : { ... "winningPlan" : { "stage" : "FETCH", "inputStage" : { "stage" : "IXSCAN", "keyPattern" : { "a" : 5 }, "indexName" : "a_1", "isMultiKey" : false, "direction" : "forward", "indexBounds" : {"a" : ["[5.0, 5.0]"]} } }}, ... }
從結果 winningPlan 中能夠看出執行計劃是否高效,好比:
關於 explain 的結果說明,能夠進一步參考文檔:
https://docs.mongodb.com/manual/reference/explain-results/index.html
在大數據領域經常提到的4V特徵中,Volume(數據量大)是首當其衝被說起的。
因爲單機垂直擴展能力的侷限,水平擴展的方式則顯得更加的靠譜。 MongoDB 自帶了這種能力,能夠將數據存儲到多個機器上以提供更大的容量和負載能力。
此外,同時爲了保證數據的高可用,MongoDB 採用副本集的方式來實現數據複製。
一個典型的MongoDB集羣架構會同時採用分片+副本集的方式,以下圖:
圖-MongoDB 分片集羣(Shard Cluster)
架構說明
數據分片(Shards)
分片用於存儲真正的集羣數據,能夠是一個單獨的 Mongod實例,也能夠是一個副本集。 生產環境下Shard通常是一個 Replica Set,以防止該數據片的單點故障。
對於分片集合(sharded collection)來講,每一個分片上都存儲了集合的一部分數據(按照分片鍵切分),若是集合沒有分片,那麼該集合的數據都存儲在數據庫的 Primary Shard中。
配置服務器(Config Servers)
保存集羣的元數據(metadata),包含各個Shard的路由規則,配置服務器由一個副本集(ReplicaSet)組成。
查詢路由(Query Routers)
Mongos是 Sharded Cluster 的訪問入口,其自己並不持久化數據 。Mongos啓動後,會從 Config Server 加載元數據,開始提供服務,並將用戶的請求正確路由到對應的Shard。
Sharding 集羣能夠部署多個 Mongos 以分擔客戶端請求的壓力。
下面的幾個細節,對於理解和應用 MongoDB 的分片機制比較重要,因此有必要說起一下:
1. 數據如何切分
首先,基於分片切分後的數據塊稱爲 chunk,一個分片後的集合會包含多個 chunk,每一個 chunk 位於哪一個分片(Shard) 則記錄在 Config Server(配置服務器)上。
Mongos 在操做分片集合時,會自動根據分片鍵找到對應的 chunk,並向該 chunk 所在的分片發起操做請求。
數據是根據分片策略來進行切分的,而分片策略則由 分片鍵(ShardKey)+分片算法(ShardStrategy)組成。
MongoDB 支持兩種分片算法:
如上圖所示,假設集合根據x字段來分片,x的取值範圍爲[minKey, maxKey](x爲整型,這裏的minKey、maxKey爲整型的最小值和最大值),將整個取值範圍劃分爲多個chunk,每一個chunk(默認配置爲64MB)包含其中一小段的數據:
如Chunk1包含x的取值在[minKey, -75)的全部文檔,而Chunk2包含x取值在[-75, 25)之間的全部文檔...
範圍分片能很好的知足範圍查詢的需求,好比想查詢x的值在[-30, 10]之間的全部文檔,這時 Mongos 直接能將請求路由到 Chunk2,就能查詢出全部符合條件的文檔。 範圍分片的缺點在於,若是 ShardKey 有明顯遞增(或者遞減)趨勢,則新插入的文檔多會分佈到同一個chunk,沒法擴展寫的能力,好比使用_id做爲 ShardKey,而MongoDB自動生成的id高位是時間戳,是持續遞增的。
Hash分片是根據用戶的 ShardKey 先計算出hash值(64bit整型),再根據hash值按照範圍分片的策略將文檔分佈到不一樣的 chunk。
因爲 hash值的計算是隨機的,所以 Hash 分片具備很好的離散性,能夠將數據隨機分發到不一樣的 chunk 上。 Hash 分片能夠充分的擴展寫能力,彌補了範圍分片的不足,但不能高效的服務範圍查詢,全部的範圍查詢要查詢多個 chunk 才能找出知足條件的文檔。
2. 如何保證均衡
如前面的說明中,數據是分佈在不一樣的 chunk上的,而 chunk 則會分配到不一樣的分片上,那麼如何保證分片上的 數據(chunk) 是均衡的呢?
在真實的場景中,會存在下面兩種狀況:
A. 全預分配,chunk 的數量和 shard 都是預先定義好的,好比 10個shard,存儲1000個chunk,那麼每一個shard 分別擁有100個chunk。
此時集羣已是均衡的狀態(這裏假定)
B. 非預分配,這種狀況則比較複雜,通常當一個 chunk 太大時會產生分裂(split),不斷分裂的結果會致使不均衡;或者動態擴容增長分片時,也會出現不均衡的狀態。 這種不均衡的狀態由集羣均衡器進行檢測,一旦發現了不均衡則執行 chunk數據的搬遷達到均衡。
MongoDB 的數據均衡器運行於 Primary Config Server(配置服務器的主節點)上,而該節點也同時會控制 Chunk 數據的搬遷流程。
圖-數據自動均衡
對於數據的不均衡是根據兩個分片上的 Chunk 個數差別來斷定的,閾值對應表以下:
Number of Chunks | Migration Threshold |
---|---|
Fewer than 20 | 2 |
20-79 | 4 |
80 and greater | 8 |
MongoDB 的數據遷移對集羣性能存在必定影響,這點沒法避免,目前的規避手段只能是將均衡窗口對齊到業務閒時段。
3. 應用高可用
應用節點能夠經過同時鏈接多個 Mongos 來實現高可用,以下:
圖- mongos 高可用
固然,鏈接高可用的功能是由 Driver 實現的。
副本集又是另外一個話題,實質上除了前面架構圖所體現的,副本集能夠做爲 Shard Cluster 中的一個Shard(片)以外,對於規模較小的業務來講,也可使用一個單副本集的方式進行部署。
MongoDB 的副本集採起了一主多從的結構,即一個Primary Node + N* Secondary Node的方式,數據從主節點寫入,並複製到多個備節點。
典型的架構以下:
利用副本集,咱們能夠實現::
請注意,讀寫分離只能增長集羣"讀"的能力,對於寫負載很是高的狀況卻無能爲力。
對此需求,使用分片集羣並增長分片,或者提高數據庫節點的磁盤IO、CPU能力能夠取得必定效果。
選舉
MongoDB 副本集經過 Raft 算法來完成主節點的選舉,這個環節在初始化的時候會自動完成,以下面的命令:
config = { _id : "my_replica_set", members : [ {_id : 0, host : "rs1.example.net:27017"}, {_id : 1, host : "rs2.example.net:27017"}, {_id : 2, host : "rs3.example.net:27017"}, ] } rs.initiate(config)
initiate 命令用於實現副本集的初始化,在選舉完成後,經過 isMaster()命令就能夠看到選舉的結果:
> db.isMaster() { "hosts" : [ "192.168.100.1:27030", "192.168.100.2:27030", "192.168.100.3:27030" ], "setName" : "myReplSet", "setVersion" : 1, "ismaster" : true, "secondary" : false, "primary" : "192.168.100.1:27030", "me" : "192.168.100.1:27030", "electionId" : ObjectId("7fffffff0000000000000001"), "ok" : 1 }
受 Raft算法的影響,主節點的選舉須要知足"大多數"原則,能夠參考下表:
投票成員數 | 大多數 |
---|---|
1 | 1 |
2 | 2 |
3 | 2 |
4 | 3 |
5 | 3 |
所以,爲了不出現平票的狀況,副本集的部署通常採用是基數個節點,好比3個,正所謂三人行必有我師..
心跳
在高可用的實現機制中,心跳(heartbeat)是很是關鍵的,判斷一個節點是否宕機就取決於這個節點的心跳是否仍是正常的。
副本集中的每一個節點上都會定時向其餘節點發送心跳,以此來感知其餘節點的變化,好比是否失效、或者角色發生了變化。
利用心跳,MongoDB 副本集實現了自動故障轉移的功能,以下圖:
默認狀況下,節點會每2秒向其餘節點發出心跳,這其中包括了主節點。 若是備節點在10秒內沒有收到主節點的響應就會主動發起選舉。
此時新一輪選舉開始,新的主節點會產生並接管原來主節點的業務。 整個過程對於上層是透明的,應用並不須要感知,由於 Mongos 會自動發現這些變化。
若是應用僅僅使用了單個副本集,那麼就會由 Driver 層來自動完成處理。
複製
主節點和備節點的數據是經過日誌(oplog)複製來實現的,這很相似於 mysql 的 binlog。
在每個副本集的節點中,都會存在一個名爲local.oplog.rs的特殊集合。 當 Primary 上的寫操做完成後,會向該集合中寫入一條oplog,
而 Secondary 則持續從 Primary 拉取新的 oplog 並在本地進行回放以達到同步的目的。
下面,看看一條 oplog 的具體形式:
{ "ts" : Timestamp(1446011584, 2), "h" : NumberLong("1687359108795812092"), "v" : 2, "op" : "i", "ns" : "test.nosql", "o" : { "_id" : ObjectId("563062c0b085733f34ab4129"), "name" : "mongodb", "score" : "100" } }
其中的一些關鍵字段有:
MongoDB 對於 oplog 的設計是比較仔細的,好比:
有興趣的讀者,能夠參考官方文檔:
https://docs.mongodb.com/manual/core/replica-set-oplog/index.html
一直以來,"不支持事務" 是 MongoDB 一直被詬病的問題,固然也能夠說這是 NoSQL 數據庫的一種權衡(放棄事務,追求高性能、高可擴展)
但實質上,MongoDB 很早就有事務的概念,可是這個事務只能是針對單文檔的,即單個文檔的操做是有原子性保證的。
在4.0 版本以後,MongoDB 開始支持多文檔的事務:
在事務的隔離性上,MongoDB 支持快照(snapshot)的隔離級別,能夠避免髒讀、不可重複讀和幻讀。
儘管有了真正意義上的事務功能,但多文檔事務對於性能有必定的影響,應用應該在充分評估後再作選用。
一致性是一個複雜的話題,而一致性更多從應用角度上提出的,好比:
向系統寫入一條數據,應該可以立刻讀到寫入的這個數據。
在分佈式架構的CAP理論以及許多延續的觀點中提到,因爲網絡分區的存在,要求系統在一致性和可用性之間作出選擇,而不能二者兼得。
圖 -CAP理論
在 MongoDB 中,這個選擇是能夠由開發者來定的。 MongoDB 容許客戶端爲其操做設定必定的級別或者偏好,包括:
使用不一樣的設定將會產生對於C(一致性)、A(可用性)的不一樣的抉擇,好比:
關於這種權衡的討論會一直存在,而 MongoDB 除了提供多樣化的選擇以外,其主要是經過複製、基於心跳的自動failover等機制來下降系統發生故障時產生的影響,從而提高總體的可用性。
本文主要揭示了 MongoDB 多個方面的細節,同時在使用體驗上也藉助 SQL 的概念作了一些對比。
從筆者的角度看,MongoDB 的發展性是很強的,其靈活快速的開發模式、天生自帶分佈式等能力彌補了傳統型SQL數據庫的缺陷。固然,目前的 NewSQL 本質上也貌似在以"模仿的方式"彌補這些缺陷。
但願本文的內容對你能有些參考。