前言
本文是对 Mongo 官方文档粗略的总结,并没有涉及到很深的细节(细节还是直接看官方文档吧)。我认为 Mongo 有重要的就 3 点:
- 存储引擎原理,如何保证断电后恢复数据?Mongo 的 data 在文件系统中,是如何组织和保存的?
- Replication
- Sharding
思维导图
目录
Basic
Aggregation & Data Modeling
Indexes
Storage
Replication & Sharding
思考
Document 在内部是如何存储的?
每个 Document 被保存在一个 Record
中。Record 相当于 MongoDB 内部分配的一块空间,除了保存 Document 的内容可能还会预留一些填充的额外空间
。对于写入后的 Document 如果还会更新,可能导致 Document 长度增加,就可以利用上额外的填充空间来。若业务对于写入后的 Document 不会再更新或删除(像监控日志、流水记录等),可以指定无填充的 Record 分配策略,更节省空间。
单个 Document 的容量是否有限制?
16MB
。Document 这种 JSON 形态天生会带来数据存储冗余,主要是 field 属性每个 Document 都会保存一遍。目前 3.2 版本的 MongoDB 已经将新的 WiredTiger 作为默认存储引擎,它提供了压缩功能,有两种压缩形式:
-
Snappy
默认压缩算法,在压缩率和 CPU 开销之间取得平衡。 -
Zlib
更高的压缩率,但也带来更高的 CPU 开销。
而每个 Document 依然有最大容量限制,不能无限增长下去,这个限制目前是 16MB。那么我要存大于 16MB 的文件怎么办,MongoDB 提供了 GridFS 来存储超过 16MB 大小的文件。如下图所示,一个大文件被拆分成小的 File Chunk,每个 Chunk 大小 255KB,并存放在一个 Document 中。GridFS 使用了 2 个 Collection 来分别存放文件 Chunk 和文件元数据。
遇到真正的「大数据」(单机存储容量不够)怎么办?
分片化
:利用更多的机器来提供更大的容量,分片集群采用代理模式:
而每个分片上的数据又以 Chunk
的形式组织(类似于 Redis Cluster 的 Slot 概念),以便于集群内部的数据迁移和再平衡。比较容易混淆的是这里的 Chunk 不是前面 GridFS 里提到的 Chunk,它们的关系大概如下图:
Mongo 的数据安全吗?在保证效率的同时,在服务器突然宕机的情况下,是否能够保存数据?
安全
和效率
其实是相互制约的,越安全则效率越低,越高效则越不安全。MongoDB 的设计场景考虑的是应对大量的数据写入和查询,而数据的重要性相对没那么高。所以 MongoDB 的默认设置在安全和效率之间,更偏向效率。
Write To Buffer Without ACK
这个模式下 MongoDB 是不确认写请求的,Client 端调用驱动写入后若没有网络错误就认为成功,实际到底写入成功没有是不确定的。即使网络没有问题,数据到达 MongoDB 后它先保存在内存 Buffer 中,再异步写入 Journaling 日志,这中间有 100ms(默认值) 的落盘(写入磁盘)时间窗口。一般数据库的设计都是先写 Journaling 的流水日志,随后异步再写真正的数据文件到磁盘,这个可能就比较长了,MongoDB 是 60 秒或者 Journaling 日志达到 2G。
Write To Buffer With ACK
这个比上一种模式稍微好一点,MongoDB 收到写入请求,先写入内存 Buffer 后回发 Ack 确认。Client 端能确保 MongoDB 收到了写入数据,但依然有短暂的 Journaling 日志落盘时差导致潜在的数据丢失可能。
Write To Journaling With ACK
这个模式确保至少写入 Journaling 日志
后才回发 Ack 确认,Client 端能确保数据至少写入磁盘了,安全性较高。
Write To Replica Buffer With ACK
这个模式是针对多副本集的,为了提升数据安全性,除了及时写入磁盘也可以通过写多个副本来提升。在这个模式下,数据至少写入 2 个副本的内存 Buffer 中才回发 Ack 确认。虽然都在内存 Buffer 中,但两个实例在落盘短暂的 100ms 时差中同时故障的概率很低,所以安全性有所提升。
MMAPv1 和 WiredTiger 有什么区别?
- MMAPv1 是 Mongo 在 3.0 以前的存储引擎,WiredTiger 是 Mongo 在 3.2 及以后版本的默认存储引擎;
- MMAPv1 只是单纯地将 BSON 数据直接存储在磁盘上,WiredTiger 则会在数据从内存存储到磁盘前进行一次
压缩
; - MMAPv1 在 3.0 版本之前,以 database 为单位加锁,对同一个Database的其他Collection所做的操作也会被阻塞。 而到了 3.0 版本,MMAPv1 则开始使用以 Collection 为单位的加锁。WiredTiger 是基于
Document 级锁
机制。
MMAPv1 是如何分配记录的?
在MongoDB中,每条数据以 Document
的形式进行存储,并通过 Collection
来管理Document。同一个Collection中的Document会根据插入(insert)的先后顺序, 连续地写入到磁盘的同一个区域(region)上。MMAP在第一次插入时会为每个Document开辟一小块专属的区域,你可以管它叫一个"record"(记录),或一个"slot"(record这个名字容易和别的东西混淆,所以后面我会管它叫slot), 其他新插入的Document则必须从这一小块区域的结尾处开始写入。
为了避免 update 时 Document 变大重新分配空间,创建 Document 时会预留一定的空间,称为 padding
,可以降低重新分配 Document 的几率。
WiredTiger 是如何实现 Document 级锁的?
在平常的使用中,大多数对数据库的更新操作都只会对某个 Collection 中的少量 Document 进行更新。对多个Collection进行同时更新的情况已是十分稀有,对多个 Database 进行同时更新则是更为罕见了。 由此可见,加锁粒度最小只支持到 Collection 是远远不够的。相对于 MMAPv1,WiredTiger 使用的实际为 Document 级的乐观锁机制。
WiredTiger的乐观锁机制
与其他乐观锁机制实现大同小异。WiredTiger会在更新Document前记录住即将被更新的所有Document的当前版本号
,并在进行更新前再次验证其当前版本号。 若当前版本号没有发生改变,则说明该Document在该原子事件中没有被其他请求所更新,可以顺利进行写入,并修改版本号;但如果版本号发生改变,则说明该Document在更新发生之前已被其他请求所更新, 由此便触发了一次“写冲突”。不过,在遇到写冲突以后,WiredTiger也会自动重试更新操作。