Fork me on GitHub

Mongodb补充

简介

MongoDB 是一个由 C++ 语言编写的基于分布式文件存储的数据库,旨在为 WEB 应用提供可扩展的高性能数据存储解决方案,这里简要介绍一下基本操作和存储原理,勉强能够应付日常工作。如果主要工作环境和业务都是mongo,那么就需要深入研究了,本文是远远不够的。

P1(基础阶段)

db.COLLECTION_NAME.insert(document)

例:

db.col.insert({title: 'MongoDB 教程', 
    description: 'MongoDB 是一个 Nosql 数据库',
    by: '菜鸟教程',
    url: 'http://www.runoob.com',
    tags: ['mongodb', 'database', 'NoSQL'],
    likes: 100
})

db.collection.remove(
query,
justOne
)

db.col.remove({'title':'MongoDB 教程'})

db.col.remove({})

db.col.find()

db.collection.update(
,
,
{
upsert: ,
multi: ,
writeConcern:
}
)

例:

db.col.update({‘title’:’MongoDB 教程’},{$set:{‘title’:’MongoDB’}})

db.col.update({‘title’:’MongoDB 教程’},{$set:{‘title’:’MongoDB’}},{multi:true})

db.col.save({
“_id” : ObjectId(“56064f89ade2f21f36b03136”),
“title” : “MongoDB”,
“description” : “MongoDB 是一个 Nosql 数据库”,
“by” : “Runoob”,
“url” : “http://www.runoob.com“,
“tags” : [
“mongodb”,
“NoSQL”
],
“likes” : 110
})

MongoDB 查询文档使用 find() 方法,以非结构化的方式来显示所有文档。

db.collection.find(query, projection)

可以使用where,大于等于小于,与或非等条件进行查询,具体语句自行查找

排序

db.COLLECTION_NAME.find().sort({KEY:1})

db.col.find({},{"title":1,_id:0}).sort({"likes":-1})

索引

db.COLLECTION_NAME.ensureIndex({KEY:1})

db.col.ensureIndex({"title":1})

聚合

MongoDB中聚合(aggregate)主要用于处理数据(诸如统计平均值,求和等),并返回计算后的数据结果。有点类似sql语句中的 count(*)。

db.COLLECTION_NAME.aggregate(AGGREGATE_OPERATION)

db.mycol.aggregate([{$group : {_id : "$by_user", num_tutorial : {$sum : 1}}}])

mongo集群搭建

大概就是副本集,主从,sharding,目的是将数据同步到多个服务器,复制提供数据的冗余备份,从而提高了数据的可用性, 并保证数据的安全性,主节点记录在其上的所有操作oplog,从节点定期轮询主节点获取这些操作,然后对自己的数据副本执行这些操作,从而保证从节点的数据与主节点一致。

mongo备份恢复

在另一篇博客里有应用场景的命令格式,大概就是用mongodump和mongorestor两个命令实现数据导出到备份集和导入

mongo监控

mongostat命令是mongo自带的状态检测工具,可以间隔固定时间获取并输出mongodb的当前运行状态,数据库变慢或者有异常的首选操作

P2(稍高一点)

mongodb map reduce

Map-Reduce是一种计算模型,简单的说就是将大批量的工作(数据)分解(MAP)执行,然后再将结果合并成最终结果(REDUCE),MongoDB提供的Map-Reduce非常灵活,对于大规模数据分析也相当实用

以下是MapReduce的基本语法:

>db.collection.mapReduce(
   function() {emit(key,value);},  //map 函数
   function(key,values) {return reduceFunction},   //reduce 函数
   {
  out: collection,
  query: document,
  sort: document,
  limit: number
   }
)

使用 MapReduce 要实现两个函数 Map 函数和 Reduce 函数,Map 函数调用 emit(key, value), 遍历 collection 中所有的记录, 将 key 与 value 传递给 Reduce 函数进行处理。

Map 函数必须调用 emit(key, value) 返回键值对。

参数说明:

  • map :映射函数 (生成键值对序列,作为 reduce 函数参数)。
  • reduce 统计函数,reduce函数的任务就是将key-values变成key-value,也就是把values数组变成一个单一的值value。。
  • out 统计结果存放集合 (不指定则使用临时集合,在客户端断开后自动删除)。
  • query 一个筛选条件,只有满足条件的文档才会调用map函数。(query。limit,sort可以随意组合)
  • sort 和limit结合的sort排序参数(也是在发往map函数前给文档排序),可以优化分组机制
  • limit 发往map函数的文档数量的上限(要是没有limit,单独使用sort的用处不大)

以下实例在集合 orders 中查找 status:”A” 的数据,并根据 cust_id 来分组,并计算 amount 的总和。

操作实例

准备数据集

db.posts.insert({
   "post_text": "菜鸟教程,最全的技术文档。",
   "user_name": "mark",
   "status":"active"
})
WriteResult({ "nInserted" : 1 })
db.posts.insert({
   "post_text": "菜鸟教程,最全的技术文档。",
   "user_name": "mark",
   "status":"active"
})
WriteResult({ "nInserted" : 1 })
db.posts.insert({
   "post_text": "菜鸟教程,最全的技术文档。",
   "user_name": "mark",
   "status":"active"
})
WriteResult({ "nInserted" : 1 })
db.posts.insert({
   "post_text": "菜鸟教程,最全的技术文档。",
   "user_name": "mark",
   "status":"active"
})
WriteResult({ "nInserted" : 1 })
db.posts.insert({
   "post_text": "菜鸟教程,最全的技术文档。",
   "user_name": "mark",
   "status":"disabled"
})
WriteResult({ "nInserted" : 1 })
db.posts.insert({
   "post_text": "菜鸟教程,最全的技术文档。",
   "user_name": "runoob",
   "status":"disabled"
})
WriteResult({ "nInserted" : 1 })
db.posts.insert({
   "post_text": "菜鸟教程,最全的技术文档。",
   "user_name": "runoob",
   "status":"disabled"
})
WriteResult({ "nInserted" : 1 })
db.posts.insert({
   "post_text": "菜鸟教程,最全的技术文档。",
   "user_name": "runoob",
   "status":"active"
})
WriteResult({ "nInserted" : 1 })

在 posts 集合中使用 mapReduce 函数来选取已发布的文章(status:”active”),并通过user_name分组,计算每个用户的文章数

db.posts.mapReduce( 
   function() { emit(this.user_name,1); }, 
   function(key, values) {return Array.sum(values)}, 
  {  
 query:{status:"active"},  
 out:"post_total" 
  }
)

结果如下

结果表明,共有 5 个符合查询条件(status:”active”)的文档, 在map函数中生成了 5 个键值对文档,最后使用reduce函数将相同的键值分为 2 组。

用类似的方式,MapReduce可以被用来构建大型复杂的聚合查询。

Map函数和Reduce函数可以使用 JavaScript 来实现,使得MapReduce的使用非常灵活和强大。

以小见大,mongo集群的存储性能,可扩展性,对mapreduce的支持,使得分布式集群搭建后运行各种数据分析挖掘算法来处理海量数据有了可靠的保证。

mongodb 全文检索

全文检索对每一个词建立一个索引,指明该词在文章中出现的次数和位置,当用户查询时,检索程序就根据事先建立的索引进行查找,并将查找的结果反馈给用户的检索方式。

这个过程类似于通过字典中的检索字表查字的过程。

MongoDB 从 2.4 版本开始支持全文检索,目前支持15种语言(暂时不支持中文)的全文索引。

mongodb 正则表达式

正则表达式是使用单个字符串来描述,匹配一系列符合某个语法规则的字符串。

正则表达式在文本处理方面极为实用,很多语言都支持利用正则表达式进行字符串操作

mongodb使用$regex操作符来设置匹配字符串的正则表达式

可以设置 $options 为 $i 使正则匹配忽略大小写

如果你的文档中字段设置了索引,那么使用索引相比于正则表达式匹配查找所有的数据查询速度更快

mongodb 管理工具: Rockmongo

rockmongo是php写的一个mongodb管理工具,可以用来管理服务,数据库,集合,文档,索引等,具体使用方法等用到了学就是,界面管理方法想来也是跟Plsqldeveloper和navicat那些差不多的页面版

P3(再高一点点)

mongo是常用的面向文档非关系数据库,主要应用场景在微博,博客等消息存储业务,数据与金融等传统行业比起来没有那么重要,对事务要求相对不高,这时候用mongo比关系型数据库更合适,因为关系型数据库每次操作都有ACK,mongo省去了这一步,大大提高存储性能,同时设计时就考虑了廉价设备的容错和业务增长时的可扩展性。

在其他的博客内容对mongo的集群搭建有了一定的认识,这里通过一种副本集与分片混合部署的方案和数据存取,数据存储,对mongo的应用做进一步的介绍。

副本集中每个节点存储的数据都是相同的,相当于主备方式的数据冗余,分片是为了数据拓展,按照片键进行节点划分,数据根据片键存储到对应服务器上。

mongo的集群中有三类角色:实际数据存储节点,配置文件存储节点和路由介入节点。

ps:在另一个介绍集群搭建方案的博客里,副本集配置里除了存储节点还有一个arbiter节点,负责节点故障的时候仲裁主节点切换,实测有该节点时双节点中主节点停/起副节点会自动切主备,没有则主掉了副节点还是副节点,不会自动切换

客户端与路由节点连接,从配置节点上查询数据,根据查询结果到实际的存储节点上查询和存储数据。

客户端发起写操作请求,路由节点接到请求后查询配置服务器,得到有存储空间的服务器,路由节点把写入操作转发给由副本集组成的数据存储服务器,管理过程类似hdfs中master节点对chunk server的管理

客户端发起读操作请求,路由节点接到请求后查询配置服务器,根据存储服务器中的记录,返回目标数据的存储服务器路径大小等信息,路由节点转发读操作到存储服务器,获取信息后路由服务器将查询结果返回给客户端

副本集内部的写操作

写操作只写到主节点当中,由主节点以异步的方式同步到从节点

副本集内部的读操作

可以从任意节点读取数据,也可以指定具体到哪个节点

P4(可以说相当高了)

存储引擎:wiredTiger

按照Mongodb默认的配置,WiredTiger的写操作会先写入Cache,并持久化到WAL(Write ahead log),每60s或log文件达到2GB时会做一次Checkpoint,将当前的数据持久化,产生一个新的快照。Wiredtiger连接初始化时,首先将数据恢复至最新的快照状态,然后根据WAL恢复数据,以保证存储可靠性

所有write请求都基于”文档级别”的lock,因此多个客户端可以同时更新一个collection中的不同文档,这种更细颗粒度的lock,可以支撑更高的读写负载和并发量,因为对于production环境,更多的CPU可以有效提升wiredTiger性能,因为它是多线程IO。

架构模式

Replica set:复制集,mongodb的架构方式之一 ,通常是三个对等的节点构成一个“复制集”集群

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

系统配置:

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

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

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

- ulimit参数调整,这个在基于网络IO或者磁盘IO操作的应用中,通常都会调整,上调系统允许打开的文件个数(ulimit -n 65535)

数据文件存储原理

Data Files

mongodb的数据会保存在底层文件系统中,比如”dbpath=/usr/local/mongodb/master”,创建一个database为”runoob”,collection为”col”,然后插入若干documents,可以看到如下列表

可以看到数据文件所在目录”/usr/local/mongodb/master”下面生成了”runoob”文件夹,里面存放有若干collection文件,数据文件从16M开始,每次扩张一倍(16M,32M,64M,128M…),默认情况下迟迟单个data file最大尺寸为2G,smallfile设置后限定512M,每个database最多支持16000个数据文件,约32T,smallfiles设置后单个database最大容量8T

一个database中所有的collections及索引信息会分散存储在多个数据文件中,没有像SQL数据库一样每个表的数据索引分别存储;

数据分块的单位是extent(范围,区域),extent中可以保存collection数据或者index数据,每个extent包含多条document,大小不等,一个extent只能保存同一个collection的数据,不同collections数据分布在不同extents中,indexs也保存在各自的extent中,一个collection由一个或多个extent构成,最小size8K,最大2G,这些extent分散在多个datafile中,换句话说,一个datafile可能包含多个collection的数据,由不同collection的extent组成,但一个extent不会跨越两个datafile

举个栗子(吼吼),两个数据文件my-db.1和my-db.2里存放A和B两张表的数据,AB两张表上的document和index存放在一个个小的extent里面,这些extent并不是各自集中存放,而是散布在两个数据文件里面

在每个database的namespace文件中,比如test.ns文件中,每个collection只保存了第一个extent的位置信息,并不保存所有的extents列表,但每个extent都维护者一个链表关系,即每个extent都在其header信息中记录了此extent的上一个、下一个extent的位置信息,这样当对此collection进行scan操作时(比如全表扫描),可以提供很大的便利性。

由存储机制可以很容易想到,删除document会导致磁盘碎片,有些update也会导致磁盘碎片,比如update导致文档尺寸变大,进而超过原来分配的空间;当有新的insert操作时,mongodb会检测现有的extents中是否合适的碎片空间可以被重用,如果有,则重用这些fragment,否则分配新的存储空间。磁盘碎片对write操作有一定的性能影响,而且会导致磁盘空间浪费;

如果database已经运行一段时间,数据已经有很大的磁盘碎片(storageSize与dataSize比较),可以通过mongodump将指定database的所有数据导出,然后将原有的db删除,再通过mongorestore指令将数据重新导入

Namespace文件

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

journal文件

journal日志保存在dbpath下“journal”子目录中,一般会有三个journal文件,journal文件中保存了write操作的记录,每条记录中包含write操作内容之外,还包含一个“lsn”(last sequence number),表示此记录的ID

journal是一个预写事务日志,来确保数据持久性,存储引擎每隔60s(默认/可调)或者待写入数据达到2G,mongo将对journal文件提交一个checkpoint检测点,将内存中的数据变更flush到磁盘的数据文件中,并做一个标记点,表示此前的数据已经持久存储在数据文件中,检测点创建后,journal日志清空,后续数据更改存在于内存和journal日志。

例如write操作首先被写入journal日志,然后在内存中变更数据,数据量积累或者时间间隔条件满足后触发检测点,数据flush到数据文件。即检测点之前的数据只是在journal中持久存储,并没有写入数据文件,延迟持久化可以提高磁盘效率,如果在checkpoint之前mongo异常退出,再次启动可以根据journal恢复数据。

默认情况下,“journal”特性是开启的,特别在production环境中,我们没有理由来关闭它,当然也可以关闭journal,提高了性能,但单点mongo降低了数据安全性,如果有异常则丢失checkpoint之间的数据,副本集在所有节点同时退出的情况时有影响。

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

1)减小commitIntervalMs的值

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

3)最佳手段就是采用“replica set”架构模式,通过数据备份方式解决,同时还需要在“write concern”中指定“w”选项,且保障级别不低于“majority”,最终我们需要在“写入性能”和“数据一致性”两个方面权衡,即CAP理论。

至此mongodb的基础内容介绍得差不多,具体实践和高级技巧等工作项目中遇到了再深入研究,理论指导实践,在实
践中检验和调整升华理论,然后在下一次实践中表现得更加出色,理论和实践的螺旋式上升是不变的旋律