转自(http://blog.csdn.net/lchjustc/article/details/16988251)
Mongodb调研
1. 调研目的
现在公司缺乏一个通用的key-value存储系统。快盘的底层存储系统适合用来存储文件,文件的元数据现在是存储在mysql中的。为了应对元数据规模 的不断扩大,在元数据上增加引用计数,以及应对未来可能的元数据格式变化,都需要一个更加通用的key/value存储系统来存储这些元数据。
调研Mongodb是否适合用来作为存储ufa/block元数据信息的key/value存储系统。
调研Mongodb系统的集群架构,使用方法,内部实现原理,数据冗余,数据分片等。
调研Mongodb在大数据规模下是否能够保证稳定的操作性能。
2. 调研内容
2.1 Mongodb简介
Mongodb是一个开源的,文档存储的内存数据库。由10gen公司负责开发,维护和进行商业支持。
ü 数据存储格式
和传统数据库不同,Mongodb存储的数据类型是BSON格式,可以看做是json数据格式的二进制表示。
Mongodb将数据按照数据库(database),集合(collections)和文档(document)的方式进行存储,分别对应于 RDBMS中的数据库,表和记录的概念。Document是BSON格式(类似于json的二进制存储格式),每个document都是自描述的。
Mongodb对数据格式要求宽松,宣称是Schema Less。collection仅仅是一个文档的集合,不要求collection中的所有文档(document)有相同结构的定义。
Mongodb甚至不要求先建立数据库,然后建立集合,然后再插入数据的操作顺序。可以直接向不存在的数据库的集合中插入数据,Mongodb会建立相应的数据库和集合,并将文档插入。
ü 数据访问方式
通过mongodb提供的driver对数据进行操作,可以进行增删改查等操作,建立索引,对数据的聚合操作,排序等,另外,Mongodb还提供了Map-reduce的支持。
ü 数据冗余机制
Mongodb的数据冗余方式有两种,主从复制(Master-slave)和副本集(ReplicaSet)。现在推荐的是ReplicaSet的方式进行数据冗余同步。ReplicaSet可以看做是具有选主功能的Master-Slave同步方式。
ü 数据分片方式
Mongodb数据分片方式有hash和range两种方式,当一个chunk的大小超过配置时,Mongodb会将过大的chunk一分为二,然后将chunk在负载差别过大的分片群组(Shard)之间进行自动的迁移。
ü 数据存储方式
Mongodb的数据存储方式简单,将BSON和索引存入文件,然后将文件以mmap的形式影射到进程地址空间,Mongodb直接操作内存的方式来读写文件,文件数据在硬盘和内存之间的迁移完全由操作系统来完成。
2.2 Mongodb的集群架构
Mongodb既可以单节点运行,也可以作为集群部署,作为单节点运行时,客户端直接连接mongod进程或者连接多个mongod组成的副本集,进行增删改查操作。
Mongodb的集群架构如下图
架构中各个模块的作用如下:
ü Mongod
Mongodb的存储引擎,存储Mongodb的数据和索引,在生产环境下的集群部署中,一版每三个mongod模块构成一组副本集 (ReplicaSet)作为整个集群的一个分片(Shard),一个ReplicaSet,只有一个mongod是Primer,其余的mongod是 Secondary,写操作发生在Primer Mongod上,读操作默认情况下也是分发到Primer Mongod,Secondary Mongod从Primer同步数据,作为备份存在。
ü Config Server
配置服务器(ConfigServer)其实就是mongod,被用来专门存储集群的分片信息。路由节点根据ConfigServer上的分片信息 将客户端的操作命令分发给不同的分片进行处理。生产环境下部署3个ConfigServer,各个ConfigServer之间使用两阶段提交协议保证数 据的强一致性。
ü Mongos
集群的代理路由节点,接收客户端的增删改查命令,读取ConfigServer的分片信息,将操作路由到相应的Shard进行处理,对返回结果进行进一步处理后返回客户端。
Mongos还负责对集群中过大的数据块(chunk)进行分裂,然后将数据块(chunk)在Shard之间进行迁移。
ü 客户端
客户端程序通过mongodb提供的driver(C/C++/java/php等)连接mongos进行操作。
Mongodb提供了一个特殊的客户端Shell程序mongo,连接mongs,configserver和mongod,通过执行命令对集群进行管理,比如新建副本集(ReplicaSet),将分片加入集群,设置参数,手动分片等。
2.3 Mongodb的常见操作
2.3.1 CRUDI操作例子
Mongodb提供了对数据进行增删改查的接口,提供了建立索引的接口。可以通过Driver进行访问,也可以通过Mongodb提供的管理工具mongo进行操作。简单地例子如下:
ü 数据查询操作
use test;
上述语句的含义是在test数据库的users集合(collections)中查找age字段大于18的所有记录(document),然后根据age字段排序。
$gt是关系操作符,mongodb支持的关系操作符有$gt,$lt,$ne等。
ü 数据插入操作
将documen插入当前数据库的users集合中,数据插入操作会同步更新相应的索引。
ü 数据更新操作
更新当前数据库的users的集合,将其中age字段大于18的文档的status字段设置为”A”,update默认情况下只更新一行,如果要更新符合更新条件的多行,则需要制定multi为true。
除了$set命令外,Mongodb还支持自增($inc),自减($dec)更新命令。
ü 数据删除操作
在当前数据库中删除所有status字段等于”D”的文档,在删除过滤条件中可以使用$gt,$ne,$lt等关系操作符,也可以使用$or,$and等逻辑运算符。
ü 建立索引
Mongodb支持建立Index,可以建立单个字段的Index,也可以建立包括多个字段的复合index,还可以对一个字段先Hash后再建立Index,Mongodb的索引都是以B+树的形式存储的。
如果一次查询的过滤条件和输出字段都在索引中,则称该查询被Index覆盖(Cover),这种情形会显著提高Mongodb的查询效率。
Mongodb对一个document的操作是原子的,任何跨越document的操作都不保证原子性。
2.3.2 写保护级别
为了加快Mongodb的写入速度和满足不同业务场景的需要,Mongodb提供和各种级别的写保护(Write Concern)机制。
ü Errors Ignored
Mongodb不会对写请求进行结果响应,客户端无法检测写操作是否成功。通过设置driver的w参数为-1来使用,正常操作时不要设置此种类型的写保护。
ü Unacknowledged
Mongodb同样不会将写请求的结果发送给客户端,但是客户端会检测网络错误。这种级别的保护机制依然很弱,无法确定写操作是否写入成功。
ü Acknowledged
Mongodb会将写操作的结果发送给客户端,客户端可以检测写操作是否成功,客户端可以检测到网络异常,重复key异常和其他错误,但是不保证写 入内容已经写入磁盘。通过设置driver的w=1来使用该保护机制。Acknowledged写保护是Mongodb默认采用的写保护机制。
ü Journaled
Mongodb可以开启journal功能,Mongodb采用writeahead log的方式保证数据安全性。Mongodb将写操作先写入journal日志,然后才更改数据,当发生故障时,根据journal操作日志恢复数据。 Journal日志的提交(flush到硬盘)间隔时间由journalCommitInterval配置参数决定。
当使用Journal级别的写保护时,只有写操作日志提交给journal,也就是journal日志被flush到硬盘后,写操作才会返回给客户端。
通过设置journal=true来启用该保护机制,这种保护机制可以避免机器宕机或者重启造成的数据丢失。
ü Replica Acknowledged
这种保护机制只有在ReplicaSet集群配置时才有效,通过设置w=n(n>1)来使用该级别的写保护机制。只有数据在n个节点上都写成 功时才会返回客户端写成功。如果同时指定了journal和replica acknowledged级别的写保护,Mongodb保证在主节点上采用journaled级别的写保护,在从节点上采用acknowledged级别 的写保护。
综合一下,Error Ignore, Unacknowledged级别的写保护无法保证数据正确写入,Acknowledged级别的写保护可以保证数据成功写入系统缓 存,Journaled级别的写保护可以保证数据的安全性,保证数据在节点宕机的情况下数据的安全性。Replica Acknowledged级别的写保护可以保证数据成功写入多个节点,可以保证数据在宕机,断电或者磁盘坏的情况下数据的安全性。(即使是该级别的保护, 在断电,主节点磁盘损坏时依然无法保证数据安全性,但同时发生磁盘损坏和断电的可能性很小)。
应用可以根据数据的重要性采用不同的写保护机制,默认采用的是Acknowledged级别的写保护。
2.4 Mongodb的数据冗余机制
Mongodb的数据冗余机制分两种,主从复制(Master-Slave)和副本集(ReplicaSet)的方式。这两种冗余机制使用的都是通过回放主节点操作日志的异步数据同步方式。
主节点会将写操作日志记录到oplog中,从节点从主节点请求oplog日志,然后在节点上进行回放来构建数据;如果从节点和主节点的数据差异超过oplog的范围,则需要从节点和主节点先进行sync后再反演oplog日志。
ReplicaSet是在Master-Slave同步机制的基础上加入了选主机制。一个ReplicaSet中各个节点之间有心跳信息,当主节点 失败时,从节点发起一轮主节点选举过程,重新选出一个主节点。主节点选取跟各个节点配置的优先级和数据的更新程度有关。选主时先考虑优先级,优先级相等 时,拥有最新数据的节点胜出,成为新的主节点,各个节点与新的主节点进行数据同步,旧的主节点重新加入ReplicaSet后会作为从节点与新的主节点进 行数据同步。
Mongodb的这种通过操作日志回放进行数据同步的方式保证数据的最终一致性。主节点为了数据的安全性考虑加入了journal机制,但是由于不 同节点之间的数据是异步同步的,在主节点宕机或者网络故障时还是有可能丢失数据,在写负载重的场合丢失数据的可能性会更大,当然,这种可能性也取决于客户 端采用的写保护机制。
2.5 Mongodb的数据分片机制
NoSql系统的水平扩展性依赖于其数据分片机制和数据迁移机制。Mongodb集群通过自动分片(auto-sharding)机制来进行数据分片和数据迁移。
在一个Mongodb集群中,Shards(分片)负责存储数据,QueryRouter(Mongos)负责代理客户端请求,mongos是无状 态的,在一个Mongodb集群中可以配置多个mongs来分散客户端的操作压力。配置服务器(ConfigServer)负责存储集群元信息,元信息包 括从shard-key到shard的影射规则,mongos通过这些信息将用户请求路由到相应的shards。
Mongodb集群数据按照指定的shard key进行分片,数据集合必须存在一个以shard key开始的索引。可以按照shard-key范围或者按照hash进行分片,所谓hash分片就是将指定的shard key进行hash后,再进行范围分片。
Mongodb的数据分片机制可以分解为数据块分裂和数据块迁移两个独立的操作,数据块的分裂和迁移都是由mongs进程发起的,各个mongos进程会通过configServer进行同步。
ü 数据块分裂(chunk split)
Mongodb的数据块大小默认为64MB(可以配置),当数据块数据量过大时,Mongos会将过大的数据块一分为二,形成两个小的数据块。可以关闭数据块自动分裂功能,可以手动进行数据块的创建,分裂。
ü 数据块迁移
通过数据块的分裂,各个shard承载的数据块数量会不均匀,Mongodb会在各个shard之间自动地进行数据块的迁移。当负载最高的 shard和负载最低的shard所承载的数据块差值大于阈值(可以配置)时,mongos会将数据块从负载高的shard迁移至负载低的shard。
在数据块迁移之后,源shard会删除该数据块。如果迁移过程失败,不会对源shard的数据造成影响。
在线上系统中,数据自动迁移会造成网络压力,尤其是在集群负载已经很高的情况下,自动数据迁移会进一步加重系统负载。
Mongodb支持关闭自动的分片和迁移功能,支持手动配置分片,数据块分裂和数据迁移。
2.6 Mongodb的存储引擎架构
Mongodb的存储引擎设计比较简单。
Mongodb为每个数据库建立一个namespace(.ns结尾)文件,存储该数据库的元信息,比如该数据都有哪些collections,各个collections的数据文件,索引文件信息等。
数据库的所有数据都存放在文件中,Mongodb的文件采用预分配的方式来避免磁盘碎片和保证操作性能。第一次分配的数据文件为64M,后续新分配的数据文件级数递增,直到预分配的数据文件大小达到2G。
Mongodb通过mmap来操作所有的磁盘文件,Mongodb启动时将ns文件和所有的数据文件通过mmap的方式全部影射到进程逻辑地址空间(在32位系统下Mongodb最大支持2G的磁盘文件),后续通过内存操作来操作物理文件。
Mongodb的数据文件格式如下
DataFileHeader是数据文件的头部,后面的部分为Extent。文件空间的分配以Extent为单位。每个命名空间的所申请的Extent形成一个双向链表,表头和表尾存在命名空间信息里。
Record即记录,在Extent里分配,每个Extent里的所有Record形成一个双向链表,表头和表尾存在Extent头部。可以想到, 对命名空间的所有Record的遍历方法为:遍历Extent链表,对每个Extent,遍历其Record链表。空闲的Record(Extent里剩 余的空间、或者Record被删除),称作DeleteRecord,根据其大小,形成19个单向链表(表头也存在命名空间里)。可以想到,申请一个 Record的方法:先从空闲的Record里面找;如果找不到,则分配新的Extent。
当一个命名空间被删除的时候,它的所有的Extent都会挂到名为$freelist的collection的Extent链表中。那么,分配Extent的时候,会先从$freelist的Extent链表中寻找。如果找不到,就申请新的Extent。
Mongodb索引结构采用B+树的形式建立。
2.7 Mongodb存储引擎优缺点分析
Mongodb采用mmap将存储文件全部影射到进程逻辑地址空间,通过操作系统的page cache和缺页中断机制来实现数据在内存和磁盘之间的迁移。
Mongodb的这种实现,将系统的所有内存当做存储系统的缓存,而缓存的管理完全由操作系统的页面缓存(page cache)机制来管理,大大简化了Mongodb的存储引擎设计和实现。
这种设计的优点是在系统内存充足的时候会将所有的数据和索引文件都放在内存中,可以达到很高的读写速度;另外,由于将内存管理任务交给操作系统的页缓存来完成,使得Mongodb的存储引擎实现简单明了。
但是这种设计也很大的缺点:
1> 通过mmap的形式将索引和数据文件全部影射到进程地址空间,使用页缓存来管理内存,使得mongodb会占用大量内存,随着数据量增加,所有的系统内存都会被Mongodb占用。
2> 操作系统无法理解各个文件之间的区别,只能将索引和数据等同看待,使用操作系统本身页缓存的LRU算法进行数据在磁盘和内存之间的换入换出,对内存的使用效率低,无法保证索引比数据优先使用内存。
3> Mongodb没有对数据在磁盘文件上的存储进行优化,随着大量更新和删除操作,磁盘文件会出现碎片,即使删除了数据库的部分数据,磁盘空间也不会归还给系统,必须使用自带的Compact工具重新进行磁盘压缩来减少磁盘碎片。
4> Mongodb由于采用了MMap机制操作磁盘和内存,无法对索引和数据的读取进行优化,因为Mongodb无法知道索引和数据是存放在内存还是存放在磁盘,Mongodb看到的是所有的索引和数据都存在内存的假象,无法进行算法实现上的优化。
5> Mongodb在数据和索引的量超过系统内存量时会造成大量的缺页中断,导致数据在磁盘和内存之间频繁换入换出,增加磁盘IO压力,增加访问延迟。
可见,Mongodb存储引擎更适用于数据和索引能够全部放在内存的场合,至少是数据和索引的工作集(热数据)可以完全放入内存的场合。
2.8 Mongodb单节点性能测试
通过2.7的分析,预测Mongodb存储引擎在数据集合大于内存时存在性能问题,构造测试环境进行了验证。
测试环境:
系统: VMWare虚拟机CentOS6.4
CPU: 4核
内存: 6G
2.8.2 小数据量性能测试
数据量:
数据条数: 1千万条
数据量: 610.3M
索引量: 266.1M
访问方式:
客户端和服务端在同一台机器上,key随机访问
性能数据:
单线程查询访问次数:10001
耗时: 1.5s
平均访问延迟为0.14ms
最短访问延迟:0.1ms
最大访问延迟为:0.7ms
可见,数据集完全在内存中时Mongodb的访问性能相当好。
2.8.1 大数据量性能测试
数据量:
数据条数: 4.54亿条
数据量: 34.6G
索引量: 12.6G
数据检索方式:
使用索引key进行随机访问。
测试结果:
1. 客户端单线程访问Mongodb
访问14173次,耗时585s,qps为24,
平均访问延迟为:41.2ms。
最短访问延迟为:2.4ms
最长访问延迟为:385.2ms
2. 客户端多线程访问Mongodb
5个客户端同时访问Mongodb
一个客户端访问10600次,耗时1414s,qps为7.5,
平均访问延迟为133.4ms。
最短访问延迟为:2.1ms
最长访问延迟为:5724.9ms
可见,在数据集过大时,随机访问mongodb会造成大量的磁盘IO和频繁的内存换入换出,导致Mongodb的性能惨不忍睹。
3. 调研结论
Mongodb提供了Schema Less的文档存储特性,并且支持在文档上建立一级和多级索引,允许对存储数据的格式进行任意的修改。
Mongodb提供了数据备份的Master-Slave和ReplicaSet机制,再结合多种写保护等级选择来保证数据的安全。
Mongodb的分片管理机制可以自动或者手动进行分片管理,可以比较容易地进行集群规模的水平扩展,通过水平扩展,Mongodb理论上可以存储大规模数据。
Mongodb使用mmap的方式管理磁盘存储,简化了存储管理,利用系统内存来提高读写效率,在数据和索引可以全部放在内存时提供很好的读写性能。
但是,Mongodb的高性能是建立在数据和索引都存放在内存中的前提下的,当单个节点的数据集合过大,超过系统内存时,Mongodb的性能会发生很大的波动,如果工作集过大无法放入内存时,Mongodb的访问性能会严重下降。
综合来看:
Mongodb是一个比较好的内存数据库,Mongodb与Redis更加相似。Mongodb的SchemaLess属性适合产品原型时期的快速 开发,能够适应数据格式的频繁变化。通过水平扩展提供大数据的存储能力,通过ReplicaSet提供比较好的数据安全性。Mongodb更适合用于存储 数据量规模不大,需要持久化和快速访问的场合。
但是Mongodb在存储引擎上的设计缺陷使得单节点的数据存储受限于内存大小,当数据量超过内存容量时性能严重下降。考虑到成本问题,Mongodb不适合用作大规模数据的存储系统。
Mongodb的竞争对手应该是Redis集群,而不是key-value存储集群或者传统的mysql集群。
4. 参考文献
http://www.mongodb.com/ mongodb官网
http://docs.mongodb.org/manual/mongodb手册
http://api.mongodb.org/cplusplus/2.4.8/mongodb C++ DriverAPI
http://docs.mongodb.org/ecosystem/drivers/cpp/
http://www.cnblogs.com/tripleH/archive/2013/03/15/2958147.html
http://www.cnblogs.com/foxracle/p/3421893.html
http://nyeggen.com/blog/2013/10/18/the-genius-and-folly-of-mongodb/