前言
本文是對(duì) Mongo 官方文檔粗略的總結(jié),并沒(méi)有涉及到很深的細(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ù)留一些填充的額外空間。對(duì)于寫(xiě)入后的 Document 如果還會(huì)更新,可能導(dǎo)致 Document 長(zhǎng)度增加,就可以利用上額外的填充空間來(lái)。若業(yè)務(wù)對(duì)于寫(xiě)入后的 Document 不會(huì)再更新或刪除(像監(jiān)控日志、流水記錄等),可以指定無(wú)填充的 Record 分配策略,更節(jié)省空間。
單個(gè) Document 的容量是否有限制?
16MB。Document 這種 JSON 形態(tài)天生會(huì)帶來(lái)數(shù)據(jù)存儲(chǔ)冗余,主要是 field 屬性每個(gè) Document 都會(huì)保存一遍。目前 3.2 版本的 MongoDB 已經(jīng)將新的 WiredTiger 作為默認(rèn)存儲(chǔ)引擎,它提供了壓縮功能,有兩種壓縮形式:
-
Snappy默認(rèn)壓縮算法,在壓縮率和 CPU 開(kāi)銷(xiāo)之間取得平衡。 -
Zlib更高的壓縮率,但也帶來(lái)更高的 CPU 開(kāi)銷(xiāo)。
而每個(gè) Document 依然有最大容量限制,不能無(wú)限增長(zhǎng)下去,這個(gè)限制目前是 16MB。那么我要存大于 16MB 的文件怎么辦,MongoDB 提供了 GridFS 來(lái)存儲(chǔ)超過(guò) 16MB 大小的文件。如下圖所示,一個(gè)大文件被拆分成小的 File Chunk,每個(gè) Chunk 大小 255KB,并存放在一個(gè) Document 中。GridFS 使用了 2 個(gè) Collection 來(lái)分別存放文件 Chunk 和文件元數(shù)據(jù)。
遇到真正的「大數(shù)據(jù)」(單機(jī)存儲(chǔ)容量不夠)怎么辦?
分片化:利用更多的機(jī)器來(lái)提供更大的容量,分片集群采用代理模式:
而每個(gè)分片上的數(shù)據(jù)又以 Chunk 的形式組織(類(lèi)似于 Redis Cluster 的 Slot 概念),以便于集群內(nèi)部的數(shù)據(jù)遷移和再平衡。比較容易混淆的是這里的 Chunk 不是前面 GridFS 里提到的 Chunk,它們的關(guān)系大概如下圖:
Mongo 的數(shù)據(jù)安全嗎?在保證效率的同時(shí),在服務(wù)器突然宕機(jī)的情況下,是否能夠保存數(shù)據(jù)?
安全和效率其實(shí)是相互制約的,越安全則效率越低,越高效則越不安全。MongoDB 的設(shè)計(jì)場(chǎng)景考慮的是應(yīng)對(duì)大量的數(shù)據(jù)寫(xiě)入和查詢(xún),而數(shù)據(jù)的重要性相對(duì)沒(méi)那么高。所以 MongoDB 的默認(rèn)設(shè)置在安全和效率之間,更偏向效率。
Write To Buffer Without ACK
這個(gè)模式下 MongoDB 是不確認(rèn)寫(xiě)請(qǐng)求的,Client 端調(diào)用驅(qū)動(dòng)寫(xiě)入后若沒(méi)有網(wǎng)絡(luò)錯(cuò)誤就認(rèn)為成功,實(shí)際到底寫(xiě)入成功沒(méi)有是不確定的。即使網(wǎng)絡(luò)沒(méi)有問(wèn)題,數(shù)據(jù)到達(dá) MongoDB 后它先保存在內(nèi)存 Buffer 中,再異步寫(xiě)入 Journaling 日志,這中間有 100ms(默認(rèn)值) 的落盤(pán)(寫(xiě)入磁盤(pán))時(shí)間窗口。一般數(shù)據(jù)庫(kù)的設(shè)計(jì)都是先寫(xiě) Journaling 的流水日志,隨后異步再寫(xiě)真正的數(shù)據(jù)文件到磁盤(pán),這個(gè)可能就比較長(zhǎng)了,MongoDB 是 60 秒或者 Journaling 日志達(dá)到 2G。
Write To Buffer With ACK
這個(gè)比上一種模式稍微好一點(diǎn),MongoDB 收到寫(xiě)入請(qǐng)求,先寫(xiě)入內(nèi)存 Buffer 后回發(fā) Ack 確認(rèn)。Client 端能確保 MongoDB 收到了寫(xiě)入數(shù)據(jù),但依然有短暫的 Journaling 日志落盤(pán)時(shí)差導(dǎo)致潛在的數(shù)據(jù)丟失可能。
Write To Journaling With ACK
這個(gè)模式確保至少寫(xiě)入 Journaling 日志后才回發(fā) Ack 確認(rèn),Client 端能確保數(shù)據(jù)至少寫(xiě)入磁盤(pán)了,安全性較高。
Write To Replica Buffer With ACK
這個(gè)模式是針對(duì)多副本集的,為了提升數(shù)據(jù)安全性,除了及時(shí)寫(xiě)入磁盤(pán)也可以通過(guò)寫(xiě)多個(gè)副本來(lái)提升。在這個(gè)模式下,數(shù)據(jù)至少寫(xiě)入 2 個(gè)副本的內(nèi)存 Buffer 中才回發(fā) Ack 確認(rèn)。雖然都在內(nèi)存 Buffer 中,但兩個(gè)實(shí)例在落盤(pán)短暫的 100ms 時(shí)差中同時(shí)故障的概率很低,所以安全性有所提升。
MMAPv1 和 WiredTiger 有什么區(qū)別?
- MMAPv1 是 Mongo 在 3.0 以前的存儲(chǔ)引擎,WiredTiger 是 Mongo 在 3.2 及以后版本的默認(rèn)存儲(chǔ)引擎;
- MMAPv1 只是單純地將 BSON 數(shù)據(jù)直接存儲(chǔ)在磁盤(pán)上,WiredTiger 則會(huì)在數(shù)據(jù)從內(nèi)存存儲(chǔ)到磁盤(pán)前進(jìn)行一次
壓縮; - MMAPv1 在 3.0 版本之前,以 database 為單位加鎖,對(duì)同一個(gè)Database的其他Collection所做的操作也會(huì)被阻塞。 而到了 3.0 版本,MMAPv1 則開(kāi)始使用以 Collection 為單位的加鎖。WiredTiger 是基于
Document 級(jí)鎖機(jī)制。
MMAPv1 是如何分配記錄的?
在MongoDB中,每條數(shù)據(jù)以 Document 的形式進(jìn)行存儲(chǔ),并通過(guò) Collection 來(lái)管理Document。同一個(gè)Collection中的Document會(huì)根據(jù)插入(insert)的先后順序, 連續(xù)地寫(xiě)入到磁盤(pán)的同一個(gè)區(qū)域(region)上。MMAP在第一次插入時(shí)會(huì)為每個(gè)Document開(kāi)辟一小塊專(zhuān)屬的區(qū)域,你可以管它叫一個(gè)"record"(記錄),或一個(gè)"slot"(record這個(gè)名字容易和別的東西混淆,所以后面我會(huì)管它叫slot), 其他新插入的Document則必須從這一小塊區(qū)域的結(jié)尾處開(kāi)始寫(xiě)入。
為了避免 update 時(shí) Document 變大重新分配空間,創(chuàng)建 Document 時(shí)會(huì)預(yù)留一定的空間,稱(chēng)為 padding,可以降低重新分配 Document 的幾率。
WiredTiger 是如何實(shí)現(xiàn) Document 級(jí)鎖的?
在平常的使用中,大多數(shù)對(duì)數(shù)據(jù)庫(kù)的更新操作都只會(huì)對(duì)某個(gè) Collection 中的少量 Document 進(jìn)行更新。對(duì)多個(gè)Collection進(jìn)行同時(shí)更新的情況已是十分稀有,對(duì)多個(gè) Database 進(jìn)行同時(shí)更新則是更為罕見(jiàn)了。 由此可見(jiàn),加鎖粒度最小只支持到 Collection 是遠(yuǎn)遠(yuǎn)不夠的。相對(duì)于 MMAPv1,WiredTiger 使用的實(shí)際為 Document 級(jí)的樂(lè)觀鎖機(jī)制。
WiredTiger的樂(lè)觀鎖機(jī)制與其他樂(lè)觀鎖機(jī)制實(shí)現(xiàn)大同小異。WiredTiger會(huì)在更新Document前記錄住即將被更新的所有Document的當(dāng)前版本號(hào),并在進(jìn)行更新前再次驗(yàn)證其當(dāng)前版本號(hào)。 若當(dāng)前版本號(hào)沒(méi)有發(fā)生改變,則說(shuō)明該Document在該原子事件中沒(méi)有被其他請(qǐng)求所更新,可以順利進(jìn)行寫(xiě)入,并修改版本號(hào);但如果版本號(hào)發(fā)生改變,則說(shuō)明該Document在更新發(fā)生之前已被其他請(qǐng)求所更新, 由此便觸發(fā)了一次“寫(xiě)沖突”。不過(guò),在遇到寫(xiě)沖突以后,WiredTiger也會(huì)自動(dòng)重試更新操作。