前言
本文是對 Mongo 官方文檔粗略的總結(jié)沛简,并沒有涉及到很深的細(xì)節(jié)(細(xì)節(jié)還是直接看官方文檔吧)扰藕。我認(rèn)為 Mongo 有重要的就 3 點(diǎn):
- 存儲引擎原理问顷,如何保證斷電后恢復(fù)數(shù)據(jù)九府?Mongo 的 data 在文件系統(tǒng)中稻励,是如何組織和保存的父阻?
- Replication
- Sharding
思維導(dǎo)圖
目錄
Basic
Aggregation & Data Modeling
Indexes
Storage
Replication & Sharding
思考
Document 在內(nèi)部是如何存儲的?
每個 Document 被保存在一個 Record
中望抽。Record 相當(dāng)于 MongoDB 內(nèi)部分配的一塊空間加矛,除了保存 Document 的內(nèi)容可能還會預(yù)留一些填充的額外空間
。對于寫入后的 Document 如果還會更新煤篙,可能導(dǎo)致 Document 長度增加斟览,就可以利用上額外的填充空間來。若業(yè)務(wù)對于寫入后的 Document 不會再更新或刪除(像監(jiān)控日志辑奈、流水記錄等)苛茂,可以指定無填充的 Record 分配策略,更節(jié)省空間鸠窗。
單個 Document 的容量是否有限制妓羊?
16MB
。Document 這種 JSON 形態(tài)天生會帶來數(shù)據(jù)存儲冗余稍计,主要是 field 屬性每個 Document 都會保存一遍躁绸。目前 3.2 版本的 MongoDB 已經(jīng)將新的 WiredTiger 作為默認(rèn)存儲引擎,它提供了壓縮功能臣嚣,有兩種壓縮形式:
-
Snappy
默認(rèn)壓縮算法净刮,在壓縮率和 CPU 開銷之間取得平衡。 -
Zlib
更高的壓縮率硅则,但也帶來更高的 CPU 開銷淹父。
而每個 Document 依然有最大容量限制,不能無限增長下去怎虫,這個限制目前是 16MB弹灭。那么我要存大于 16MB 的文件怎么辦督暂,MongoDB 提供了 GridFS 來存儲超過 16MB 大小的文件。如下圖所示穷吮,一個大文件被拆分成小的 File Chunk,每個 Chunk 大小 255KB饥努,并存放在一個 Document 中捡鱼。GridFS 使用了 2 個 Collection 來分別存放文件 Chunk 和文件元數(shù)據(jù)。
遇到真正的「大數(shù)據(jù)」(單機(jī)存儲容量不夠)怎么辦酷愧?
分片化
:利用更多的機(jī)器來提供更大的容量驾诈,分片集群采用代理模式:
而每個分片上的數(shù)據(jù)又以 Chunk
的形式組織(類似于 Redis Cluster 的 Slot 概念),以便于集群內(nèi)部的數(shù)據(jù)遷移和再平衡溶浴。比較容易混淆的是這里的 Chunk 不是前面 GridFS 里提到的 Chunk乍迄,它們的關(guān)系大概如下圖:
Mongo 的數(shù)據(jù)安全嗎?在保證效率的同時士败,在服務(wù)器突然宕機(jī)的情況下闯两,是否能夠保存數(shù)據(jù)?
安全
和效率
其實(shí)是相互制約的谅将,越安全則效率越低漾狼,越高效則越不安全。MongoDB 的設(shè)計(jì)場景考慮的是應(yīng)對大量的數(shù)據(jù)寫入和查詢饥臂,而數(shù)據(jù)的重要性相對沒那么高逊躁。所以 MongoDB 的默認(rèn)設(shè)置在安全和效率之間,更偏向效率隅熙。
Write To Buffer Without ACK
這個模式下 MongoDB 是不確認(rèn)寫請求的稽煤,Client 端調(diào)用驅(qū)動寫入后若沒有網(wǎng)絡(luò)錯誤就認(rèn)為成功,實(shí)際到底寫入成功沒有是不確定的囚戚。即使網(wǎng)絡(luò)沒有問題酵熙,數(shù)據(jù)到達(dá) MongoDB 后它先保存在內(nèi)存 Buffer 中,再異步寫入 Journaling 日志弯淘,這中間有 100ms(默認(rèn)值) 的落盤(寫入磁盤)時間窗口绿店。一般數(shù)據(jù)庫的設(shè)計(jì)都是先寫 Journaling 的流水日志,隨后異步再寫真正的數(shù)據(jù)文件到磁盤庐橙,這個可能就比較長了假勿,MongoDB 是 60 秒或者 Journaling 日志達(dá)到 2G。
Write To Buffer With ACK
這個比上一種模式稍微好一點(diǎn)态鳖,MongoDB 收到寫入請求转培,先寫入內(nèi)存 Buffer 后回發(fā) Ack 確認(rèn)。Client 端能確保 MongoDB 收到了寫入數(shù)據(jù)浆竭,但依然有短暫的 Journaling 日志落盤時差導(dǎo)致潛在的數(shù)據(jù)丟失可能浸须。
Write To Journaling With ACK
這個模式確保至少寫入 Journaling 日志
后才回發(fā) Ack 確認(rèn)惨寿,Client 端能確保數(shù)據(jù)至少寫入磁盤了,安全性較高删窒。
Write To Replica Buffer With ACK
這個模式是針對多副本集的裂垦,為了提升數(shù)據(jù)安全性,除了及時寫入磁盤也可以通過寫多個副本來提升肌索。在這個模式下蕉拢,數(shù)據(jù)至少寫入 2 個副本的內(nèi)存 Buffer 中才回發(fā) Ack 確認(rèn)。雖然都在內(nèi)存 Buffer 中诚亚,但兩個實(shí)例在落盤短暫的 100ms 時差中同時故障的概率很低晕换,所以安全性有所提升。
MMAPv1 和 WiredTiger 有什么區(qū)別站宗?
- MMAPv1 是 Mongo 在 3.0 以前的存儲引擎闸准,WiredTiger 是 Mongo 在 3.2 及以后版本的默認(rèn)存儲引擎;
- MMAPv1 只是單純地將 BSON 數(shù)據(jù)直接存儲在磁盤上梢灭,WiredTiger 則會在數(shù)據(jù)從內(nèi)存存儲到磁盤前進(jìn)行一次
壓縮
夷家; - MMAPv1 在 3.0 版本之前,以 database 為單位加鎖或辖,對同一個Database的其他Collection所做的操作也會被阻塞瘾英。 而到了 3.0 版本,MMAPv1 則開始使用以 Collection 為單位的加鎖颂暇。WiredTiger 是基于
Document 級鎖
機(jī)制缺谴。
MMAPv1 是如何分配記錄的?
在MongoDB中耳鸯,每條數(shù)據(jù)以 Document
的形式進(jìn)行存儲湿蛔,并通過 Collection
來管理Document。同一個Collection中的Document會根據(jù)插入(insert)的先后順序县爬, 連續(xù)地寫入到磁盤的同一個區(qū)域(region)上阳啥。MMAP在第一次插入時會為每個Document開辟一小塊專屬的區(qū)域,你可以管它叫一個"record"(記錄)财喳,或一個"slot"(record這個名字容易和別的東西混淆察迟,所以后面我會管它叫slot), 其他新插入的Document則必須從這一小塊區(qū)域的結(jié)尾處開始寫入耳高。
為了避免 update 時 Document 變大重新分配空間扎瓶,創(chuàng)建 Document 時會預(yù)留一定的空間,稱為 padding
泌枪,可以降低重新分配 Document 的幾率概荷。
WiredTiger 是如何實(shí)現(xiàn) Document 級鎖的?
在平常的使用中碌燕,大多數(shù)對數(shù)據(jù)庫的更新操作都只會對某個 Collection 中的少量 Document 進(jìn)行更新误证。對多個Collection進(jìn)行同時更新的情況已是十分稀有继薛,對多個 Database 進(jìn)行同時更新則是更為罕見了。 由此可見愈捅,加鎖粒度最小只支持到 Collection 是遠(yuǎn)遠(yuǎn)不夠的遏考。相對于 MMAPv1,WiredTiger 使用的實(shí)際為 Document 級的樂觀鎖機(jī)制蓝谨。
WiredTiger的樂觀鎖機(jī)制
與其他樂觀鎖機(jī)制實(shí)現(xiàn)大同小異诈皿。WiredTiger會在更新Document前記錄住即將被更新的所有Document的當(dāng)前版本號
,并在進(jìn)行更新前再次驗(yàn)證其當(dāng)前版本號像棘。 若當(dāng)前版本號沒有發(fā)生改變,則說明該Document在該原子事件中沒有被其他請求所更新壶冒,可以順利進(jìn)行寫入缕题,并修改版本號;但如果版本號發(fā)生改變胖腾,則說明該Document在更新發(fā)生之前已被其他請求所更新烟零, 由此便觸發(fā)了一次“寫沖突”。不過咸作,在遇到寫沖突以后锨阿,WiredTiger也會自動重試更新操作。