mongodb基本概念

  ①.mongodb是属于文档型的非关系型数据库

  ②. mongodb 数据库和关系型数据库概念一致,mongodb集合就是关系型数据库的表,

       mongodb文档就是关系型数据库表的一行数据

  ③.文档中的数据是以BSON(binary json)格式存储的,其格式就是json格式

  ④.mongodb没有固定的行列组织结构,

  ⑤.集合不支持关联查询:

     1).可以使用插入文档数据的时候使用嵌入文档,这样的问题是原本多个关系型数据库表的数据放在一个集合里面

     2).可以使用引用文档,有两种方式: 手动引用和使用DBRef标准。

1.MongoDB集群搭建方式:

   ①Replica set:复制集,mongodb的架构方式之一 ,通常是三个对等的节点构成一个“复制集”集群,有“primary”和secondary等多中角色(稍后详细介绍),其中primary负责读写请求,secondary可以负责读请求,这有配置决定,其中secondary紧跟primary并应用write操作;如果primay失效,则集群进行“多数派”选举,选举出新的primary,即failover机制,即HA架构。复制集解决了单点故障问题,也是mongodb垂直扩展的最小部署单位,当然sharding cluster中每个shard节点也可以使用Replica set提高数据可用性。

  ②Sharding cluster:分片集群,数据水平扩展的手段之一;replica set这种架构的缺点就是“集群数据容量”受限于单个节点的磁盘大小,如果数据量不断增加,对它进行扩容将时非常苦难的事情,所以我们需要采用Sharding模式来解决这个问题。将整个collection的数据将根据sharding key被sharding到多个mongod节点上,即每个节点持有collection的一部分数据,这个集群持有全部数据,原则上sharding可以支撑数TB的数据。

 ③系统配置:

   1)建议mongodb部署在linux系统上,较高版本,选择合适的底层文件系统(ext4),开启合适的swap空间

   2)无论是MMAPV1或者wiredTiger引擎,较大的内存总能带来直接收益。

   3)对数据存储文件关闭“atime”(文件每次access都会更改这个时间值,表示文件最近被访问的时间),可以提升文件访问效率。

   4)ulimit参数调整,这个在基于网络IO或者磁盘IO操作的应用中,通常都会调整,

     上调系统允许打开的文件个数(ulimit -n  65535)。

2. 存储引擎

   ①MMAPV1 :   MongoDB的原始存储引擎,在第一个版本中引入,从4.0版本开始被弃用。

       1) 数据压缩: 因为是基于内存映射文件,所以MMAPV1不支持压缩。如果所有数据都能放入内存那么它将变的非常高效。

                            MMAPV1善于处理大量插入、删除及更新的工作场景。

       2)日志:对于该引擎Journal可确保写入的”原子”性,即当某些修改在提交到数据文件之前MongoDB发生了崩溃,此时

                  MongoDB可以使用journal日志在下次启动的时候应用对应修改操作到数据文件以保持数据一致性。

       3)锁: 从3.0版本开始,MMAPv1存储引擎采用集合级别锁,这也是对早期版本的改进,早期版本中,库锁是最细粒度的锁。

       4)内存:该引擎下MongoDB将会使用机器所有的空闲内存作为自己的缓存。系统资源监控会显示MongoDB使用了大量的

                  内存,但是这样的使用是动态的。假如有一个其他的进程突然想申请机器一半的内存,此时MonoDB将会让出自己

                  的缓存内存给该进程使用。从技术层面讲,MMAPV1引擎下的MongoDB内存由操作系统的虚拟内存子系统来管理。

                 这就意味着MongoDB将使用尽可能多的空闲内存,并根据需要交换到磁盘。故给MongoDB足以覆盖所有热数据的

                  内存容量将会使得MongoDB获得最佳的性能。

 

   ②wiredTiger:  在3.0版本中引入插件式引擎,从3.2版本开始变成了默认的存储引擎。

       1) 数据压缩: 支持snappy和zlib两种压缩模式。因此与MMAP相比,使用WiredTiger的MongoDB占用的磁盘空间要小很多。

                            并且WiredTiger引擎本身有自己的写缓存(可配置)同时也能使用文件系统缓存。

       2) 日志: 对于该引擎journal日志可以确保两个检查点之间所有数据修改的持久化。因此任何数据库的崩溃只需要最后一次

                    检查点后的journal日志即可。大多数情况下对于引擎来说journal日志是不必要的,只有你想确保能恢复到崩溃之前

                    最后一次成功的时候才需要使用journal日志。否则,MongoDB通常可以从最后一个有效checkpoint进行恢复,

                    默认情况下MongoDB每分钟做一次checkpoint。

       3) 锁: 支持文档级别锁,对于大多数读写操作,WiredTiger使用乐观并发控制。即WiredTiger只在全局、

               数据库和集合级别使用意向锁。

       4) 内存: 该引擎会同时使用引擎内部缓存和操作系统缓存资源。对于文件系统缓存,MongoDB将会自动使用WiredTiger

                    缓存外及其他进程不使用的所有空闲内存。

     ③快速对比:

关键特性

MMAPV1引擎

wiredTiger引擎

默认引擎引入

 3.0版本及其之前作为MongoDB默认引擎,4.0 及其以后版本废弃

3.0版本引入,3.2版本起作为默认引擎

数据压缩

不支持

默认采用  snappy 压缩模式同时支持zlib 压缩模式

Journal日志

写操作首先在内存进行修改并将对应操作写入磁盘journal文件。如果修改在数据同步至磁盘之前MongoDB崩溃,重启之后MongoDB可以利用journal日志重新应用对应的写操作到数据文件从而保证数据一致性

journal日志保存两个检查点之间的所有修改,如果MongoDB在下一个检查点之前崩溃,引擎将会使用journal重放最后一个检查点以来的所有数据修改操作

锁和并发控制

Till 2.6,  MongoDB uses a readers-writer [1] lock that allows concurrent reads  access to a database but gives exclusive access to a single write operation.  From 3.0, uses collection level lock

It supports  document level locking.

事务

单个文档操作具备原子性

MongoDB4.0版本支持多文档事务

CPU与性能

增加CPU核数对性能的提升有限

多核系统中提升CPU核数可很大程度提升性能

数据加密

不支持

官方企业版和percona的3.6.8(PSMDB) BETA 版本支持

内存使用

自动使用机器所有空闲内存作为自己的缓存

同时使用引擎内部缓存及文件系统缓存

修改

善于处理具有大量插入、读取和就地更新的工作场景

不支持原地更新故会导致整个文档的重写

调优

可调优的点极少

允许通过不同的参数对引擎进行调优,比如: 内部缓存大小(cache size),读写并发控制(read / write tickets), 检查点触发时间间隔(checkpoint  interval)等

   3.持久化原理

       ①: mongodb在启动时,专门初始化一个线程不断循环(除非应用crash掉),用于在一定时间周期内来从defer队列中获取要持久化的数据并写入到磁盘的journal(日志)和mongofile(数据)处,当然因为它不是在用户添加记录时就写到磁盘上,所以按mongodb开发者说,它不会造成性能上的损耗,因为看过代码发现,当进行CUD操作时,记录(Record类型)都被放入到defer队列中以供延时批量(groupcommit)提交写入,但相信其中时间周期参数是个要认真考量的参数,系统为90毫秒,如果该值更低的话,可能会造成频繁磁盘操作,过高又会造成系统宕机时数据丢失过。

     ②:为了确保数据的安全性,mongodb将所有的变更操作写入journal并间歇性的持久到磁盘上,对于实际数据文件将延迟写入,和wiredTiger一样journal也是用于数据恢复。所有的记录在磁盘上连续存储,当一个document尺寸变大时,mongodb需要重新分配一个新的记录(旧的record标记删除,新的记record在文件尾部重新分配空间),这意味着mongodb同时还需要更新此文档的索引(指向新的record的offset),与in-place update相比,将消耗更多的时间和存储开支。由此可见,如果你的mongodb的使用场景中有大量的这种update,那么或许MMAPv1引擎并不太适合,同时也反映出如果document没有索引,是无法保证document在read中的顺序(即自然顺序)。3.0之后,mongodb默认采用“Power of 2 Sized Allocations”,所以每个document对应的record将有实际数据和一些padding组成,这padding可以允许document的尺寸在update时适度的增长,以最小化重新分配record的可能性。此外重新分配空间,也会导致磁盘碎片(旧的record空间)。

Power of 2 Sized Allocations:默认情况下,MMAPv1中空间分配使用此策略,每个document的size是2的次幂,比如32、64、128、256...2MB,如果文档尺寸大于2MB,则空间为2MB的倍数(2M,4M,6M等)。这种策略有2种优势,首先那些删除或者update变大而产生的磁盘碎片空间(尺寸变大,意味着开辟新空间存储此document,旧的空间被mark为deleted)可以被其他insert重用,再者padding可以允许文档尺寸有限度的增长,而无需每次update变大都重新分配空间。此外,mongodb还提供了一个可选的“No padding Allocation”策略(即按照实际数据尺寸分配空间),如果你确信数据绝大多数情况下都是insert、in-place update,极少的delete,此策略将可以有效的节约磁盘空间,看起来数据更加紧凑,磁盘利用率也更高

4.数据文件存储原理

   ①Data Files :  mongodb的数据将会保存在底层文件系统中

       1) :  每个文件以“database”的名字 + 序列数字组成,序列号从0开始,逐个递增,数据文件从16M开始,每次扩张一倍(16M、32M、64M、128M...),在默认情况下单个data file的最大尺寸为2G,如果设置了smallFiles属性(配置文件中)则最大限定为512M;mongodb中每个database最多支持16000个数据文件,即约32T,如果设置了smallFiles则单个database的最大数据量为8T 

       2): 删除document会导致磁盘碎片,有些update也会导致磁盘碎片,比如update导致文档尺寸变大,进而超过原来分配的空间;当有新的insert操作时,mongodb会检测现有的extents中是否合适的碎片空间可以被重用,如果有,则重用这些fragment,否则分配新的存储空间。磁盘碎片,对write操作有一定的性能影响,而且会导致磁盘空间浪费;如果你需要删除某个collection中大部分数据,则可以考虑将有效数据先转存到新的collection,然后直接drop()原有的collection。或者使用db.runCommand({compact: '<collection>'})。

如果你的database已经运行一段时间,数据已经有很大的磁盘碎片(storageSize与dataSize比较),可以通过mongodump将指定database的所有数据导出,然后将原有的db删除,再通过mongorestore指令将数据重新导入。(同compact,这种操作需要停机维护)

    3):  mongod中还有2个默认的database,系统级的,“admin”和“local”;它们的存储原理同上,其中“admin”用于存储“用户授权信息”,比如每个database中用户的role、权限等;“local”即为本地数据库,我们常说的oplog(replication架构中使用,类似与binlog)即保存在此数据库中。

 ②namespace:

 对于namespace文件,比如“test.ns”文件,默认大小为16M,此文件中主要用于保存“collection”、index的命名信息,比如collection的“属性”信息、每个索引的属性类型等,如果你的database中需要存储大量的collection(比如每一小时生成一个collection,在数据分析应用中),那么我们可以通过配置文件“nsSize”选项来指定。

③journal日志:

   1): journal日志为mongodb提供了数据保障能力,它本质上与mysql binlog没有太大区别,用于当mongodb异常crash后,重启时进行数据恢复;这归结于mongodb的数据持久写入磁盘是滞后的。默认情况下,“journal”特性是开启的,特别在production环境中,我们没有理由来关闭它。(除非,数据丢失对应用而言,是无关紧要的)

  2): 对于write操作而言,首先写入journal日志,然后将数据在内存中修改(mmap),此后后台线程间歇性的将内存中变更的数据flush到底层的data files中,时间间隔为60秒(参见配置项“syncPeriodSecs”);write操作在journal文件中是有序的,为了提升性能,write将会首先写入journal日志的内存buffer中,当buffer数据达到100M或者每隔100毫秒,buffer中的数据将会flush到磁盘中的journal文件中;如果mongodb异常退出,将可能导致最多100M数据或者最近100ms内的数据丢失,flush磁盘的时间间隔有配置项“commitIntervalMs”决定,默认为100毫秒。mongodb之所以不能对每个write都将journal同步磁盘,这也是对性能的考虑,mysql的binlog也采用了类似的权衡方式。开启journal日志功能,将会导致write性能有所降低,可能降低5~30%,因为它直接加剧了磁盘的写入负载,我们可以将journal日志单独放置在其他磁盘驱动器中来提高写入并发能力(与data files分别使用不同的磁盘驱动器)。 

3) 如果你希望数据尽可能的不丢失,可以考虑:

    (1)减小commitIntervalMs的值

    (2)每个write指定“write concern”中指定“j”参数为true

    (3)最佳手段就是采用“replica set”架构模式,通过数据备份方式解决,同时还需要在“write concern”中指定“w”选项,且保障级别不低于“majority”。

5.数据模型

  ① mongodb是一个模式自由的NOSQL,不像其他RDBMS一样需要预先定义Schema而且所有的数据都“整齐划一”,mongodb的document是BSON格式,松散的,原则上说任何一个Colleciton都可以保存任意结构的document,甚至它们的格式千差万别,不过从应用角度考虑,包括业务数据分类和查询优化机制等,我们仍然建议每个colleciton中的document数据结构应该比较接近。

对于有些update,比如对array新增元素等,会导致document尺寸的增加,无论任何存储系统包括MYSQL、Hbase等,对于这种情况都需要额外的考虑,这归结于磁盘空间的分配是连续的(连续意味着读取性能将更高,存储文件空间通常是预分配固定尺寸,我们需要尽可能的利用磁盘IO的这种优势)。对于MMAPV1引擎,如果文档尺寸超过了原分配的空间(上文提到Power of 2 Allocate),mongodb将会重新分配新的空间来保存整个文档(旧文档空间回收,可以被后续的insert重用)。

document模型的设计与存储,需要兼顾应用的实际需要,否则可能会影响性能。mongodb支持内嵌document,即document中一个字段的值也是一个document,可以形成类似于RDBMS中的“one-to-one”、“one-to-many”,只需要对reference作为一个内嵌文档保存即可。这种情况就需要考虑mongodb存储引擎的机制了,如果你的内嵌文档(即reference文档)尺寸是动态的,比如一个user可以有多个card,因为card数量无法预估,这就会导致document的尺寸可能不断增加以至于超过“Power of 2 Allocate”,从而触发空间重新分配,带来性能开销,这种情况下,我们需要将内嵌文档单独保存到一个额外的collection中,作为一个或者多个document存储,比如把card列表保存在card collection中。“one-to-one”的情况也需要个别考虑,如果reference文档尺寸较小,可以内嵌,如果尺寸较大,建议单独存储。此外内嵌文档还有个优点就是write的原子性,如果使用reference的话,就无法保证了。

索引:提高查询性能,默认情况下_id字段会被创建唯一索引;因为索引不仅需要占用大量内存而且也会占用磁盘,所以我们需要建立有限个索引,而且最好不要建立重复索引;每个索引需要8KB的空间,同时update、insert操作会导致索引的调整,会稍微影响write的性能,索引只能使read操作收益,所以读写比高的应用可以考虑建立索引。

数据生命周期管理:mongodb提供了expire机制,即可以指定文档保存的时长,过期后自动删除,即TTL特性,这个特性在很多场合将是非常有用的,比如“验证码保留15分钟有效期”、“消息保存7天”等等,mongodb会启动一个后台线程来删除那些过期的document。需要对一个日期字段创建“TTL索引”,比如插入一个文档:{"check_code":"101010",$currentDate:{"created":true}}},其中created字段默认值为系统时间Date;然后我们对created字段建立TTL索引。

我们向collection中insert文档时,created的时间为系统当前时间,其中在creatd字段上建立了“TTL”索引,索引TTL为15分钟,mongodb后台线程将会扫描并检测每条document的(created时间 + 15分钟)与当前时间比较,如果发现过期,则删除索引条目(连带删除document)。

某些情况下,我们可能需要实现“在某个指定的时刻过期”,我们只需要将上述文档和索引变通改造即可,即created指定为“目标时间”,expiredAfter指定为0。