MongoDB 全面总结

原文地址:http://blog.csdn.net/jakenson/article/details/7060431

 

 MongoDB的内部构造《MongoDB The Definitive Guide》

MongoDB的官方文档基本是how to do的介绍,而关于how it worked却少之又少,本人也刚买了《MongoDB TheDefinitive Guide》的影印版,还没来得及看,本文原作者将其书中一些关于MongoDB内部现实方面的一些知识介绍如下,值得一看。

今天下载了《MongoDB The Definitive Guide》电子版,浏览了里面的内容,还是挺丰富的。是官网文档实际应用方面的一个补充。和官方文档类似,介绍MongoDB的内部原理是少之又少,只有在附录的一个章节中介绍了相关内容。

对于大多数的MongoDB的用户来说,MongoDB就像是一个大黑盒,但是如果你能够了解到MongoDB内部一些构造的话,将有利于你更好地理解和使用MongoDB。

BSON

在MongoDB中,文档是对数据的抽象,它被使用在Client端和Server端的交互中。所有的Client端(各种语言的Driver)都会使用这种抽象,它的表现形式就是我们常说的BSON(Binary JSON )。

BSON是一个轻量级的二进制数据格式。MongoDB能够使用BSON,并将BSON作为数据的存储存放在磁盘中。

当Client端要将写入文档,使用查询等等操作时,需要将文档编码为BSON格式,然后再发送给Server端。同样,Server端的返回结果也是编码为BSON格式再放回给Client端的。

使用BSON格式出于以下3种目的:

效率

BSON是为效率而设计的,它只需要使用很少的空间。即使在最坏的情况下,BSON格式也比JSON格式再最好的情况下存储效率高。

传输性

在某些情况下,BSON会牺牲额外的空间让数据的传输更加方便。比如,字符串的传输的前缀会标识字符串的长度,而不是在字符串的末尾打上结束的标记。这样的传输形式有利于MongoDB修改传输的数据。

性能

最后,BSON格式的编码和解码都是非常快速的。它使用了C风格的数据表现形式,这样在各种语言中都可以高效地使用。

更多关于BSON的介绍,可以参考:http://www.bsonspec.org

写入协议

Client端访问Server端使用了轻量级的TCP/IP写入协议。这种协议在MongoDB Wiki中有详细介绍,它其实是在BSON数据上面做了一层简单的包装。比如说,写入数据的命令中包含了1个20字节的消息头(由消息的长度和写入命令标识组成),需要写入的Collection名称和需要写入的数据。

数据文件

在MongoDB的数据文件夹中(默认路径是/data/db)由构成数据库的所有文件。每一个数据库都包含一个.ns文件和一些数据文件,其中数据文件会随着数据量的增加而变多。所以如果有一个数据库名字叫做foo,那么构成foo这个数据库的文件就会由foo.ns,foo.0,foo.1,foo.2等等组成。

数据文件每新增一次,大小都会是上一个数据文件的2倍,每个数据文件最大2G。这样的设计有利于防止数据量较小的数据库浪费过多的空间,同时又能保证数据量较大的数据库有相应的空间使用。

MongoDB会使用预分配方式来保证写入性能的稳定(这种方式可以使用–noprealloc关闭)。预分配在后台进行,并且每个预分配的文件都用0进行填充。这会让MongoDB始终保持额外的空间和空余的数据文件,从而避免了数据增长过快而带来的分配磁盘空间引起的阻塞。

名字空间和盘区

每一个数据库都由多个名字空间组成,每一个名字空间存储了相应类型的数据。数据库中的每一个Collection都有各自对应的名字空间,索引文件同样也有名字空间。所有名字空间的元数据都存储在.ns文件中。

名字空间中的数据在磁盘中分为多个区间,这个叫做盘区。在下图中,foo这个数据库包含3个数据文件,第三个数据文件属于空的预分配文件。头两个数据文件被分为了相应的盘区对应不同的名字空间。

 

上图显示了名字空间和盘区的相关特点。每一个名字空间可以包含多个不同的盘区,这些盘区并不是连续的。与数据文件的增长相同,每一个名字空间对应的盘区大小的也是随着分配的次数不断增长的。这样做的目的是为了平衡名字空间浪费的空间与保持某一个名字空间中数据的连续性。上图中还有一个需要注意的名字空间:$freelist,这个名字空间用于记录不再使用的盘区(被删除的Collection或索引)。每当名字空间需要分配新的盘区的时候,都会先查看$freelist是否有大小合适的盘区可以使用。

内存映射存储引擎

MongoDB目前支持的存储引擎为内存映射引擎。当MongoDB启动的时候,会将所有的数据文件映射到内存中,然后操作系统会托管所有的磁盘操作。这种存储引擎有以下几种特点:

* MongoDB中关于内存管理的代码非常精简,毕竟相关的工作已经有操作系统进行托管。

* MongoDB服务器使用的虚拟内存将非常巨大,并将超过整个数据文件的大小。不用担心,操作系统会去处理这一切。

* MongoDB无法控制数据写入磁盘的顺序,这样将导致MongoDB无法实现writeahead日志的特性。所以,如果MongoDB希望提供一种durability的特性(这一特性可以参考我写的关于Cassandra文章:http://www.cnblogs.com/gpcuster/tag/Cassandra/),需要实现另外一种存储引擎。

* 32位系统的MongoDB服务器每一个Mongod实例只能使用2G的数据文件。这是由于地址指针只能支持32位。

其他

在《MongoDB The Definitive Guide》中介绍的MongoDB内部构造只有这么多,如果真要把它说清楚,可能需要另外一本书来专门讲述了。比如内部的JS解析,查询的优化,索引的建立等等。有兴趣的朋友可以直接参考源代码

 

 

MongoDB的架构

当前架构                           双服务器架构

 

 

 

 

 

 

 

 

 

 

 

 

 

 

当前架构为单shard+replica Set模式,双服务器为双Shard+Replica Set模式。同一个Shard中的primary和Secondary存储内容一致。而双Shard则是两个Shard分布式存储不同数据,备份由shard内部进行。

双服务器中的两个Shard各含一个primary ,一个secondary,和一个arbiter(arbiter的唯一作用是在primary 宕机后选举新的primary时拥有投票权,用以使存活节点数大于50%,不包括50%,否则系统将整个down掉,以及在票数相同的情况下用以打破选举的平衡,并不存储和读取数据)。

因为同一个shard中,只有primary可以用以写,secondary只是用于对primary节点的备份并用于读操作,然后再primary宕机的情况下接管它的工作。所以,双shard模式下,两个服务器分别包含一个primary,而且同一个shard的arbiter必须和secondary在一个服务器上。这样子既保证了两个服务器都可以进行读、写操作,而且在primary down的时候也能够继续使得选取成功secondary。

后续扩展时,可以再在集群中添加新的shard,然后与老的shard进行balance均衡操作。

MongoDB的特点

MongoDB 是一个面向集合的,模式自由的文档型数据库.

面向集合, 意思是数据被分组到若干集合,这些集合称作聚集(collections). 在数据库里每个聚集有一个唯一的名字,可以包含无限个文档. 聚集是RDBMS中表的同义词,区别是聚集不需要进行模式定义.

模式自由, 意思是数据库并不需要知道你将存入到聚集中的文档的任何结构信息.实际上,你可以在同一个聚集中存储不同结构的文档.

文档型, 意思是我们存储的数据是键-值对的集合,键是字符串,值可以是数据类型集合里的任意类型,包括数组和文档. 我们把这个数据格式称作 "[BSON]"即 "Binary Serialized dOcument Notation."

u  面向文档存储:(类JSON数据模式简单而强大)。

u  高效的传统存储方式:支持二进制数据及大型对象(如照片和视频)。

u  复制及自动故障转移:Mongo数据库支持服务器之间的数据复制,支持主-从模式及服务器之间的相互复制。

u  Auto-Sharding自动分片支持云级扩展性(处于早期alpha阶段):自动分片功能支持水平的数据库集群,可动态添加额外的机器。

u  动态查询:它支持丰富的查询表达式。查询指令使用JSON形式的标记,可轻易查询文档中内嵌的对象及数组。

u  全索引支持:包括文档内嵌对象及数组。Mongo的查询优化器会分析查询表达式,并生成一个高效的查询计划。

u  支持RUBY,PYTHON,JAVA,C++,PHP等多种语言。

u  面向集合存储,易存储对象类型的数据:存储在集合中的文档,被存储为键-值对的形式。键用于唯一标识一个文档,为字符串类型,而值则可以是各中复杂的文件类型;

u  *模式自由:存储在mongodb数据库中的文件,我们不需要知道它的任何结构定义;

u  *支持完全索引,包含内部对象。

u  *支持复制和故障恢复。

u  *自动处理碎片: 自动分片功能支持水平的数据库集群,可动态添加额外的机器

u 查询监视:Mongo包含一个监视工具用于分析数据库操作的性能

MongoDB的功能

       查询:基于查询对象或者类SQL语句搜索文档. 查询结果可以排序,进行返回大小限制,可以跳过部分结果集,也可以返回文档的一部分.

       插入和更新 : 插入新文档,更新已有文档.

       索引管理 : 对文档的一个或者多个键(包括子结构)创建索引,删除索引等等

常用命令: 所有MongoDB 操作都可以通过socket传输的DB命令来执行.

MongoDB的局限性与不足

本文来源于对Quora上一个问答的整理,主要列举了MongoDB身上一些局限的功能及目前做得不够好的地方。其中包括了原本就并非MongoDB想做的部分,也包括了MongoDB想做但没做好的方面。

  • 在32位系统上,不支持大于2.5G的数据。详见这里
  • 单个文档大小限制为 4 M/16 M(1.8版本后升为16M)
  • 锁粒度太粗,MongoDB使用的是一把全局的读写锁,详见这里
  • 不支持join操作和事务机制,这个确实是非MongoDB要做的领域
  • 对内存要求比较大,至少要保证热数据(索引,数据及系统其它开销)都能装进内存
  • 用户权限方面比较弱,这一点MongoDB官方推荐的是将机器部署在安全的内网环境中,尽量不要用权限,详见这里
  • MapReduce在单个实例上无法并行,只有采用Auto-Sharding才能并行。这是由JS引擎的限制造成的
  • MapReduce的结果无法写入到一个被Sharding的Collection中,2.0版本对这个问题的解决好像也不彻底
  • 对于数组型的数据操作不够丰富
  • Auto-Sharding还存在很多问题,所谓的水平扩展也不是那么理想

适用范围

u  适合实时的插入,更新与查询,并具备应用程序实时数据存储所需的复制及高度伸缩性。

u  适合作为信息基础设施的持久化缓存层。

u  适合由数十或数百台服务器组成的数据库。因为Mongo已经包含对MapReduce引擎的内置支持。

u  Mongo的BSON数据格式非常适合文档化格式的存储及查询。

网站数据:Mongo非常适合实时的插入,更新与查询,并具备网站实时数据存储所需的复制及高度伸缩性。

u  ◆缓存:由于性能很高,Mongo也适合作为信息基础设施的缓存层。在系统重启之后,由Mongo搭建的持久化缓存层可以避免下层的数据源过载。

u  ◆大尺寸,低价值的数据:使用传统的关系型数据库存储一些数据时可能会比较昂贵,在此之前,很多时候程序员往往会选择传统的文件进行存储。

u  ◆高伸缩性的场景:Mongo非常适合由数十或数百台服务器组成的数据库。Mongo的路线图中已经包含对MapReduce引擎的内置支持。

u  ◆用于对象及JSON数据的存储:Mongo的BSON数据格式非常适合文档化格式的存储及查询

 

 

MongoDB的不适用范围

·        高度事务性的系统。

·        传统的商业智能应用。

·        级为复杂的SQL查询。

·        ◆高度事务性的系统:例如银行或会计系统。传统的关系型数据库目前还是更适用于需要大量原子性复杂事务的应用程序。

·        ◆传统的商业智能应用:针对特定问题的BI数据库会对产生高度优化的查询方式。对于此类应用,数据仓库可能是更合适的选择。

·        ◆需要SQL的问题

 

u   

要点

跟mysqld一样,一个mongod服务可以有建立多个数据库,每个数据库可以有多张表,这里的表名叫collection,每个collection可以存放多个文档(document),每个文档都以BSON(binary json)的形式存放于硬盘中。跟关系型数据库不一样的地方是,它是的以单文档为单位存储的,你可以任意给一个或一批文档新增或删除字段,而不会对其它文档造成影响,这就是所谓的schema-free,这也是文档型数据库最主要的优点。跟一般的key-value数据库不一样的是,它的value中存储了结构信息,所以你又可以像关系型数据库那样对某些域进行读写、统计等操作。可以说是兼备了key-value数据库的方便高效与关系型数据库的强大功能。

索引

跟关系型数据库类似,mongodb可以对某个字段建立索引,可以建立组合索引、唯一索引,也可以删除索引。当然建立索引就意味着增加空间开销,我的建议是,如果你能把一个文档作为一个对象的来考虑,在线上应用中,你通常只要对对象ID建立一个索引即可,根据ID取出对象某些数据放在memcache即可。如果是后台的分析需要,响应要求不高,查询非索引的字段即便直接扫表也费不了太多时间。如果还受不了,就再建一个索引得了。

默认情况下每个表都会有一个唯一索引:_id,如果插入数据时没有指定_id,服务会自动生成一个_id,为了充分利用已有索引,减少空间开销,最好是自己指定一个unique的key为_id,通常用对象的ID比较合适,比如商品的ID。

capped collection

capped collection是一种特殊的表,它的建表命令为:

db.createCollection("mycoll",{capped:true, size:100000})

允许在建表之初就指定一定的空间大小,接下来的插入操作会不断地按顺序APPEND数据在这个预分配好空间的文件中,如果已经超出空间大小,则回到文件头覆盖原来的数据继续插入。这种结构保证了插入和查询的高效性,它不允许删除单个记录,更新的也有限制:不能超过原有记录的大小。这种表效率很高,它适用于一些暂时保存数据的场合,比如网站中登录用户的session信息,又比如一些程序的监控日志,都是属于过了一定的时间就可以被覆盖的数据。

复制与分片

mongodb的复制架构跟mysql也很类似,除了包括master-slave构型和master-master构型之外,还有一个Replica pairs构型,这种构型在平常可以像master-slave那样工作,一但master出现问题,应用会自动了连接slave。要做复制也很简单,我自己使用过master-slave构型,只要在某一个服务启动时加上–master参数,而另一个服务加上–slave与–source参数,即可实现同步。

分片是个很头疼的问题,数据量大了肯定要分片,mysql下的分片正是成为无数DBA的噩梦。在mongodb下,文档数据库类似key-value数据库那样的易分布特性就显现出来了,无论构造分片服务,新增节点还是删除节点都非常容易实现。但mongodb在这方面做还不足够成熟,现在分片的工作还只做到alpha2版本(mongodb v1.1),估计还有很多问题要解决,所以只能期待,就不多说了。

性能

在我的使用场合下,千万级别的文档对象,近10G的数据,对有索引的ID的查询不会比mysql慢,而对非索引字段的查询,则是全面胜出。mysql实际无法胜任大数据量下任意字段的查询,而mongodb的查询性能实在让我惊讶。写入性能同样很令人满意,同样写入百万级别的数据,mongodb比我以前试用过的couchdb要快得多,基本10分钟以下可以解决。补上一句,观察过程中mongodb都远算不上是CPU杀手。

GridFS

gridfs是mongodb一个很有趣的类似文件系统的东西,它可以用一大块文件空间来存放大量的小文件,这个对于存储web2.0网站中常见的大量小文件(如大量的用户头像)特别有效。使用起来也很方便,基本上跟一般的文件系统类似。

用合适的数据库做适合的事情

mongodb的文档里提到的user case包括实时分析、logging、全文搜索,国内也有人使用mongodb来存储分析网站日志,但我认为mongodb用来处理有一定规模的网站日志其实并不合适,最主要的就是它占空间过于虚高,原来1G的日志数据它可以存成几个G,如此下去,一个硬盘也存不了几天的日志。另一方面,数据量大了肯定要考虑sharding,而mongodb的sharding到现在为止仍不太成熟。由于日志的不可更新性的,往往只需APPEND即可,又因为对日志的操作往往只集中于一两列,所以最合适作为日志分析的还是列存储型的数据库,特别是像infobright那样的为数据仓库而设计的列存储数据库。

由于mongodb不支持事务操作,所以事务要求严格的系统(如果银行系统)肯定不能用它。

MongoDB分布式复制

一、主从配置(Master Slave)

    主从数据库需要两个数据库节点即可,一主一从(并不一定非得两台独立的服务器,可使用--dbpath参数指定数据库目录)。一个从节点可以有多个主节点,这种情况下,local.sources中会有多条配置信息。一台服务器可以同时即为主也为从。如果一台从节点与主节点不同步,比如从节点的数据更新远远跟不上主节点或者从节点中断之后重启但主节点中相关的数据更新日志却不可用了。这种情况下,复制操作将会终止,需要管理者的介入,看是否默认需要重启复制操作。管理者可以使用{ resync:1} 命令重启复制操作,可选命令行参数 --autoresync可使从节点在不同步情况发生10秒钟之后,自动重启复制操作。如果指定了--autoresync参数,从节点在10分钟以内自动重新同步数据的操作只会执行一次。
--oplogSize命令行参数(与--master一同使用)配置用于存储给从节点可用的更新信息占用的磁盘空间(M为单位),如果不指定这个参数,默认大小为当前可用磁盘空间的5%(64位机器最小值为1G,32位机器为50M)。
   
二、互为主从(Replica Pairs
    数据库自动协调某个时间点上的主从关系。开始的时候,数据库会判断哪个是从哪个是主,一旦主服务器负载过高,另一台就会自动成为主服务器。
remoteserver组中的其他服务器host,可加:port指定端口。
arbiterserver 仲裁(arbiter )的host,也可指定端口。仲裁是一台mongodb服务器,用于协助判断某个时间点上的数据库主从关系。如果同组服务器在同一个交换机或相同的ec2可用区域内,就没必要使用仲裁了。如果同组服务器之间不能通信,可是使用运行在第三方机器上的仲裁,使用“抢七”方式有效地敲定主服务器,也可不使用仲裁,这样所有的服务器都假定是主服务器状态,可通过命令人工检测当前哪台数据库是主数据库:
$ ./mongo 
> db.$cmd.findOne({ismaster:1});
{ "ismaster" : 0.0 , "remote" : "192.168.58.1:30001" , "ok" : 1.0 } 
一致性:故障转移机制只能够保障组中的数据库上的数据的最终一致性。如果机器L是主服务器,然后挂了,那么发生在它身上的最后几秒钟的操作信息就到达不了机器R,那么机器R在机器L恢复之前是不能执行这些操作的。
安全性:同主从的操作相同。
数据库服务器替换。当一台服务器失败了,系统能自动在线恢复。但当一台机器彻底挂了,就需要替换机器,而替换机器一开始是没有数据的,怎么办?以下会解释如何替换一组服务器中的一台机器。

MongoDB语法与现有关系型数据库SQL语法比较

MongoDB语法                                  MySql语法

db.test.find({'name':'foobar'})<==> select * from test where name='foobar'

db.test.find()                            <==> select *from test

db.test.find({'ID':10}).count()<==> select count(*) from test where ID=10

db.test.find().skip(10).limit(20)<==> select * from test limit 10,20

db.test.find({'ID':{$in:[25,35,45]}})<==> select * from test where ID in (25,35,45)

db.test.find().sort({'ID':-1})  <==> select * from test order by IDdesc

db.test.distinct('name',{'ID':{$lt:20}})  <==> select distinct(name) from testwhere ID<20

 

db.test.group({key:{'name':true},cond:{'name':'foo'},reduce:function(obj,prev){prev.msum+=obj.marks;},initial:{msum:0}})  <==> select name,sum(marks) from testgroup by name

 

db.test.find('this.ID<20',{name:1})  <==> select name from test whereID<20

 

db.test.insert({'name':'foobar','age':25})<==>insertinto test ('name','age') values('foobar',25)

 

db.test.remove({})                        <==> delete * from test

db.test.remove({'age':20})            <==> delete test where age=20

db.test.remove({'age':{$lt:20}})   <==> elete test where age<20

db.test.remove({'age':{$lte:20}})  <==> delete test where age<=20

db.test.remove({'age':{$gt:20}})  <==> delete test where age>20

db.test.remove({'age':{$gte:20}})<==> delete test where age>=20

db.test.remove({'age':{$ne:20}})  <==> delete test where age!=20

 

db.test.update({'name':'foobar'},{$set:{'age':36}})<==> update test set age=36 where name='foobar'

db.test.update({'name':'foobar'},{$inc:{'age':3}})<==> update test set age=age+3 where name='foobar'

 

Mongodb亿级数据量的性能测试

进行了一下Mongodb亿级数据量的性能测试,分别测试如下几个项目:
(所有插入都是单线程进行,所有读取都是多线程进行)
1) 普通插入性能 (插入的数据每条大约在1KB左右)
2) 批量插入性能 (使用的是官方C#客户端的InsertBatch),这个测的是批量插入性能能有多少提高
3) 安全插入功能 (确保插入成功,使用的是SafeMode.True开关),这个测的是安全插入性能会差多少
4) 查询一个索引后的数字列,返回10条记录(也就是10KB)的性能,这个测的是索引查询的性能
5) 查询两个索引后的数字列,返回10条记录(每条记录只返回20字节左右的2个小字段)的性能,这个测的是返回小数据量以及多一个查询条件对性能的影响
6) 查询一个索引后的数字列,按照另一个索引的日期字段排序(索引建立的时候是倒序,排序也是倒序),并且Skip100条记录后返回10条记录的性能,这个测的是Skip和Order对性能的影响
7) 查询100条记录(也就是100KB)的性能(没有排序,没有条件),这个测的是大数据量的查询结果对性能的影响
8) 统计随着测试的进行,总磁盘占用,索引磁盘占用以及数据磁盘占用的数量

 

MongoDB CEO:NoSQL的大数据量处理能力

为MongoDB提供技术支持的10gen公司CEO凯文-赖安Dwight Merriman说:“我们公司成立于3月29日,我认为我们选择的不是一个缝隙市场,相反,我认为我们会慢慢改变企业用户市场。现在我们可以看到,MongoDB.org网站每月的下载量达到了3万次,而几个月前,下载量还为零”。

 

10gen公司CEO Dwight Merriman

MongoDB的名字源自一个形容词humongous(巨大无比的),在向上扩展和快速处理大数据量方面,它会损失一些精度,在旧金山举行的MondoDB大会上,Merriman说:“你不适宜用它来处理复杂的金融事务,如证券交易,数据的一致性可能无法得到保证”。

NoSQL数据库都被贴上不同用途的标签,如MongoDB和CouchDB都是面向文档的数据库,但这并不意味着它们可以象JSON(JavaScript ObjectNotation,JavaScript对象标记)那样以结构化数据形式存储文本文档。

JSON被认为是XML的代替品,它是一个轻量级的,基于文本交换数据的标准,和XML一样具有人类易读的特性。简单的JSON数据结构叫做对象,可能包括多种数据类型,如整型(int),字符串(string),数组(array),日期(date),对象(object)和字节数组(bytearray)。

面向文档的数据库与关系数据库有着显著的区别,面向文档的数据库用一个有组织的文件来存储数据,而不是用行来存储数据,在MongoDB中,一组文档被看作是一个集合,在关系数据库中,许多行的集合被看作是一张表。

但同时它们的操作又是类似的,关系数据库使用select,insert,update和delete操作表中的数据,面向文档的数据库使用query,insert,update和remove做意义相同的操作。

MongoDB中对象的最大尺寸被限制为4MB,但对象的数量不受限制,MongoDB可以通过集群加快操作的执行速度,当数据库变得越来越大时,可以向集群增加服务器解决性能问题。

Wordnik工程副总裁Tony Tam说他的公司有5百万个文档,以前保存在MySQL数据库中,大约有1.5TB,一个月前迁移到MongoDB上了,Wordnik专门收集所有单词的定义和信息,因此数据量是非常大的,迁移到MongoDB后,Tony Tam说他感到更放心。

Tam说使用MySQL数据库时,Wordnik项目一直都象是在颠簸的路上前行,数据表的冻结时间有时甚至超过了10秒,这是任何人都不能容忍的。每天会有大约200个新单词出现,我们要负责收集,并要向数据库增加1500个例子显示它们的用法,我们希望写入数据库的时间只需要1秒。Tam说:“我们不关心一致性,前后两个用户的查询结果不一定非得保持一致,我们本来就是时刻在做着更新,这一点我们无法保证”。

Wordnik系统就象是一个庞大的在线词典,有很多人同时在线查询,但同时我们也在做更新,使用MongoDB后,我们可以保持高速添加数据,不用担心数据库会出现堵塞。Tam在MondoDB大会上曾做过一个题为“Wordnik:从MySQL到MongoDB”的演讲,他说他们公司只花了一天时间就从MySQL迁移到MongoDB上了。

延伸阅读

 

Mongo是一个高性能,开源,无模式的文档型数据库,它在许多场景下可用于替代传统的关系型数据库或键/值存储方式。Mongo使用C++开发,提供了以下功能:

◆面向集合的存储:适合存储对象及JSON形式的数据。

◆动态查询:Mongo支持丰富的查询表达式。查询指令使用JSON形式的标记,可轻易查询文档中内嵌的对象及数组。

◆完整的索引支持:包括文档内嵌对象及数组。Mongo的查询优化器会分析查询表达式,并生成一个高效的查询计划。

◆查询监视:Mongo包含一个监视工具用于分析数据库操作的性能。

◆复制及自动故障转移:Mongo数据库支持服务器之间的数据复制,支持主-从模式及服务器之间的相互复制。复制的主要目标是提供冗余及自动故障转移。

◆高效的传统存储方式:支持二进制数据及大型对象(如照片或图片)。

◆自动分片以支持云级别的伸缩性(处于早期alpha阶段):自动分片功能支持水平的数据库集群,可动态添加额外的机器。

MongoDB的主要目标是在键/值存储方式(提供了高性能和高度伸缩性)以及传统的RDBMS系统(丰富的功能)架起一座桥梁,集两者的优势于一身。根据官方网站的描述,Mongo适合用于以下场景:

◆网站数据:Mongo非常适合实时的插入,更新与查询,并具备网站实时数据存储所需的复制及高度伸缩性。

◆缓存:由于性能很高,Mongo也适合作为信息基础设施的缓存层。在系统重启之后,由Mongo搭建的持久化缓存层可以避免下层的数据源过载。

◆大尺寸,低价值的数据:使用传统的关系型数据库存储一些数据时可能会比较昂贵,在此之前,很多时候程序员往往会选择传统的文件进行存储。

◆高伸缩性的场景:Mongo非常适合由数十或数百台服务器组成的数据库。Mongo的路线图中已经包含对MapReduce引擎的内置支持。

◆用于对象及JSON数据的存储:Mongo的BSON数据格式非常适合文档化格式的存储及查询。

自然,MongoDB的使用也会有一些限制,例如它不适合:

◆高度事务性的系统:例如银行或会计系统。传统的关系型数据库目前还是更适用于需要大量原子性复杂事务的应用程序。

◆传统的商业智能应用:针对特定问题的BI数据库会对产生高度优化的查询方式。对于此类应用,数据仓库可能是更合适的选择。

◆需要SQL的问题

MongoDB支持OS X、Linux及Windows等操作系统,并提供了Python,PHP,Ruby,Java及C++语言的驱动程序,社区中也提供了对Erlang及.NET等平台的驱动程序。(

 

漫画:MongoDB身上的优势和劣势

SQL or NoSQL?That’s a question!SQL 与 NoSQL 的争论从来没有停息过,但其实任何一种技术都不会是适合一切应用场景的,重要的是你要充分了解自己的需求,再充分了解你要选择的技术的优劣。

下面是一个关于 MongoDB 优缺点的列表,希望对打算使用 MongoDB 的同学,能有一些作用:

优势:

快速!(当然,这和具体的应用方式有关,通常来说,它比一般的关系型数据库快5位左右。)

很高的可扩展性 – 轻轻松松就可实现PB级的存储(但是可能我们并不需要PB级的存储,10TB可能就够了)

他有一个很好的 replication 模式 (replica sets)

有很完善的Java API

他的存储格式是Json的,这对Java来说非常好处理,对javascirpt亦然。

运维起来非常方便,你不用专门为它安排一个管理员。

它有一个非常活跃的社区(我提出的一个bug在20分钟内就能得到修复。多谢Elliot)

他的版本控制非常清楚。

MongoDB 背后的公司(10gen)已经准备好了明天在 MongoDB 上面的投入的资金了。

劣势

应用经验缺乏,我们都没有相关NoSQL 产品的使用经验。

项目相对来说还比较新。

和以往的存储相比,数据的关系性操作不再存在。

另附趣图一张:

 

详细分析Memcached缓存与Mongodb数据库的优点与作用

本文详细讲下Memcached和Mongodb一些看法,以及结合应用有什么好处,希望看到大家的意见和补充。

  Memcached

  Memcached的优势我觉得总结下来主要体现在:

  1) 分布式。可以由10台拥有4G内存的机器,构成一个40G的内存池,如果觉得还不够大可以增加机器,这样一个大的内存池,完全可以把大部分热点业务数据保存进去,由内存来阻挡大部分对数据库读的请求,对数据库释放可观的压力。

  2) 单点。如果Web服务器或App服务器做负载均衡的话,在各自内存中保存的缓存可能各不相同,如果数据需要同步的话,比较麻烦(各自自己过期,还是分发数据同步?),即使数据并不需要同步,用户也可能因为数据的不一致而产生用户体验上的不友好。

  3) 性能强。不用怀疑和数据库相比确实是,根源上还是内存的读写和磁盘读写效率上几个数量级的差距。有的时候我们在抱怨数据库读写太差的情况下可以看看磁盘的IO,如果确实是瓶颈的话装啥强劲的数据库估计也档不了,强不强无非是这个数据库多少充分的利用了内存。

  但是也不太建议在任何情况下使用Memcached替代任何缓存:

  1) 如果Value特别大,不太适合。因为在默认编译下Memcached只支持1M的Value(Key的限制到不是最大的问题)。其实从实践的角度来说也 不建议把非常大的数据保存在Memcached中,因为有序列化反序列化的过程,别小看它消耗的CPU。说到这个就要提一下,我一直觉得 Memcached适合面向输出的内容缓存,而不是面向处理的数据缓存,也就是不太适合把大块数据放进去拿出来处理之后再放进去,而是适合拿出来就直接给输出了,或是拿出来不需要处理直接用。

  2) 如果不允许过期,不太适合。Memcached在默认情况下最大30天过期,而且在内存达到使用限制后它也会回收最少使用的数据。因此,如果我们要把它当 作static变量的话就要考虑到这个问题,必须有重新初始化数据的过程。其实应该这么想,既然是缓存就是拿到了存起来,如果没有必定有一个重新获取重新缓存的过程,而不是想着它永远存在。

  在使用Memcached的过程中当然也会有一些问题或者说最佳实践:

  1) 清除部分数据的问题。Memcached只是一个Key/Value的池,一个公共汽车谁都可以上。我觉得对于类似的公共资源,如果用的人都按照自己的规 则来的话很容易出现问题。因此,最好在Key值的规范上上使用类似命名空间的概念, 每一个用户都能很明确的知道某一块功能的Key的范围,或者说前缀。带来的好处是我们如果需要清空的话可以根据这个规范找到我们自己的一批Key然后再去 清空,而不是清空所有的。当然有人是采用版本升级的概念,老的Key就让它过去吧,到时候自然会清空,这也是一种办法。不过Key有规范总是有好处的,在 统计上也方便一点。

  2) Value的组织问题。也就是说我们存的数据的粒度,比如要保存一个列表,是一个保存在一个键值还是统一保存为一个键值,这取决于业务。如果粒度很小的话最好是在获取的时候能批量获取,在保存的时候也能批量保存。对于跨网络的调用次数越少越好,可以想一下,如果一个页面需要输出100行数据,每一个数据都需要获取一次,一个页面进行上百次连接这个性能会不会成问题。

  那么Memcached主要用在哪些功能上呢?

  其实我觉得平时能想到在内存中做缓存的地方我们都可以考虑下是不是可以去适用分布式缓存,但是主要的用途还是用来在前端或中部挡一下读的需求来释放Web服务器App服务器以及DB的压力。

下面讲讲Mongodb。

Mongodb

  Mongodb是一款比较优良的非关系型数据库的文档型的数据库。它的优势主要体现在:

  1) 开源。意味着即使我们不去改也可以充分挖掘它,MS SQL除了看那些文档,谁又知道它内部如何实现。

  2) 免费。意味着我们可以在大量垃圾服务器上装大量的实例,即使它性能不怎么高,也架不住非常多的点啊。

  3) 性能高。其它没比较过,和MS SQL相比,同样的应用(主要是写操作)一个撑500用户就挂了,一个可以撑到2000。在数据量上到百万之后,即使没索引,MS SQL的插入性能下降的也一塌糊涂。其实任何事物都有相对性的,在变得复杂变得完善了之后会牺牲一部分的性能,MS SQL体现的是非常强的安全性数据完整性,这点是Mongodb办不到的。

  4) 配置简单并且灵活。在生产环境中对数据库配置故障转移群集和读写分离的数据库复制是很常见的需求,MS SQL的配置繁琐的步骤还是很恐怖的,而Mongodb可以在五分钟之内配置自己所需要的故障转移组,读写分离更是只需要一分钟。灵活性体现在,我们可以配置一个M一个S,两个M一个S(两个M写入的数据会合并到S上供读取),一个M两个S(一个M写入的数据在两个S上有镜像),甚至是多个M多个S(理论上可以创建10个M,10个S,我们只需要通过轮询方式随便往哪个M上写,需要读的时候也可以轮训任意一个S,当然我们要知道不可能保证在同一时间所有的 S都有一致的数据)。那么也可以配置两个M的对作为一套故障转移群集,然后这样的群集配置两套,再对应两个S,也就是4个M对应2个S,保证M点具有故障 转移。

  5) 使用灵活。在之前的文章中我提到甚至可以通过SQL到JS表达式的转换让Mongodb支持SQL语句的查询,不管怎么说Mongodb在查询上还是很方便的。

  之前也说过了,并不是所有数据库应用都使用采用Mongodb来替代的,它的主要缺点是:

  1) 开源软件的特点:更新快,应用工具不完善。由于更新快,我们的客户端需要随着它的更新来升级才能享受到一些新功能,更新快也意味着很可能在某一阶段会缺乏某个重要功能。另外我们知道MS SQL在DEV/DBA/ADM多个维度都提供了非常好的GUI工具对数据库进行维护。而Mongodb虽然提供了一些程序,但是并不是非常友好。我们的 DBA可能会很郁闷,去优化Mongodb的查询。

  2) 操作事务。Mongodb不支持内建的事务(没有内建事务不意味着完全不能有事务的功能),对于某些应用也就不适合。不过对于大部分的互联网应用来说并不存在这个问题。

  在使用Mongodb的过程中主要遇到下面的问题:

  1) 真正的横向扩展?在使用Memcached的过程中我们已经体会到这种爽了,基本可以无限的增加机器来横向扩展,因为什么,因为我们是通过客户端来决定键值保存在那个实例上,在获取的时候也很明确它在哪个实例上,即使是一次性获取多个键值,也是同样。而对于数据库来说,我们通过各种各样的方式进行了 Sharding,不说其它的,在查询的时候我们根据一定的条件获取批量的数据,怎么样去处理?比如我们按照用户ID去分片,而查询根本不在乎用户ID, 在乎的是用户的年龄和教育程度,最后按照姓名排序,到哪里去取这些数据?不管是基于客户端还是基于服务端的Sharding都是非常难做的,并且即使有了 自动化的Sharding性能不一定能有保障。最简单的是尽量按照功能来分,再下去就是历史数据的概念,真正要做到实时数据分散在各个节点,还是很困难。

  2) 多线程,多进程。在写入速度达不到预期的情况下我们多开几个线程同时写,或者多开几个Mongodb进程(同一机器),也就是多个数据库实例,然后向不同 的实例去写。这样是否能提高性能?很遗憾,非常有限,甚至可以说根本不能提高。为什么使用Memcached的时候多开线程可以提高写入速度?那是因为内 存数据交换的瓶颈我们没达到,而对于磁盘来说,IO的瓶颈每秒那么几十兆的是很容易达到的,一旦达到这个瓶颈了,无论是开多少个进程都无法提高性能了。还 好Mongodb使用内存映射,看到内存使用的多了,其实我对它的信心又多了一点(内存占用多了我觉得CPU更容易让它不闲着),怕就怕某个DB不使用什 么内存,看着IO瓶颈到了,内存和CPU还是吃不饱。

  Memcached和Mongodb的配合

  其实有了Memcached和Mongodb我们甚至可以让80%以上的应用摆脱传统关系型数据库。我能想到它们其实可以互相配合弥补对方的不足:

  Memcached适合根据Key保存Value,那么有的时候我们并不知道需要读取哪些Key怎么办呢?我在想是不是可以把Mongodb或 说数据库当作一个原始数据,这份原始数据中分为需要查询的字段(索引字段)和普通的数据字段两部分,把大量的非查询字段保存在Memcached中,小粒 度保存,在查询的时候我们查询数据库知道要获取哪些数据,一般查询页面也就显示20-100条吧,然后一次性从Memcached中获取这些数据。也就是 说,Mongodb的读的压力主要是索引字段,而数据字段只是在缓存失效的时候才有用,使用Memcached挡住大部分实质数据的查询。反过来说,如果我们要清空Memcached中的数据也知道要清空哪些Key。

 

 

MongoDB 文档阅读笔记 —— 优雅的 NoSQL

NoSQL 数据库在上年炒得很热,于是我也萌生了使用 NoSQL 数据库写一个应用的想法。首先来认识一下 NoSQL。NoSQL 是一个缩写,含义从最初的 No-SQL 到现在已经成为了 Not-Only-SQL。确实后面一种解释比较符合 NoSQL 的使用场景。

现在网络上被人所知的 NoSQL 数据库可以在这个网页(http://nosql-database.org)看到。这个列表林林总总一大堆,要选择哪个数据库入手呢?

1. 选择非关系数据库

在我关注的 Web 领域,特别是 Ruby on Rails 社区,比较多提到的是这几个数据库:

Cassandra , apache基金会下的非关系数据库。早前一段时间传言 Twitter 要用 Cassandra 替代 Mysql,一时间坊间流传“NoSQL 要革 SQL 的命了!”。不过Twitter 博客澄清,Twitter 只是在部分领域使用 Cassandra,存放 Tweets 的主数据库依然是 MySQL。

      MongoDB10gen 公司的开源非关系数据库产品,可以选择他们公司的商业支持。RoR 相关的插件挺多。

CouchDB,另一个apache基金会下的非关系数据库。

Redis,特点是运行在内存中,速度很快。相比于用来持久化数据,也许更接近于 memcached 这样的缓存系统,或者用来实现任务队列。(比如resque

在这些候选名单中我选择了MongoDB。因为它最近在 RoR 社区中的露脸率比较高,网页文档完善,并且项目主页的设计也不错

在陈述 MongoDB 的特性之前,还是给第一次接触 NoSQL 的人提个醒:不要意图用 NoSQL 全盘取代 SQL 数据库。非关系数据库的出现不是为了取代关系数据库。具体的说,MongoDB 并不支持复杂的事务,只支持少量的原子操作,所以不适用于“转帐”等对事务和一致性要求很高的场合。而 MongoDB 适合什么场合,请继续阅读。

2. 文档型数据库初探

关系数据库比如 MySQL,通常将不同的数据划分为一个个“表”,表的数据是按照“行”来储存的。而关系数据库的“关系”是指通过“外键”将表间或者表内的数据关联起来。比如文章-评论 的一对多关系可以用这样的表来实现:

posts(id, author_id, content, ... )
comments(id, name, email, web_site, content, post_id)
实现关联的关键就是 comments 表的最后一个 post_id 字段,将 comment 数据的 post_id 字段设为评论目标文章的 id 值,就可以用 SQL 语句进行相关查询(假设要查的文章 id 是 1):
SELECT * FROM comments WHERE post_id = 1;
相对于关系数据库的行式储存和查询,MongoDB 作为一个文档型数据库,可以支持更具层次感的数据。上面举的 文章-评论 结构,在 MongoDB 里面可以这样设计。
{
  
  _id : ObjectId(...),
  author : 'Rei',
  content : 'content text',
  comments : [ { name : 'Asuka'
                 email : '...',
                 web_site : '...',
                 content : 'comment text'} ]
} 

comments 项是内嵌在 post 项中的(作为数组)。在 MongoDB 中,一个数据项叫做 Document,一个文档嵌入另一个文档(comment 嵌入 post)叫做 Embed,储存一系列文档的地方叫做 Collections。顺便一提,MongoDB 中也提供类似 SQL 数据库中的表间关联,叫做 Reference。 

3. 用文档型数据库储存文档

可以看到,文档性数据库从储存的数据项上就跟 SQL 数据库不同。在 MongoDB 中,文档是以 BSON 格式(类似 JSON)储存的,可以支持丰富的层次的结构。由于数据结构的表达能力更强,用 MongoDB 储存文档型数据可以比 SQL 数据库更直观和高效。

3.1简化模式设计

在 SQL 数据库中,为表达数据的从属关系,通常要将表间关系分为 one-to-one,one-to-many,many-to-many 等模式进行设计,这通常会需要很多链接表的辅助。在MongoDB 中,如果关联文档体积较小,固定不变,并且与另一文档是主从关系,那么通常可以嵌入(Embed)主文档。

常见情景:评论、投票点击数据、Tag。

这类场景的有时单个数据项体积很小,但是数量巨大,与之相应的是查询成本也会上升。如果将这些小数据嵌入所属文档,在查询主文档时一并提取,查询效率要比 SQL 高,后者通常需要开支较大的 JOIN 查询。并且根据文档介绍,每个文档包括 Embed 部分在物理硬盘上都是储存在同一区域的,IO 部分也会比 SQL 数据库快。(注,MongoDB 有单文档大小限制)

3.2动态的文档模式

MongoDB 中的文档其实是没有模式的,不像 SQL 数据库那样在使用前强制定义一个表的每个字段。这样可以避免对空字段的无谓开销。

例如两个用户的联系信息,A 用户带有email 不带 url,B 用户带有 url 不带 email。在 SQL 数据库中需要为两个数据项都提供 email 段和 url 段,而在MongoDB 中可以这样保存:

[ { name : 'A', email : 'A email address' }, { :name : 'B', url : 'B url address' } ]

在关系数据库中,如果这些不确定的字段很多而且数量很大,为了优化考虑可能又要划分成两个表了,比如users 和 profiles 两个表。在 MongoDB 中,如果这一类不确定信息确实是属于同一文档的,那么可以轻松的放在一起,因为并不需要预先定义模式,也不会有空字段的开销。

不过因为要支持这样的动态性,并且为了性能考虑进行预先分配硬盘空间,数据外的开销也会带来磁盘占用。我还未实测实际中开销有多大,也许未来不久我会写一个应用测试。

4.MongoDB 另外一些特点

4.1JSON 文档式查询

MongoDB 的查询语言看起来是这样的:

>db.users.find( { x : 3, y : "abc" } ).sort({x:1});

这是在 MongoDB 内置的JavaScript 控制台上的查询,表示在名为 users 的 Collections 中查找 x = 3,y = “abc” 的文档,并且以 x 递增的顺序返回数据。

JSON 文档式查询会让写惯应用层代码的开发者眼前一亮,但对于精通 SQL 查询的关系数据库管理员来说就是一个新的挑战了。

4.2对分布式的支持

MongoDB 对大型网站的最大吸引力也许来源于其对分布式部署的支持。当今互联网最流行的数据库 MySQL 在网站扩大到一定规模之后就会遇到扩展瓶颈,解决方案通常是分表分库、配置主从数据库。很多互联网开拓者前仆后继的为数据库扩展性奋斗,留下了一页页的宝贵经验。

既然年复一年的有人为同一个问题奋斗,为什么不将这些问题在数据库层面就解决了呢?而MongoDB 的优势之一就是内建对分布式的支持。

 

不过我没有组建数据库群集的需求,所以还未阅读这方面的文档。

5. 总结

没有银弹,这个教诲已经提过太多。由于缺乏对事务的支持,MongoDB 不太适用于金融等行业的关键部分(我想也没有这个人群来看我博客吧)。但对于现在要应对海量细信息的 web 网站来说,MongoDB 可能恰好出现在了正确的时代。NoSQL 的无模式,能让网站开发的迭代更轻盈;MongoDB 对分布式的支持,可以缓解网站快速成长时在数据库端的瓶颈疼痛。

不要激进的用 NoSQL 替代所有 MySQL应用,激进的 NoSQL 化只会像5年前的 all rewrite by RoR 浪潮一样,耗费不必要的精力。但在新项目开发之初,可以考虑是否更适合使用 NoSQL。而我,已经打算在下一个 web 项目里面使用 MongoDB。

 

通过 MongoDB 推动 NoSQL(第1部分)

自从 2000 年宣布 Microsoft.NET Framework 并在 2002 年首次发行以来的过去近十年中,.NET开发人员一直努力适应 Microsoft 推出的各种新事物。但这似乎还不够,“社区”(包含所有开发人员,无论他们是否每天都使用.NET)也开始行动,创造出更多的新事物来填补Microsoft 未覆盖到的空白,对您而言这可能是制造混乱和干扰。

在 Microsoft 的支持 范围之外,该社区所酝酿出的“新”事物之一就是 NoSQL 运动,一组开发人员公开质疑将所有数据存储于某种形式的关系数据库系统的这种观念。表、行、列、主键、外键约束、关于null 的争论以及有关主键是否应该为自然键或非自然键的辩论……还有什么是神圣不可侵犯的?

在本文及其后续文章中,我将探讨NoSQL 运动所倡导的主要工具之一:MongoDB,根据 MongoDB 网站的陈述,该工具的名称源自于“humongous”(并不是我杜撰的)。我基本上会讨论到与 MongoDB 相关的方方面面:安装、浏览以及在.NET Framework 中使用 MongoDB。其中包括其提供的 LINQ 支持;在其他环境(桌面应用程序和Web 应用程序及服务)中使用MongoDB;以及如何设置MongoDB,以免 Windows 生产管理员向您提出严重抗议。

问题(或者,为何我要再次关注?)

在深入了解MongoDB 之前,读者自然要问为什么.NET Framework 开发人员应该牺牲接下来宝贵的大约半小时时间继续待在电脑前阅读本文。毕竟,SQLServer 有免费、可再发行的Express Edition,提供比企业或数据中心绑定的传统关系数据库更精简的数据存储方案,而且毫无疑问,还可以使用大量工具和库来轻松访问SQL Server 数据库,其中包括Microsoft 自己的 LINQ 和实体框架。

但问题在于,关系模型(指关系模型本身)的优点也是其最大的缺点。大多数开发人员(无论是.NET、Java 还是其他开发人员都在此列)在经历短短几年的开发工作之后,就会一一痛诉这种表/行/列的“方正”模型如何不能令其满意。尝试对分层数据进行建模的举动甚至能让最有经验的开发人员完全精神崩溃,类似情况不甚枚举,因此Joe Celko 还写过一本书《SQLfor Smarties, Third Edition》(Morgan-Kaufmann,2005),其中完全是关于在关系模型中对分层数据建模的概念。如果在此基础之上再增加一个基本前提:关系数据库认为数据的结构(数据库架构)不灵活,则尝试支持数据的临时“添加”功能将变得十分困难。(快速回答下面的问题:你们之中有多少人处理过包含一个Notes 列(乃至 Note1、Note2、Note3……)的数据库?)

NoSQL 运动中没有任何人会说关系模型没有优点,也没有人会说关系数据库将会消失,但过去二十年开发人员生涯的一个最基本的事实是,开发人员经常将数据存储到本质上并非关系模型(有时甚至与这种模型相去甚远)的关系数据库中。

面向文档的数据库便是用于存储“文档”(这是一些紧密结合的数据集合,通常并未关联到系统中的其他数据元素),而非“关系”。例如,博客系统中的博客条目彼此毫无关联,即使出现某一篇博客确实引用到另一篇博客的情况,最常用的关联方法也是通过超链接(旨在由用户浏览器解除引用),而非内部关联。对本博客条目的评论完全局限于本博客条目的内部范围,不管评论的是什么博客条目,极少有用户想查看包含所有评论的内容集合。

此外,面向文档的数据库往往在高性能或高并发性环境中表现突出:MongoDB专门迎合高性能需求,而它的近亲CouchDB 则更多的是针对高并发性的情况。两者都放弃了对多对象事务的支持,也就是说,尽管它们支持在数据库中对单个对象进行的并发修改,但若尝试一次性对多个对象进行修改,将会在一小段时间内看到这些修改正依序进行。文档以“原子方式”更新,但不存在涉及多文档更新的事务概念。这并不意味着MongoDB 没有任何稳定性,只是说MongoDB 实例与 SQL Server 实例一样不能经受电源故障。需要原子性、一致性、隔离性和持久性(ACID) 完整要素的系统更适合采用传统的关系数据库系统,因此关键任务数据很可能不会太快出现在MongoDB 实例内,但Web 服务器上的复制数据或缓存数据可能要除外。

一般来说,若应用程序及组件需要存储可快速访问且常用的数据,则采用MongoDB 可以取得较好效果。网站分析、用户首选项和设置(以及包含非完全结构化数据或需采用结构灵活的数据的任何系统类型)都是采用MongoDB 的自然之选。这并不意味着MongoDB 不能作为操作型数据的主要数据存储库;只是说MongoDB 能在传统 RDBMS 所不擅长的领域内如鱼得水,另外它也能在大量其他适合的领域内大展拳脚。

入门

前面提到过,MongoDB是一款开源软件包,可通过 MongoDB 网站 mongodb.com 轻松下载。在浏览器中打开该网站应该就能找到Windows 可下载二进制包的链接,请在页面右侧查找“Downloads”链接。另外,如果更愿意使用直接链接,请访问 mongodb.org/display/DOCS/Downloads。截至本文撰写之时,其稳定版本为发行版1.2.4。它其实就是一个.zip 文件包,因此相对而言,其安装过程简单得可笑:只需在任何想要的位置解压zip 包的内容。

没开玩笑,就是这样。

该 .zip 文件解压后生成三个目录:bin、include 和 lib。唯一有意义的目录是 bin 目录,其中包含八个可执行文件。除此之外不再需要任何其他的二进制(或运行时)依赖文件,而事实上,现在只需关注其中的两个可执行文件。这两个文件分别是mongod.exe(即 MongoDB 数据库进程本身)和 mongo.exe(即命令行 Shell 客户端,其使用方法通常类似于传统的isql.exe SQL Server 命令行Shell 客户端,用于确保所有内容都已正确安装且能正常运行,并用于直接浏览数据、执行管理任务)。

验证所有内容是否正确安装的过程十分简单,只需在命令行客户端上启动mongod。默认情况下,MongoDB将数据存储在默认的文件系统路径c:\data\db,但该路径是可以配置的,方法是在命令行上通过--config 命令按名称传递一个文本文件。假设mongod 即将启动的位置存在一个名为db 的子目录,验证所有内容是否安装得当的过程很简单,如图 1 所示。

图 1 启动 mongod.exe 以验证安装是否成功

如果该目录不存在,MongoDB并不会创建它。注意在 Windows 7 界面中,当启动 MongoDB 时,会弹出常见的“该应用程序要打开端口”对话框。请确保能访问到该端口(默认情况下指27017),或者最多是难以连接到该端口。(在后面一篇文章中,我会讨论将MongoDB 投入生产环境,其中将详细论述到这一问题。)

服务器进入运行状态后,通过Shell 连接到该服务器的过程非常简单:mongo.exe应用程序启动一个命令行环境,在该环境中便可直接与服务器交互,如图 2 所示。

图 2 mongo.exe启动一个命令行环境,用于直接与服务器交互

默认情况下,Shell连接到“test”数据库。由于此处目的只是验证是否一切运行正常,因此使用test 数据库就够了。当然,在这里可以轻松地创建一些简单的示例数据以用于MongoDB,例如创建一个描述某人的快速对象。在MongoDB 中查看数据的启动过程非常简单,如图 3 所示。

图 3 创建示例数据

本质上,MongoDB 使用 JavaScript Object Notation (JSON) 作为其数据表示法,这种表示法能表现 MongoDB 的灵活性,并可说明 MongoDB 与客户端的交互方式。在内部,MongoDB 以 BSON(JSON的二进制超集)存储数据,目的是简化存储和索引。JSON 保留了MongoDB 的首选输入/输出格式,并且通常是在 MongoDB 网站和 wiki 上使用的文档格式。如果不熟悉 JSON,最好是在“深陷”MongoDB 之前充一下电。(开个玩笑。)同时,查看 mongod 用来存储数据的目录,您会发现其中一对以“test”命名的文件。

言归正传,该编写一些代码了。退出Shell 简单得只需键入“exit”,而关闭服务器也只需在窗口中按 Ctrl+C 或直接关闭窗口:服务器捕获到关闭信号并正确关闭所有内容,然后退出进程。

MongoDB 的服务器(以及 Shell,尽管它微不足道)是用地道的 C++ 应用程序(还记得吗?)编写的,因此访问该服务器需要使用某种 .NET Framework 驱动程序,此类驱动程序知道如何通过打开的套接字进行连接以向服务器输送命令和数据。MongoDB 程序包中并未绑定 .NET Framework 驱动程序,但有幸的是,社区提供了一个,此处的“社区”指的是名叫 Sam Corder 的开发人员,他构建了一个 .NET Framework 驱动程序以及 LINQ 支持来访问 MongoDB。他的作品同时以源代码形式和二进制形式提供,位于github.com/samus/mongodb-csharp。可以从该页面下载二进制文件(查找页面右上角),也可以下载源代码,然后自行编译。无论采取哪种方式,都会产生两个程序集:MongoDB.Driver.dll 和 MongoDB.Linq.dll。通过向对应项目的“引用”节点快速添加引用后,就可以使用 .NET Framework 了。

编写代码

从根本上来说,打开与正在运行的MongoDB 服务器的连接,同打开与任何其他数据库的连接没有太大差别,如图 4 所示。

图 4 打开与 MongoDB 服务器的连接

 

using System;
using MongoDB.Driver; 
 
namespace ConsoleApplication1
{
   
  class Program
  {
   
    static void Main(string[] args)
    {
   
      Mongo db = new Mongo();
      db.Connect(); //Connect to localhost on the default port
      db.Disconnect();
    }
  }
}

查找先前创建的对象并不难,只是与以前.NET Framework 开发人员使用过的方法有所不同而已(请参阅图 5)。

图 5 查找创建的 mongo 对象

 

using System;
using MongoDB.Driver; 
 
namespace ConsoleApplication1
{
   
  class Program
  {
   
    static void Main(string[] args)
    {
   
      Mongo db = new Mongo();
      db.Connect(); //Connect to localhost on the default port.
      Database test = db.getDB("test");
      IMongoCollection things = test.GetCollection("things");
      Document queryDoc = new Document();
      queryDoc.Append("lastname", "Neward");
      Document resultDoc = things.FindOne(queryDoc);
      Console.WriteLine(resultDoc);
      db.Disconnect();
    }
  }
}

如果上述内容看起来太突然,别担心,写出这样的代码并非“一日之功”,因为 MongoDB 存储数据的方式与传统数据库是不同的。

对于初学者,请回忆一下,先前插入的数据有三个字段:firstname、lastname 和 age,这三个元素都可作为数据的检索条件。但更重要的是,存储这些数据的行(以强制方式快速完成该过程)为“test.things.save()”,这表示数据被存储在称为“things”的事物中。在 MongoDB 术语中,“things”是一个集合,不言而喻,所有数据都存储在集合中。集合中依次存储着文档,文档则存储着“键/值”对,而其中的“值”又可以是其他集合。在本例中,“things”就是存储在前面提到的 test 数据库内部的集合。

因此,获取数据的过程首先要连接到MongoDB 服务器,再连接到test 数据库,然后查找集合“things”。这就是图 5 中前四行的操作:创建一个表示连接的Mongo 对象,连接到服务器,连接到test 数据库,然后获取“things”集合。

返回集合之后,代码可通过调用FindOne 的方式发出一条查询命令来查找单个文档。但与所有数据库一样,该客户端并不想获取集合中的每一个文档,只想查找感兴趣的文档,因此需要对查询进行某种方式的约束。在MongoDB 中,该约束的实现方式是创建一个Document,其中包含字段以及要在这些字段中搜索的数据,这是一种称为示例查询(简称QBE)的概念。由于此处的目标是查找包含lastname 字段(其值设为“Neward”)的文档,因此需要创建一个仅包含一个lastname 字段(及其值)的Document,并作为参数传递给FindOne。如果查询成功,则返回另一个Document,其中包含所有相关数据(外加另一个字段);否则返回null。

顺便提一句,此描述的缩略版可简化为:

 

Document anotherResult = 
         db["test"]["things"].FindOne(
           new Document().Append("lastname", "Neward"));
       Console.WriteLine(anotherResult);

运行时,不仅会显示传入的原始值,还会显示一个新值,即一个包含ObjectId 对象的 _id 字段。这是对象的唯一标识符,是在存储新数据时由数据库自动插入的。在尝试修改此对象时,必须避免修改该字段,否则数据库会将该对象视为传入的新对象。通常,这是通过修改由查询返回的Document 来完成的:

 

anotherResult["age"] = 39;
       things.Update(resultDoc);
       Console.WriteLine(
         db["test"]["things"].FindOne(
           new Document().Append("lastname", "Neward")));

但是,您始终可以创建新的Document 实例并手动填入_id 字段来匹配ObjectId(如果这样做更合理):

 

Document ted = new Document();
       ted["_id"] = new MongoDB.Driver.Oid("4b61494aff75000000002e77");
       ted["firstname"] = "Ted";
       ted["lastname"] = "Neward";
       ted["age"] = 40;
       things.Update(ted);
       Console.WriteLine(
         db["test"]["things"].FindOne(
           new Document().Append("lastname", "Neward")));

当然,如果_id 已知,那么也可将其用作查询条件。

请注意,由于Document 被有效地非类型化(无类型),因此几乎所有内容均能以任意名称存储在字段中,包括某些核心的.NET Framework 值类型,如DateTime。如前所述,从技术角度上讲,MongoDB用于存储 BSON 数据,其中包括传统 JSON 类型(字符串、整数、布尔值、双精度和null,不过 null 仅允许用于对象,不允许用于集合)的某些扩展,例如上文提到的ObjectId、二进制数据、正则表达式以及嵌入式JavaScript 代码。我们暂时先不管后面两种类型,BSON能存储二进制数据的这种说法是指能存储任何可简化为字节数组的内容,这实际上表示MongoDB 能存储任何内容,但可能无法在该二进制BLOB 中进行查询。

下载代码示例

关于作者:

Ted Neward 是 Neward & Associates 的负责人,这是一家专门研究 .NET Framework 企业系统和 Java 平台系统的独立公司。他曾写作 100 多篇文章,是 C# 领域最优秀的专家之一并且是 INETA 发言人,著作或合著过十几本书,包括即将出版的《Professional F# 2.0》(Wrox)。他定期担任顾问和导师,请通过 ted@tedneward.com 与他联系,或通过 blogs.tedneward.com 访问其博客。

 

 

通过 MongoDB 推动 NoSQL(第2部分)

 

上一篇文章中,主要介绍了 MongoDB 的基本知识:安装、运行,以及插入和查找数据。不过,这篇文章只介绍了基本知识,所用的数据对象是简单的名称/值对。这是有道理的,因为 MongoDB 的最大优势就包括可使用相对简单的非结构化数据结构。可以肯定地说,这种数据库能存储的不只是简单的名称/值对。

在本文中,我们将通过一种略微不同的方法来研究MongoDB(或任何技术)。这个称为探索测试的过程可帮助我们发现服务器中可能存在的错误,同时可以凸显面向对象开发人员在使用MongoDB 时会遇到的常见问题之一。

前文回顾…

首先,我们要确保讨论同样的问题,还要涉及一些略微不同的新领域。让我们以一种与前一文章(http://cloud.csdn.net/a/20110321/294274.html)相比更加结构化的方式来探讨MongoDB。我们不只是创建简单的应用程序,然后进行调试,我们将采取一举两得的做法,创建探索测试。探索测试的代码段看起来像单元测试,但它们探索功能而不是尝试验证功能。

在研究一项新技术时,编写探索测试可实现几种不同的目的。其一,它们有助于发现所研究的技术在本质上是不是可以测试的(假设如下:如果难于进行探索测试,则难于进行单元测试,而这是一个很严重的问题)。其二,在所研究的技术出现新的版本时,它们可作为一种回归测试,因为它们可在旧功能不再正常工作的情况下发出警告。其三,测试应是相对小型精细的,因此,在本质上,探索测试通过基于以前用例创建新“what-if”用例,使得新技术的学习更为容易。

不过,与单元测试不同,探索测试不是随应用程序连续开发的,因此,一旦考虑所学习的技术,请将这些测试放在一旁。但不要将它们丢弃,它们还可帮助分离应用程序代码中的错误与

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值