优质博文:IT-BLOG-CN
一、存储数据格式
MongoDB
作为主流的NoSQL
数据库之一,使用面向文档的数据存储方式,将数据以JSON
和BSON
的方式存储在磁盘中。BSON Binary JSON
是一种基于JSON
的二级制序列化格式,用于MongoDB
存储文档并进行远程过程调用,作为网络数据交互的一种存储形式,类似于Protocol Buffer
和Thrift
。BSON
是一种schema-less
的存储形式,它的优点是灵活性高,但它的缺点是空间利用率不是很理想
一个Collection
包含一个JSON
和BSON
文档:
JSON
和BSON
之间最主要的区别如下表所示:
JSON |
BSON |
---|---|
JSON 是javascript 对象表示法 |
BSON 是二进制JSON |
是一种轻量级的、基于文本的、开放的数据交换格式 | 是一种二进制序列化文档格式 |
JSON 包含一些基本数据类型,如字符串、数字、布尔值、空值 |
除了支持JSON 中的类型外,BSON 还包含一些额外的数据类型,例如日期Date 、二进制BinData 等 |
AnyDB 、Redis 等数据库将数据存储为JSON 格式 |
MongoDB 中将数据存储为BSON 格式 |
主要用于传输数据 | 主要用于存储数据 |
没有响应的编码和解码技术 | 有专用的编码和解码技术 |
如果想从JSON 文件中读取指定信息,需要遍历整个数据 |
在BSON 中,可以使用索引跳过到指定内容 |
JSON 格式不需要解析,因为它是人类可读的 |
BSON 需要解析,因为它是二进制的 |
JSON 是对象和数组的组合,其中对象是键值对的集合,而数组是元素的有序列表 |
BSON 是二进制数据,在其中可以存储一些附加信息,例如字符串长度、对象类型等 |
二、架构视图
MongoDB
与MySQL
中的架构相似,底层都使用了可插拔的存储引擎以满足用户的不同需要。用户可以根据程序的数据特征选择不同的存储引擎,在最新版本的MongoDB
中使用了WiredTiger
作为默认的存储引擎,WiredTiger
提供了不同粒度的并发控制和压缩机制,能够为不同种类的应用提供了最好的性能和存储率。
在存储引擎上层的就是MongoDB
的数据模型和查询语言了,由于MongoDB
对数据的存储与RDBMS:Relational Database Management System
有较大的差异,所以它创建了一套不同的数据模型和查询语言。虽然MongoDB
查询语言非常强大,支持的功能也很多,同时也是可编程的,不过其中包含的内容非常繁杂、API
设计也不是非常优雅,所以还是需要一些学习成本的,对于长时间使用MySQL
的开发者肯定会有些不习惯。
数据模型Data Model
:
【1】内嵌: 内嵌的方式指的是把相关联的数据保存在同一个文档结构之中。MongoDB
的文档结构允许一个字段或者一个数组内的值作为一个嵌套的文档。通常如下场景选择内嵌:
■ 数据对象之间有包含关系,一般是数据对象之间有一对多或者一对一的关系。
■ 需要经常一起读取的数据。
■ 有map-reduce/aggregation
需求的数据放在一起,这些操作都只能操作单个collection
。
【2】引用: 引用方式通过存储数据引用信息来实现两个不同文档之间的关联,应用程序可以通过解析这些数据引用来访问相关数据。通常如下场景选择内嵌引用:
■ 当内嵌数据会导致很多数据的重复,并且读性能的优势又不足于覆盖数据重复的弊端。
■ 需要表达比较复杂的多对多关系的时候。
■ 大型层次结果数据集嵌套不要太深。
三、存储引擎
存储引擎是MongoDB
的核心组件,负责管理数据如何存储在硬盘和内存上。MongoDB
支持的存储引擎有:MMAPv1
、WiredTiger
、InMemory
。
InMemory
存储引擎用于将数据只存储在内存中,只将少量的元数据meta-data
和诊断日志Diagnostic
存储到硬盘文件中,由于不需要Disk
的IO
操作,就能获取所需的数据,InMemory
存储引擎大幅度降低了数据查询的延迟Latency
。从mongodb3.2
开始默认的存储引擎是WiredTiger
,3.2
版本之前的默认存储引擎是MMAPv1
,mongodb4.x
版本不再支持MMAPv1
存储引擎。
storage:
journal:
enabled: true
dbPath: /data/mongo/
#是否一个库一个文件夹
directoryPerDB: true
##数据引擎
engine: wiredTiger
##WT引擎配置
WiredTiger:
engineConfig:
##WT最大使用cache(根据服务器实际情况调节)
cacheSizeGB: 2
##是否将索引也按数据库名单独存储
directoryForIndexes: true
journalCompressor:none (默认snappy)
##表压缩配置
collectionConfig:
blockCompressor: zlib (默认snappy,还可选none、zlib)
##索引配置
indexConfig:
prefixCompression: true
WiredTiger优势
【1】文档空间分配方式: WiredTiger
使用的是BTree
存储; MMAPV1
线性存储需要Padding
;
【2】并发级别:WiredTiger
文档级别锁; MMAPV1
引擎使用表级锁;
【3】数据压缩:snappy
默认和zlib
,相比MMAPV1
无压缩空间节省数倍;
【4】内存使用:WiredTiger
可以指定内存的使用大小,从MongoDB 3.2
版本开始,WiredTiger
内部缓存的使用量,默认值是:1GB
或60%of RAM - 1GB
,取两值中的较大值(不同版本会有区别,具体参考版本配置文件说明);文件系统缓存的使用量不固定, MongoDB
自动使用系统空闲的内存;
【5】Cache
使用:WT
引擎使用了二阶缓存WiredTiger Cache
,File System Cache
来保证Disk
上的数据的最终一致性。而MMAPv1
只有journal
日志;
【6】文档级别的锁: MongoDB
在执行写操作时, WiredTiger
在文档级别进行并发控制,就是说,在同一时间,多个写操作能够修改同一个集合中的不同文档;当多个写操作修改同一个文档时,必须以序列化方式执行;这意味着,如果该文档正在被修改,其他写操作必须等待,直到在该文档上的写操作完成之后,其他写操作相互竞争,获胜的写操作在该文档上执行修改操作;
【7】检查点机制: 类似关系数据库的CheckPoint
,在Checkpoint
操作开始时, WiredTiger
提供指定时间点point-in-time
的数据库快照Snapshot
,该Snapshot
呈现的是内存中数据的一致性视图。当向Disk
写入数据时,WiredTiger
将Snapshot
中的所有数据以一致性方式写入到数据文件中。同样MongoDB
借助Journal
日志文件也可以还原数据;
【1】WiredTiger.basecfg
: 存储基本配置信息,与ConfifigServer
有关系;
【2】WiredTiger.lock
: 定义锁操作;
【3】table*.wt
: 存储各张表的数据;
【4】WiredTiger.wt
: 存储table*
的元数据;
【5】WiredTiger.turtle
: 存储WiredTiger.wt
的元数据;
【6】journal
: 存储WAL(Write Ahead Log)
;
WiredTiger存储引擎实现原理
Transport Layer
是处理请求的基本单位。Mongo
有专门的listener
线程,每次有连接进来,listener
会创建一个新的线程conn
负责与客户端交互,它把具体的查询请求交给network
线程,真正到数据库里查询由TaskExecutor
来进行。
写请求:WiredTiger
的写操作会默认写入Cache
,并持久化到WAL (Write Ahead Log)
,每60s
或Log
文件达到2G
做一次checkpoint
(当然我们也可以通过在写入时传入j: true
的参数强制journal
文件的同步,writeConcern: { w: , j: , wtimeout: })
产生快照文件。WiredTiger
初始化时,恢复至最新的快照状态,然后再根据WAL
恢复数据,保证数据的完整性。
Cache
是基于BTree
的,节点是一个page
,rootpage
是根节点,internal page
是中间索引节点,leaf page
真正存储数据,数据以page
为单位读写。
WiredTiger
采用Copy on write
的方式管理写操作insert、update、delete
,写操作会先缓存在cache
里,持久化时,写操作不会在原来的leaf page
上进行,而是写入新分配的page
,每次checkpoint
都会产生一个新的rootpage
。
checkpoint
流程:
【1】对所有的table
进行一次checkpoint
,每个table
的checkpoint
的元数据更新至WiredTiger.wt
【2】对WiredTiger.wt
进行checkpoint
,将该tablecheckpoint
的元数据更新至临时文件WiredTiger.turtle.set
【3】将WiredTiger.turtle.set
重命名为WiredTiger.turtle
【4】上述过程如果中间失败,WiredTiger
在下次连接初始化时,首先将数据恢复至最新的快照状态,然后根据WAL
恢复数据,以保证存储可靠性
Journaling:
在数据库宕机时,为保证MongoDB
中数据的持久性,MongoDB
使用了Write Ahead Logging
向磁盘上的journal
文件预先进行写入。除了journal
日志,MongoDB
还使用检查点checkpoint
来保证数据的一致性,当数据库发生宕机时,我们就需要checkpoint
和journal
文件协作完成数据的恢复工作。
【1】在数据文件中查找上一个检查点的标识符;
【2】在journal
文件中查找标识符对应的记录;
【3】重做对应记录之后的全部操作;
丢数据的情况: 写入数据时,引擎内部是先将数据存在内存中,每隔60s
或内存存储容量达到2G
后提交一次到磁盘中,因此在这60s
期间如果机器宕机,则有极大的可能性会丢失数据
不丢数据的情况: 写入数据时,引擎内部是先将数据存在内存中,同时也会写一份操作日志到内存中,该日志会每个100ms
持续化到磁盘文件,这种日志成为Journaling
。Journaling
类似于关系数据库中的事务日志。Journaling
能够使MongoDB
数据库由于意外故障后快速恢复。MongoDB2.4
版本后默认开启了Journaling
日志功能,mongod
实例每次启动时都会检查journal
日志文件看是否需要恢复。由于提交journal
日志会产生写入阻塞,所以它对写入的操作有性能影响,但对于读没有影响。
四、写入策略
MongoDB
的写入策略有多种方式,写入策略是指当客户端发起写入请求后,数据库什么时候给应答,MongoDB
有三种处理策略:客户端发出去的时候,服务器收到请求的时候,服务器写入磁盘的时候。
【1】Unacknowledged
:客户端发出请求丢到socket
的时候就收到响应,这个时候客户端不需要等服务器的应答,但是的本地的驱动还是尽可能的通知客户端网络的异常,这和客户端操作系统的配置有关。
【2】Acknowledged
:这种方式客户端发送接口会等待服务器给的确认,这种方式一定能确保服务器收到了客户端的请求,并且当服务器能够异常时,响应客户端。
【3】Journaled
:Journaled
方式相比Acknowledged
的方式是要保证服务器端已经写入到硬盘文件了。对于Acknowledged
的方式有可能服务收到请求数据相应客户端后的一瞬间当机了,这个数据就丢失了,但是对于Journaled
方式,服务器保证写入到磁盘后再相应客户端,即使当机了,也不会导致数据丢失。
【4】Replica Acknowledged
:这个方式和Acknowledged
是一样的意思,适用于Replica sets
模式。Acknowledged
模式下只有一台机器收到了请求就返回了,对于复制集模式有多台机器的情况,可以要求有多台机器收到写入请求后再相应客户端。这种更安全,但是导致了客户端耗时增加,所以要结合自己的场景设置合适的策略。
可以通过下面的方式设置默认的策略,majority
表示多数节点写入成功后才相应客户端,也可以替换成具体的数子,比如w:2
表示至少写入2
个节点才返回。wtimeout
表示超时时间,还有一个参加j
可以设置true
,false
表示是否是写入日志才返回。
cfg = rs.conf()
cfg.settings.getLastErrorDefaults = { w: "majority", wtimeout: 5000 }
rs.reconfig(cfg)
也可以通过客户端来指定具体的策略,如下: 至少要写入两个节点,超时时间是5s
db.products.insert(
{ item: "envelopes", qty : 100, type: "Clasp" },
{ writeConcern: { w: 2, wtimeout: 5000 } }
)
如果复制集是3
台机器,写入两台机器,流程如下: