前言
本文是對 Mongo 官方文檔粗略的總結(jié),并沒有涉及到很深的細(xì)節(jié)(細(xì)節(jié)還是直接看官方文檔吧)。我認(rèn)為 Mongo 有重要的就 3 點(diǎn):
- 存儲(chǔ)引擎原理,如何保證斷電后恢復(fù)數(shù)據(jù)?Mongo 的 data 在文件系統(tǒng)中,是如何組織和保存的?
- Replication
- Sharding
思維導(dǎo)圖
目錄

Basic

Aggregation & Data Modeling

Indexes

Storage


Replication & Sharding

思考
Document 在內(nèi)部是如何存儲(chǔ)的?
每個(gè) Document 被保存在一個(gè) Record
中。Record 相當(dāng)于 MongoDB 內(nèi)部分配的一塊空間,除了保存 Document 的內(nèi)容可能還會(huì)預(yù)留一些填充的額外空間
。對于寫入后的 Document 如果還會(huì)更新,可能導(dǎo)致 Document 長度增加,就可以利用上額外的填充空間來。若業(yè)務(wù)對于寫入后的 Document 不會(huì)再更新或刪除(像監(jiān)控日志、流水記錄等),可以指定無填充的 Record 分配策略,更節(jié)省空間。

單個(gè) Document 的容量是否有限制?
16MB
。Document 這種 JSON 形態(tài)天生會(huì)帶來數(shù)據(jù)存儲(chǔ)冗余,主要是 field 屬性每個(gè) Document 都會(huì)保存一遍。目前 3.2 版本的 MongoDB 已經(jīng)將新的 WiredTiger 作為默認(rèn)存儲(chǔ)引擎,它提供了壓縮功能,有兩種壓縮形式:
-
Snappy
默認(rèn)壓縮算法,在壓縮率和 CPU 開銷之間取得平衡。 -
Zlib
更高的壓縮率,但也帶來更高的 CPU 開銷。
而每個(gè) Document 依然有最大容量限制,不能無限增長下去,這個(gè)限制目前是 16MB。那么我要存大于 16MB 的文件怎么辦,MongoDB 提供了 GridFS 來存儲(chǔ)超過 16MB 大小的文件。如下圖所示,一個(gè)大文件被拆分成小的 File Chunk,每個(gè) Chunk 大小 255KB,并存放在一個(gè) Document 中。GridFS 使用了 2 個(gè) Collection 來分別存放文件 Chunk 和文件元數(shù)據(jù)。

遇到真正的「大數(shù)據(jù)」(單機(jī)存儲(chǔ)容量不夠)怎么辦?
分片化
:利用更多的機(jī)器來提供更大的容量,分片集群采用代理模式:

而每個(gè)分片上的數(shù)據(jù)又以 Chunk
的形式組織(類似于 Redis Cluster 的 Slot 概念),以便于集群內(nèi)部的數(shù)據(jù)遷移和再平衡。比較容易混淆的是這里的 Chunk 不是前面 GridFS 里提到的 Chunk,它們的關(guān)系大概如下圖:

Mongo 的數(shù)據(jù)安全嗎?在保證效率的同時(shí),在服務(wù)器突然宕機(jī)的情況下,是否能夠保存數(shù)據(jù)?
安全
和效率
其實(shí)是相互制約的,越安全則效率越低,越高效則越不安全。MongoDB 的設(shè)計(jì)場景考慮的是應(yīng)對大量的數(shù)據(jù)寫入和查詢,而數(shù)據(jù)的重要性相對沒那么高。所以 MongoDB 的默認(rèn)設(shè)置在安全和效率之間,更偏向效率。
Write To Buffer Without ACK

這個(gè)模式下 MongoDB 是不確認(rèn)寫請求的,Client 端調(diào)用驅(qū)動(dòng)寫入后若沒有網(wǎng)絡(luò)錯(cuò)誤就認(rèn)為成功,實(shí)際到底寫入成功沒有是不確定的。即使網(wǎng)絡(luò)沒有問題,數(shù)據(jù)到達(dá) MongoDB 后它先保存在內(nèi)存 Buffer 中,再異步寫入 Journaling 日志,這中間有 100ms(默認(rèn)值) 的落盤(寫入磁盤)時(shí)間窗口。一般數(shù)據(jù)庫的設(shè)計(jì)都是先寫 Journaling 的流水日志,隨后異步再寫真正的數(shù)據(jù)文件到磁盤,這個(gè)可能就比較長了,MongoDB 是 60 秒或者 Journaling 日志達(dá)到 2G。
Write To Buffer With ACK

這個(gè)比上一種模式稍微好一點(diǎn),MongoDB 收到寫入請求,先寫入內(nèi)存 Buffer 后回發(fā) Ack 確認(rèn)。Client 端能確保 MongoDB 收到了寫入數(shù)據(jù),但依然有短暫的 Journaling 日志落盤時(shí)差導(dǎo)致潛在的數(shù)據(jù)丟失可能。
Write To Journaling With ACK

這個(gè)模式確保至少寫入 Journaling 日志
后才回發(fā) Ack 確認(rèn),Client 端能確保數(shù)據(jù)至少寫入磁盤了,安全性較高。
Write To Replica Buffer With ACK

這個(gè)模式是針對多副本集的,為了提升數(shù)據(jù)安全性,除了及時(shí)寫入磁盤也可以通過寫多個(gè)副本來提升。在這個(gè)模式下,數(shù)據(jù)至少寫入 2 個(gè)副本的內(nèi)存 Buffer 中才回發(fā) Ack 確認(rèn)。雖然都在內(nèi)存 Buffer 中,但兩個(gè)實(shí)例在落盤短暫的 100ms 時(shí)差中同時(shí)故障的概率很低,所以安全性有所提升。
MMAPv1 和 WiredTiger 有什么區(qū)別?
- MMAPv1 是 Mongo 在 3.0 以前的存儲(chǔ)引擎,WiredTiger 是 Mongo 在 3.2 及以后版本的默認(rèn)存儲(chǔ)引擎;
- MMAPv1 只是單純地將 BSON 數(shù)據(jù)直接存儲(chǔ)在磁盤上,WiredTiger 則會(huì)在數(shù)據(jù)從內(nèi)存存儲(chǔ)到磁盤前進(jìn)行一次
壓縮
; - MMAPv1 在 3.0 版本之前,以 database 為單位加鎖,對同一個(gè)Database的其他Collection所做的操作也會(huì)被阻塞。 而到了 3.0 版本,MMAPv1 則開始使用以 Collection 為單位的加鎖。WiredTiger 是基于
Document 級鎖
機(jī)制。
MMAPv1 是如何分配記錄的?
在MongoDB中,每條數(shù)據(jù)以 Document
的形式進(jìn)行存儲(chǔ),并通過 Collection
來管理Document。同一個(gè)Collection中的Document會(huì)根據(jù)插入(insert)的先后順序, 連續(xù)地寫入到磁盤的同一個(gè)區(qū)域(region)上。MMAP在第一次插入時(shí)會(huì)為每個(gè)Document開辟一小塊專屬的區(qū)域,你可以管它叫一個(gè)"record"(記錄),或一個(gè)"slot"(record這個(gè)名字容易和別的東西混淆,所以后面我會(huì)管它叫slot), 其他新插入的Document則必須從這一小塊區(qū)域的結(jié)尾處開始寫入。
為了避免 update 時(shí) Document 變大重新分配空間,創(chuàng)建 Document 時(shí)會(huì)預(yù)留一定的空間,稱為 padding
,可以降低重新分配 Document 的幾率。
WiredTiger 是如何實(shí)現(xiàn) Document 級鎖的?
在平常的使用中,大多數(shù)對數(shù)據(jù)庫的更新操作都只會(huì)對某個(gè) Collection 中的少量 Document 進(jìn)行更新。對多個(gè)Collection進(jìn)行同時(shí)更新的情況已是十分稀有,對多個(gè) Database 進(jìn)行同時(shí)更新則是更為罕見了。 由此可見,加鎖粒度最小只支持到 Collection 是遠(yuǎn)遠(yuǎn)不夠的。相對于 MMAPv1,WiredTiger 使用的實(shí)際為 Document 級的樂觀鎖機(jī)制。
WiredTiger的樂觀鎖機(jī)制
與其他樂觀鎖機(jī)制實(shí)現(xiàn)大同小異。WiredTiger會(huì)在更新Document前記錄住即將被更新的所有Document的當(dāng)前版本號
,并在進(jìn)行更新前再次驗(yàn)證其當(dāng)前版本號。 若當(dāng)前版本號沒有發(fā)生改變,則說明該Document在該原子事件中沒有被其他請求所更新,可以順利進(jìn)行寫入,并修改版本號;但如果版本號發(fā)生改變,則說明該Document在更新發(fā)生之前已被其他請求所更新, 由此便觸發(fā)了一次“寫沖突”。不過,在遇到寫沖突以后,WiredTiger也會(huì)自動(dòng)重試更新操作。