MongoDB和Redis-NoSQL数据库-文档型-内存型

1NoSQL简述mysql

CAP(Consistency,Availabiity,Partitiontolerance)理论告诉咱们,一个分布式系统不可能知足一致性,可用性和分区容错性这三个需求,最多只能同时知足两个。关系型数据库经过把更新操做写到事务型日志里实现了部分耐用性,但带来的是写性能的降低。MongoDB等NoSQL数据库背后蕴涵的哲学是不一样的平台应该使用不一样类型的数据库,MongoDB经过下降一些特性来达到性能的提升,这在不少大型站点中是可行的。由于MongoDB是非原子性的,因此若是若是应用须要事务,仍是须要选择MySQL等关系数据库。正则表达式

NoSQL数据库,顾名思义就是打破了传统关系型数据库的范式约束。不少NoSQL数据库从数据存储的角度看也不是关系型数据库,而是key-value数据格式的hash数据库。因为放弃了关系数据库强大的SQL查询语言和事务一致性以及范式约束,NoSQL数据库在很大程度上解决了传统关系型数据库面临的诸多挑战。redis

在社区中,NoSQL是指“notonly sql”,其特色是非关系型,分布式,开源,可水平扩展,模式自由,支持replication,简单的API,最终一致性(相对于即时一致性,最终一致性容许有一个“不一致性窗口”,但能保证最终的客户都能看到最新的值)。sql

2MongoDB简介mongodb

mongo取自“humongous”(海量的),是开源的文档数据库──nosql数据库的一种。shell

MongoDB是一种面向集合(collection)的,模式自由的文档(document)数据库。数据库

面向集合是说数据被分红集合的形式,每一个集合在数据库中有唯一的名称,集合能够包含不限数目的文档。除了模式不是预先定义好的,集合与RDBMS中的表概念相似,虽然两者并非彻底对等。数据库和集合的建立是“lazy”的,即只有在第一个document被插入时集合和数据库才真正建立——这时在磁盘的文件系统里才能看见。编程

模式自由是说数据库不须要知道存放在集合中的文档的结构,彻底能够在同一个集合中存放不一样结构的文档,支持嵌入子文档。json

文档相似于RDBMS中的记录,以BSON的格式保存。BSON是BinaryJSON的简称,是对JSON-like文档的二进制编码序列化。像JSON(JavaScriptObject Notation)同样,BSON支持在对象和数组内嵌入其它的对象和数组。有些数据类型在JSON里不能表示,但能够在BSON里表示,如Date类型和BinData(二进制数据),Python原生的类型均可以表示。与ProtocalBuffers(Google开发的用以处理对索引服务器请求/应答的协议)相比,BSON模式更自由,因此更灵活,但这样也使得每一个文档都要保存字段名,因此空间压缩上不如ProtocolBuffers。数组

BSON第一眼看上去像BLOB,但MongoDB理解BSON的内部机制,因此MongoDB能够深刻BSON对象的内部,即便是嵌套的对象,这样MongoDB就能够在顶层和嵌套的BSON对象上创建索引来应对各类查询了。

MongoDB可运行在Linux、Windows和OSX平台,支持32位和64位应用,默认端口为27017。推荐运行在64位平台,由于MongoDB为了提升性能使用了内存映射文件进行数据管理,而在32位模式运行时支持的最大文件为2GB。

MongoDB查询速度比MySQL要快,由于它cache了尽量多的数据到RAM中,即便是non-cached数据也很是快。当前MongoDB官方支持的客户端API语言就多达8种(C|C++|Java|Javascript|Perl|PHP|Python|Ruby),社区开发的客户端API还有Erlang、Go、Haskell......

3术语介绍

数据库、集合、文档

每一个MongoDB服务器能够有多个数据库,每一个数据库都有可选的安全认证。数据库包括一个或多个集合,集合以命名空间的形式组织在一块儿,用“.”隔开(相似于JAVA/Python里面的包),好比集合blog.posts和blog.authors都处于"blog"下,不会与bbs.authors有名称上的冲突。集合里的数据由多个BSON格式的文档对象组成,document的命名有一些限定,如字段名不能以"$"开头,不能有".",名称"_id"被保留为主键。

若是插入的文档没有提供“_id”字段,数据库会为文档自动生成一个ObjectId对象做为“_id”的值插入到集合中。字段“_id”的值能够是任意类型,只要可以保证唯一性。BSONObjectID是一个12字节的值,包括4字节的时间戳,3字节的机器号,2字节的进程id以及3字节的自增计数。建议用户仍是使用有意义的“_id”值。

MongoDb相比于传统的SQL关系型数据库,最大的不一样在于它们的模式设计(SchemaDesign)上的差异,正是因为这一层次的差异衍生出其它各方面的不一样。

若是将关系数据库简单理解为由数据库、表(table)、记录(record)三个层次概念组成,而在构建一个关系型数据库的时候,工做重点和难点都在数据库表的划分与组织上。通常而言,为了平衡提升存取效率与减小数据冗余之间的矛盾,设计的数据库表都会尽可能知足所谓的第三范式。相应的,能够认为MongoDb由数据库、集合(collection)、文档对象(Document-oriented、BSON)三个层次组成。MongoDb里的collection能够理解为关系型数据库里的表,虽然两者并不彻底对等。固然,不要指望collection会知足所谓的第三范式,由于它们根本就不在同一个概念讨论范围以内。相似于表由多条记录组成,集合也包含多个文档对象,虽说通常状况下,同一个集合内的文档对象具备相同的格式定义,但这并非必须的,即MongoDb的数据模式是自由的(schema-free、模式自由、无模式),collection中能够包含具备不一样schema的文档记录,支持嵌入子文档。

4MongoDB资源消耗

考虑到性能的缘由,mongo作了不少预分配,包括提早在文件系统中为每一个数据库分配逐渐增加大小的文件集。这样能够有效地避免潜在的文件系统碎片,使数据库操做更高效。

一个数据库的文件集从序号0开始分配,0,1...,大小依次是64M,128M,256M,512M,1G,2G,而后就是一直2G的建立下去(32位系统最大到512M)。因此若是上一个文件是1G,而数据量恰好超过1G,则下一个文件(大小为2G)则可能有超过90%都是空的。

若是想使磁盘利用更有效率,下面是一些解决方法:

1. 只创建一个数据库,这样最多只会浪费2G。

2. 每一个文档使用自建的“_id”值而不要使用默认的ObjectId对象。

3. 因为每一个document的每一个字段名都会存放,因此若是字段名越长,document的数据占用就会越大,所以把字段名缩短会大大下降数据的占用量。如把“timeAdded”改成“tA”。

Mongo使用内存映射文件来访问数据,在执行插入等操做时,观察mongod进程的内存占用时会发现量很大,当使用内存映射文件时是正常的。而且映射数据的大小只出如今虚拟内存那一列,常驻内存量才反应出有多少数据cached在内存中。

【按照mongodb官方的说法,mongodb彻底由系统内核进行内存管理,会尽量的占用系统空闲内存,用free能够看到,大部份内存都是做为iocache被占用的,而这部份内存是能够释放出来给应用使用的。】

5交互式shell

mongo相似于MySQL中的mysql进程,但功能远比mysql强大,它可使用JavaScript语法的命令从交互式shell中直接操做数据库。如查看数据库中的内容,使用游标循环查看查询结果,建立索引,更改及删除数据等数据库管理功能。下面是一个在mongo中使用游标的例子:

> for(var cur= db.posts.find(); cur.hasNext();) {    ...print(tojson(cur.next())); ... } 

输出:

  {         "_id" :ObjectId("4bb311164a4a1b0d84000000"),         "date" : "Wed Mar 3117:05:23 2010",         "content" :"blablablabla",         "author" :"navygong",         "title" : "the firstblog"   } 

...其它的documents。

6通常功能

6.1插入

客户端把数据序列化为BSON格式传给DB后被存储在磁盘上,在读取时数据库几乎不作什么改动直接把对象返回给客户端,由client完成unserialized。如:

> doc ={'author': 'joe', 'created': new Date('2010, 6, 21'), 'title':'Yet another blogpost', 'text': 'Here is the text...', 'tags': ['example', 'joe'], 'comments':[{'author': 'jim', 'comment': 'I disgree'}, {'author': 'navy', 'comment': 'Goodpost'}], '_id': 'test_id'}

>db.posts.insert(doc)

6.2查询

基本上你能想到的查询种类MongoDB都支持,如等值匹配,<,<=,>,>=,$ne,$in,$mod,$all,$size[1],$exists,$type[2],正则表达式匹配,全文搜索,......。还有distinct(),sort(),count(),skip()[3],group()[4],......。这里列表的查询中不少用法都和通常的RDBMS不一样。

[1]匹配一个有size个元素的数组。如db.things.find({a:{$size: 1}})可以匹配文档{a:["foo"]}。

[2] 根据类型匹配。db.things.find({a: {$type : 16}})可以匹配全部a为int类型的文档。BSON协议中规定了各类类型对应的枚举值。

[3]指定跳过多少个文档后开始返回结果,能够用在分页中。如:db.students.find().skip((pageNumber-1)*nPerPage).limit(nPerPage).forEach(function(student) { print(student.name + "<p>"); } )。

[4] 在shardedMongoDB配置环境中应该应该使用map/reduce来代替group()。

6.3删除

能够像查询同样指定条件来删除特定的文档。

6.4索引

能够像在通常的RDBMS中同样使用索引。提供了创建(通常、唯1、组合)索引、删除索引、重建索引等各类方法。索引信息保存在集合“system.indexes”中。

6.5map/reduce

MongoDB提供了map/reduce方法来进行数据的批处理及汇集操做。和Hadoop的使用相似,从集合中接收输入,结果输出到另外一个集合。若是你须要使用group,map/reduce会是个不错的选择。但MongoDB中的索引和标准查询不是使用map/reduce,而是与MySQL类似。

7模式设计

Mongo式的模式设计

使用Mongo有不少种方式,你本能上可能会像使用关系型数据库同样去使用。固然这样也能够工做得很好,但却没能发挥出Mongo的真正威力。Monog是专门设计为富对象模型(richobject model)使用的。

例如:若是你创建了一个简单的在线商店而且把产品信息存储在关系型数据库中,那你可能会有两个像这样的表:

item

title
price
sku

item_features

sku
feature_name
feature_value

你进行了范式处理由于不一样的物品有不一样的特征,这样你不用创建一个包含全部特征的表了。在Mongo中你也能够像上面那样创建两个集合,但像下面这样存储每种物品会更有效。

item : {        "title" : <title> ,        "price" : <price> ,        "sku" : <sku> ,        "features" : {           "optical zoom" : <value> ,           ...        } }

由于只要查询一个集合就能取得一件物品的全部信息,而这些信息都保存在磁盘上同一个地方,所以大大提升了查询的速度。若是你想插入或更新一种特征,如db.items.update({ sku : 123 } , { "$set" : { "features.zoom" :"5" } } ),也没必要在磁盘上移动整个对象,由于Mongo为每一个对象在磁盘上预留了空间来适应对象的增加。

8嵌入与引用

以一实例来讲,假设须要设计一个小型数据库来存储“学生、地址、科目、成绩”这些信息,那么关系型数据库的设计如图1所示,而key-value型数据库的设计则可能如图2所示。

图1关系型的数据库设计

图2key-value型的数据库设计

对比图1和图2,在关系型的数据库设计里划分出了4个表,而在key-value型的数据库设计里却只有两个集合。若是说集合与表一一对应的话,那么图2中应该也有4个集合才对,把本应该是集合的address和scores直接合入了集合students中,缘由在于在key-value型的数据库里,数据模式是自由的。

以scores来讲,在关系型的数据库设计中将其单独成一个表是由于student与score是一对多的关系,若是将score合入student表,那么就必须预留最多可能的字段,这会存在浪费,而且当之后新增一门课程时扩展困难,所以通常都会将score表单独出来。而对于key-value型的数据库就不一样了,其scores字段就是一个BSON,该BSON能够只有一个for_course,也能够有任意多个for_course,其固有的模式自由特性使得它能够将score包含在内而无需另建一个score集合。

对于与student为一对一关系的address表也能够直接合入student,无需担忧address的扩展性,当之后须要给address新增一个province字段,直接在数据插入时加上这个值便可。

固然,对于与student成多对多关系course表,为了减小数据冗余,能够将course创建为一个集合,同关系型的数据库设计中相似。

students文档中嵌入了address文档和scores文档,scores文档的“for_course”字段的值是指向courses集合的文档的引用。若是是关系型数据库,须要把“scores”做为一个单独的表,而后在students表中创建一个指向“scores”的外键。因此Mongo模式设计中的一个关键问题就是“是值得为这个对象新建一个集合呢,仍是把这个对象嵌入到其它的集合中”。在关系型数据库中为了范式的要求,每一个子项都要建一个单独的表,但在Mongo中使用嵌入式对象更有效,因此你应该给出不使用嵌入式对象而单独建一个集合的理由。

为何说引用要慢些呢,以上面的students集合为例,好比执行:

print(student.scores[0].for_course.name );

若是这是第一次访问scores[0],那些客户端必须执行:

student.scores[0].for_course= db.courses.findOne({_id:_course_id_to_find_}); //伪代码

因此每一次遍历引用都要对数据库进行一次这样的查询,即便全部的数据都在内存中。再考虑到从客户端到服务器端的种种延迟,这个时间也不会低。

有一些规则能够决定该用嵌入仍是引用:

1. 第一个类对象,也就是处于顶层的,每每应该有本身的集合。

2. 排列项详情对象应该用嵌入。

3. 处于被包含关系的应该用嵌入。

4. 多对多的关系一般应该用引用。

5. 数据量小的集合能够放心地作成一个单独的集合,由于整个集合能够很快地cached。

6. 要想得到嵌入式对象的系统级视图会更困难一些。如上面的“Scores”若是不作成嵌入式对象能够更容易地查询出分数排名前100的学生。

7. 若是嵌入的是大对象,须要留意到BSON对象的4M大小限定(后面会讲到)。

8. 若是性能是关键就用嵌入。

下面是一些示例:

1.Customer/Order/Order Line-Item

cutomers和orders应该作成一个集合,line-items应该以数组的形式嵌入在order中。

2. 博客系统

posts应该是一个集合;author能够是一个单独的集合,若是只需记录做者的email地址也能够以字段的方式存在于posts中;comments应该作成嵌入的对象。

9GridFS

GridFS是MongoDB中用来存储大文件而定义的一种文件系统。MongoDB默认是用BSON格式来对数据进行存储和网络传输。但因为BSON文档对象在MongoDB中最大为4MB,没法存储大的对象。即便没有大小限制,BSON也没法知足对大数据集的快速范围查询,因此MongoDB引进了GridFS。

9.1GridFS表示的对象信息

1. 文件对象(类GridFSFile 的对象)的元数据信息。结构以下

{  "_id" : <unspecified>,                 // unique ID for this file  "filename" : data_string,               // human name for the file  "contentType" : data_string,            // valid mime type for the object  "length" : data_number,               // size of the file in bytes  "chunkSize" : data_number,            // size of each of the chunks.  Default is 256k  "uploadDate" : data_date,              // date when object first stored  "aliases" : data_array ofdata_string,     // optional array ofalias strings  "metadata" : data_object,              // anything the user wants tostore  "md5" : data_string    //result of running "filemd5"command on the file's chunks } 

以下是put进去的一个文件例子:

{  _id:ObjId(4bbdf6200459d967be9d8e98), filename:"/home/hjgong/source_file/wnwb.svg",  length: 7429,  chunkSize:262144,  uploadDate: newDate(1270740513127),  md5:"ccd93f05e5b9912c26e68e9955bbf8b9" } 

2. 数据的二进制块以及一些统计信息。结构以下:

{  "_id": <unspecified>,       // object id of the chunk in the _chunkscollection  "files_id":<unspecified>,    // _id value ofthe owning {{files}} collection entry  "n": data_number,          // "chunk number" -starting with 0  "data": data_binary (type 0x02),        // binary data for chunk }

所以使用GridFS能够储存富媒体文件,同时存入任意的附加信息,由于这些信息实际上也是一个普通的collection。之前,若是要存储一个附件,一般的作法是,在主数据库中存放文件的属性同时记录文件的path,当查询某个文件时,须要首先查询数据库,得到该文件的path,而后从存储系统中得到相应的文件。在使用GridFS时则很是简单,能够直接将这些信息直接存储到文件中。好比下面的Java代码,将文件file(file能够是图片、音频、视频等文件)储存到db中:

其中该方法的第一个参数的类型还能够是InputStream,byte[],从而实现多个重载的方法

9.2GridFS管理

MongoDB提供的工具mongofiles能够从命令行操做GridFS。如:

./mongofiles -host localhost:1727 -unavygong -p 111 put ~/source_file/wnwb.svg

每种语言提供的MongoDB客户端API都提供了一套方法,能够像操做普通文件同样对GridFS文件进行操做,包括read(),write(),tell(),seek()等。

10Replication(复制)

Mongo提供了两种方式的复制:简单的master-slave配置及replicapair的概念。

若是安全认证被enable,无论哪一种replicate方式,都要在master/slave中建立一个能为各个database识别的用户名/密码。认证步骤以下:

slave先在local.system.users里查找一个名为"repl"的用户,找到后用它去认证master。若是"repl"用户没有找到,则使用local.system.users中的第一个用户去认证。local数据库和admin数据库同样,local中的用户能够访问整个dbserver。

10.1master-slave模式

一个server能够同时为master和slave。一个slave能够有多个master,这种方式并不推荐,由于可能会产生不可预期的结果。

在该模式中,通常是在两个不一样的机器上各部署一个MongDB实例,一个为master,另外一做为slave。将MongoDB做为master启动,只须要在命令行输入:

./mongod --master

而后主服务进程将会在数据库中建立一个集合local.oplog.$main,该collection主要记录了事务日志,即须要在slave执行的操做。

而将MongoDB做为slave启动,只须要在命令行输入:

./mongod --slave --source <masterhostname>[:<port>]

port不指定时即便用默认端口,masterhostname是master的IP或master机器的FQDN。

其余配置选项:

--autoresync:自动sync,但在10分钟内最多只会进行一次。

--oplogSize:指定master上用于存放更改的数据量,若是不指定,在32位机上最少为50M,在64位机上最少为1G,最大为磁盘空间的5%。

10.2replicapairs模式

以这种方式启动后,数据库会自动协商谁是master谁是slave。一旦一个数据库服务器断电,另外一个会自动接管,并从那一刻起起为master。万一另外一个未来也出错了,那么master状态将会转回给第一个服务器。以这种复制方式启动本地MongoDB的命令以下:

./mongod --pairwith <remoteserver> --arbiter <arbiterserver>

其中remoteserver是pair里的另外一个server,arbiterserver是一个起仲裁做用的Mongo数据库服务器,用来协商pair中哪个是master。arbiter运行在第三个机器上,利用“平分决胜制”决定在pair中的两台机器不能联系上对方时让哪个作master,通常是能同arbiter通话的那台机器作master。若是不加--arbiter选项,出现网络问题时两台机器都做为master。命令db.$cmd.findOne({ismaster:1})能够检查当前哪个database是master。

pair中的两台机器只能知足最终一致性。当replicapair中的一台机器彻底挂掉时,须要用一台新的来代替。如(n1,n2)中的n2挂掉,这时用n3来代替n2。步骤以下:

1. 告诉n1用n3来代替n2:db.$cmd.findOne({replacepeer:1});

2. 重启n1让它同n3对话:./mongod--pairwith n3 --arbiter <arbiterserver>

3. 启动n3:./mongod--pairwith n1 --arbiter <arbiterserver>。

在n3的数据没有同步到n1前n3还不能作master,这个过程长短由数据量的多少决定。

10.3受限的master-master复制

Mongo不支持彻底的master-master复制,一般状况下不推荐使用master-master模式,但在一些特定的状况下master-master也可用。master-master也只支持最终一致性。配置master-master只需运行mongod时同时加上--master选项和--slave选项。以下:

$ nohup mongod --dbpath /data1/db --port 27017 --master --slave --source localhost:27018 > /tmp/dblog1 &

$ nohup mongod --dbpath /data2/db --port 27018 --master --slave --source localhost:27017 > /tmp/dblog2 &

这种模式对插入、查询及根据_id进行的删除操做都是安全的。但对同一对象的并发更新没法进行。

11Sharding(分片)

11.1sharding介绍

MongoDB包括一个自动分片的的模块(“mongos”),从而能够构建一个大的水平可扩展的数据库集群,能够动态地添加和移走机器。以下是一个数据库集群的示意图:

mongod:数据库服务器进程,相似于mysqld。

shards:每一个shard有一个或多个mongod,一般是一个master,多个slave组成replication。数据由集合按一个预约的顺序划分,某一个范围的数据被放到一个特定的shard中,这样能够经过shard的key进行有效的范围查询。

shard keys:用于划分集合,格式相似于索引的定义,也是把一个或多个字段做为key,以key来分布数据。如:{name : 1 (1表明升序,-1表明降序)}、{_id : 1 }、{ lastname : 1,firstname : 1 }、{ tag : 1, timestamp :-1 }。若是有100万人同名,可能还须要划分,由于放到一个块里太大了,这时定义的sharkey不能只有一个name字段了。划分可以保证相邻的数据存储在一个server(固然也在相同的块上)。

chunks:是一个集合里某一范围的数据,(collection,minkey, maxkey)描述了一个chunk。块的大小有限定,当块里的数据超过最大值,块会一分为二。若是一个shard里的数据过多(添加shard时,能够指定这个shard上能够存放的最大数据量maxSize),就会有块迁移到其它的shard。一样,当添加新的server时,为了平衡各个server的负载,也会迁移chunk过去。

config server(配置服务器):存储了集群的元信息,包括每个shard、一个shard里的server、以及每个chunk的基本信息。其中主要是chunk的信息,每一个configserver中都有一份全部chunk信息的彻底拷贝。使用两阶段提交协议来保证配置信息在configserver间的一致。mongos:能够认为是一个“数据库路由器”,用以协调集群的各个部分,使它们看起来像一个系统。mongos没有固定的状态,能够在server须要的时候运行。mongos启动后会从configserver里取出元信息,而后接收客户请求,把请求路由到合适的server,获得结果后送回客户。一个系统能够有多个mongos例程,每一个例程都须要内存来存储元信息。例程间不需协同工做,每一个mongos只须要协同shardservers和config servers工做便可。固然shardservers间也会彼此对话,也会同config servers对话。

11.2sharding的配置和管理

mongod的启动选项中也包含了与sharding相关的参数,如--shardsvr(声明这是一个sharddb),--configsvr(声明这是一个configdb)。mongos的启动选项--configdb指定configserver的位置。下面的连接地址是一个简单的sharding配置例子:http://www.mongodb.org/display/DOCS/A+Sample+Configuration+Session。

像安全和认证同样,若是要sharding,先要容许一个数据库sharding,而后要指定数据库里集合的分片方式,这些都有相应的命令能够完成。

12JavaAPI简介

要使用Java操做MongoDB,在官网上下载jar包,目前最新的版本是:mongo-2.0.jar。首先介绍一下比较经常使用的几个类:

Mongo:链接服务器,执行一些数据库操做的选项,如新创建一个数据库等;

DB:对应一个数据库,能够用来创建集合等操做;

DBCollection:对应一个集合(相似表),多是咱们用得最多的,能够添加删除记录等;

DBObject接口和BasicDBObject对象:表示一个具体的记录,BasicDBObject实现了DBObject,由于是key-value的数据结构,因此用起来其实和HashMap是基本一致的;

DBCursor:用来遍历取得的数据,实现了Iterable和Iterator。

下面以一段简单的例子说明:

13MongoDB实例分析

下面经过一个实例说明如何用MongoDB做为数据库。该实例中有一个user实体,包含一个name属性,每一个user对应一到多个图片image。按照关系型数据库设计,能够设计一个user表和一个image表,其中image表中有一个关联到user表的外键。若是将这两个表对应为两个collection,即image对应的collection中的每个document都有一个key,其value是该image关联的user。但为了体现MongoDB的效率,即MongoDB是schema-free的,并且支持嵌入子文档,所以在实现时,将一个user发布的image做为该user的子文档嵌入其中,这样只须要定义一个collection,即userCollection。以下图所示:

对于图片等文件,能够存储在文件系统中,也能够存储在数据库中。所以下面分两种状况实现。

13.1图片保存在文件系统中

这种状况下,图片实体中须要记录图片的路径uri,所以Image类的定义以下:

由于在MongoDB中,当保存的对象没有设置ID时,mongoDB会默认给该条记录设置一个ID("_id"),所以在类中没有定义id属性(下同)。

由于一个user对应多个image,因此在user实体中须要记录对应的image。以下:

在main函数中实现以下功能:首先定义一个user(假设id为1),其对应3张图片,而后将该user插入userCollection中。而后,经过查询查找到该user(根据id),再发布第4张图片,更新该user,而后打印出其信息。部分代码以下:

程序运行后,在控制台打印出的信息以下:

从该结果容易看出,用户user有两个属性“_id”和“Name”,并且ImageList做为其子文档(数组)嵌入其中,该数组中是3个图片,每一个图片仍然是bson格式。

13.2图片保存在数据库中

这种状况下,图片实体只须要存储文件名便可,所以Image2类的定义以下:

User2类和上面相似,以下所示:

实现了类MongoTest2,其功能仍然是一个user对应3个图片,存入数据库中后,经过查询获得该user后,再插入第4幅图片,而后打印出信息。同时为了演示文件的查询,对存入MongoDB中的图片进行了查询并打印出其部分元数据信息。部分代码以下所示:

运行程序,控制台打印出的结果以下:

14MongoDB经常使用API总结

类转换

当把一个类对象存到mongoDB后,从mongoDB取出来时使用setObjectClass()将其转换回原来的类。

public class Tweet implements DBObject {      /* ... */  }  Tweet myTweet = new Tweet();  myTweet.put("user", "bruce");  myTweet.put("message", "fun");  myTweet.put("date", new Date());  collection.insert(myTweet);  //转换  collection.setObjectClass(Tweet.class);  Tweet myTweet = (Tweet)collection.findOne();  

默认ID

当保存的对象没有设置ID时,mongoDB会默认给该条记录设置一个ID("_id")。

固然你也能够设置本身指定的ID,如:(在mongoDB中执行用db.users.save({_id:1,name:'bruce'});)

BasicDBObject bo = new BasicDBObject();

bo.put('_id', 1);

bo.put('name', 'bruce');

collection.insert(bo);

权限

判断是否有mongoDB的访问权限,有就返回true,不然返回false。

boolean auth = db.authenticate(myUserName, myPassword);

查看mongoDB数据库列表

Mongo m = new Mongo();  for (String s : m.getDatabaseNames()) {  System.out.println(s);  } 

查看当前库下全部的表名,等于在mongoDB中执行showtables;

Set
     
     
     
     
      
      
      
       colls = db.getCollectionNames();  for (String s : colls) {  System.out.println(s);  }  
     
     
     
     

查看一个表的索引

List
     
     
     
     
      
      
      
       list = coll.getIndexInfo();  for (DBObject o : list) {  System.out.println(o);  } 
     
     
     
     

删除一个数据库

Mongo m = new Mongo();  m.dropDatabase("myDatabaseName"); 

创建mongoDB的连接

Mongo m = new Mongo("localhost", 27017); //有多个重载方法,可根据须要选择 DB db = m.getDB("myDatabaseName"); //至关于库名  DBCollection coll = db.getCollection("myUsersTable");//至关于表名  

查询数据

查询第一条记录

DBObject firstDoc = coll.findOne();

findOne()返回一个记录,而find()返回的是DBCursor游标对象。

查询所有数据

DBCursor cur = coll.find();  while(cur.hasNext()) {  System.out.println(cur.next());  } 

查询记录数量

coll.find().count();  coll.find(new BasicDBObject("age", 26)).count(); 

条件查询

BasicDBObject condition = new BasicDBObject();  condition.put("name", "bruce");  condition.put("age", 26);  coll.find(condition);  

查询部分数据块

DBCursor cursor = coll.find().skip(0).limit(10);  while(cursor.hasNext()) {  System.out.println(cursor.next());  } 

比较查询(age >50)

BasicDBObject condition = new BasicDBObject();  condition.put("age", new BasicDBObject("$gt",50));  coll.find(condition); 

比较符

"$gt": 大于

"$gte":大于等于

"$lt": 小于

"$lte":小于等于

"$in": 包含

//如下条件查询20<age<=30

condition.put("age", new BasicDBObject("$gt",20).append("$lte", 30));

插入数据

批量插入

List datas = new ArrayList();  for (int i=0; i < 100; i++) {  BasicDBObject bo = new BasicDBObject();  bo.put("name", "bruce");  bo.append("age", i);  datas.add(bo);  }  coll.insert(datas); 

又如:

DBCollection coll = db.getCollection("testCollection");    for(int i=1; i<=100; i++) {//插入100条记录        User user = new User();        user.setName("user_"+i);       user.setPoint(i);        coll.insert(user); }   

正则表达式

查询全部名字匹配 /joh?n/i 的记录

Pattern pattern = Pattern.compile("joh?n",CASE_INSENSITIVE);  BasicDBObject query = new BasicDBObject("name", pattern);  DBCursor cursor = coll.find(query); 

15Redis简介

Redis是一个key-value类型的内存数据库,每个key都与一个value关联,使得Redis与其余key-value数据库不一样是由于在Redis中的每个value都有一个类型(type),目前在Redis中支持5中数据类型:String、List、Set、ZSet和Hash。每一种类型决定了能够赋予其上的操做(这些操做成为命令command)。好比你可使用LPUSH或RPUSH命令在O(1)时间对一个list添加一个元素,而后你可使用LRANGE命令获得list中的一部分元素或使用LTRIM对该list进行trim操做。集合set操做也是很灵活的,你能够从set(无序的String的集合)中add或remove元素,还能够进行交集、合集和差集运算。每个command都是服务端自动的操做。

Redis性能上和memcached同样快但提供了更多的特性。和memcached同样,Redis支持对key设置失效时间,所以当设定的时间事后会被自动删除。

15.1Redis特性

速度快

Redis使用标准C编写实现,并且将全部数据加载到内存中,因此速度很是快。官方提供的数据代表,在一个普通的Linux机器上,Redis读写速度分别达到81000/s和110000/s。

持久化

因为全部数据保持在内存中(2.0版本开始能够只将部分数据的value放在内存,见“虚拟内存”),因此对数据的更新将异步地保存到磁盘上,Redis提供了一些策略来保存数据,好比根据时间或更新次数。

数据结构

能够将Redis看作“数据结构服务器”。目前,Redis支持5种数据结构。

自动操做

Redis对不一样数据类型的操做是自动的,所以设置或增长key值,从一个集合中增长或删除一个元素都能安全的操做。

支持多种语言

Redis支持多种语言,诸如Ruby,Python, Twisted Python, PHP, Erlang, Tcl, Perl, Lua, Java, Scala, Clojure等。对Java的支持,包括两个。一是JDBC-Redis,是使用JDBC链接Redis数据库的驱动。这个项目的目标并非彻底实现JDBC规范,由于Redis不是关系型数据库,但给Java开发人员提供了一个像操做关系数据库同样操做Redis数据库的接口。

另外一个是JRedis,是使用Redis做为数据库开发Java程序的开发包,主要提供了对数据结构的操做。

主-从复制

Redis支持简单而快速的主-从复制。官方提供了一个数据,Slave在21秒即完成了对Amazon网站10Gkey set的复制。

Sharding

很容易将数据分布到多个Redis实例中,但这主要看该语言是否支持。目前支持Sharding功能的语言只有PHP、Ruby和Scala。

16Redis数据类型

官方文档上,将Redis成为一个“数据结构服务器”(data structures server)是有必定道理的。Redis的全部功能就是以其固有的几种数据结构保存,并提供用户操做这几种结构的接口。能够对比在其余语言中那些固有数据类型及其操做。

Redis目前提供四种数据类型:string、list、set和zset(sortedset)。

16.1String类型

String是最简单的类型,一个key对应一个value。RedisString是安全的,String类型的数据最大1G。String类型的值能够被视做integer,从而可让“INCR”命令族操做,这种状况下,该integer的值限制在64位有符号数。

在list、set和zset中包含的独立的元素类型都是RedisString类型。

在Redis中,String类型由sds.c库定义,它被封装成Redis对象。和Java中的对象同样,Redis对象也是使用“引用”,所以当一个Redis String被屡次使用时,Redis会尽可能使用同一个String对象而不是屡次分配。

从Redis 1.1版开始,String对象能够编码成数字,所以这样能够节省内存空间。

16.2List类型

链表类型,主要功能是push、pop、获取一个范围的全部值等。其中的key能够理解为链表的名字。在Redis中,list就是RedisString的列表,按照插入顺序排序。好比使用LPUSH命令在list头插入一个元素,使用RPUSH命令在list的尾插入一个元素。当这两个命令之一做用于一个空的key时,一个新的list就建立出来了。好比:

最终在mylist中存储的元素为:”b”,”a”,”c”。

List的最大长度是2^32-1个元素。

16.3Set类型

集合,和数学中的集合概念类似。操做中的key理解为集合的名字。在Redis中,set就是RedisString的无序集合,不容许有重复元素。对set操做的command通常都有返回值标识所操做的元素是否已经存在。好比SADD命令是往set中插入一个元素,若是set中已存在该元素,则命令返回0,不然返回1。

Set的最大元素数是2^32-1。

Redis中对set的操做还有交集、并集、差集等。

16.4ZSet类型

Zset是set的一个升级版本,在set的基础上增长了一个顺序属性,这一属性在添加修改元素时能够指定,每次指定后zset会自动安装指定值从新调整顺序。能够理解为一张表,一列存value,一列存顺序。操做中的key理解为zset的名字。

好比ZADD命令是向zset中添加一个新元素,对该元素要指定一个score。若是对已经在zset中存在的元素施加ZADD命令同时指定了不一样的score,则该元素的score将更新同时该元素将被移动到合适的位置以使集合保持有序。

使用ZRANGE命令能够获得zset的一部分元素,固然也可使用命令ZRANGEBYSCORE,根据score获得或删除一部分元素。

Zset的最大元素数是2^32-1。

对于已经有序的zset,仍然可使用SORT命令,经过指定ASC|DESC参数对其进行排序。

16.5Hash类型

Redis Hash类型对数据域和值提供了映射,这一结构很方便表示对象。此外,在Hash中能够只保存有限的几个“域”,而不是将全部的“域”做为key,这能够节省内存。这一特性的体现能够参考下文的“虚拟内存”部分。

17Alldata in memory, but saved on disk

Redis在内存中加载并维护整个数据集,但这些数据是持久化的,由于它们在同一时间被保持在磁盘上,因此当Redis服务重启时,这些数据可以从新被加载到内存中。

Redis支持两种数据持久化的方法。一种称为snapsshotting,在这种模式下,Redis异步地将数据dump到磁盘上。Redis还能够经过配置,根据更新操做次数或间隔时间,将数据dump到磁盘。好比你能够设定发生1000个更新操做时,或距上次转储最多60s就要将数据dump到磁盘。

由于数据是异步dump的,因此当系统崩溃时,就会出现错误。由于,Redis提供了另一种模式,更安全的持久化模式,称为AppendOnly File,当出现修改数据命令的地方,这些命令就要写到“appendonly file”—ASAP。当服务重启时,这些命令会从新执行(replay)从而在内存中从新构建数据。

Redis的存储能够分为:内存存储、磁盘存储和log文件三部分,配置文件(redis.conf)中有三个参数对其进行配置。

save<seconds> <changes>:save配置,指出在多长时间,有多少次更新操做,就将数据同步到数据文件。在默认的配置文件中设置就设置了三个条件。

appendonlyyes/no:appendonly配置,指出是否在每次更新操做后进行日志记录,若是不开启,可能会在断电时致使一段时间内的数据丢失。由于redis自己同步数据文件是按上面的save条件来同步的,因此有的数据会在一段时间内只存在于内存中。

appendfsyncno/always/everysec:appendfsync配置,no表示等操做系统进行数据缓存同步到磁盘,always表示每次更新操做后手动调用fsync()将数据写到磁盘,everysec表示每秒同步一次。

18Redis的Master-Slave模式

Redis中配置Master-Slave模式很简单,两者会自动同步。配置方法是在从机的配置文件中指定slaveof参数为主机的ip和port便可,好比:slaveof 192.168.2.2016379。从原理上来讲,是从机请求主机的方式,按照tokyotyrant的作法,是能够实现主-主,主-从等灵活形式的,而redis只能支持主-从,不能支持主-主。Redis中的复制具备如下特色:

  • 一个master能够有多个slave。
  • 一个slave能够接收其余slave的连接,即不只仅能链接一个master所属的slaves,并且能链接到slave的slave,从而造成了“图”结构。
  • 在复制进行时,master是“非阻塞”的。即一个或多个slave对master进行第一次同步时,master仍然能够提供查询服务,而对于slave,当复制进行时则是“阻塞”的,期间slave不能响应查询。
  • 复制能够用于提升数据的扩展性(好比多个slave用于只读查询),或者仅用于数据备份。
  • 使用复制能够避免在master存储数据,经过修改redis.conf文件(将“save”的al注释),这样数据存储只在slave端进行。

slave链接master并发出SYNC命令开始复制或当链接关闭时与master同步数据。master在“后台”进行存储操做,同时会监听全部对数据的更新操做。当存储操做完成后,master开始向slave传送数据并将数据保存到磁盘,加载到内存。这一过程当中,master将会向slave发送全部对数据有更新操做的命令。经过telnet能够实验这一过程。经过链接到Redis端口而且发出SYNC命令,在telnet的session中,能够看到数据包的传送,以及发送给master的命令都将从新发送给slave。

当master-slave链接断掉时,slave能自动从新创建链接。当一个master并发地收到多个slave数据同步请求时,master也能自动处理。

19Redis虚拟内存管理

在Redis2.0开始(目前最新版本)第一次提出了Virtual Memory(VM)的特性。Redis是内存数据库,所以一般状况下Redis会将全部的key和value都放在内存中,但有时这并非最好的选择,为了查询速度,能够将全部的key放在内存中,而values能够放在磁盘上,当用到时再交换到内存。

好比你的数据有100000个key都放在了内存中,而只有其中10%的key被常常访问,那么可进行VM配置的Redis会尝试将不常常访问的key关联的value放到磁盘上,当这些value被请求时才会交换到内存中。

何时使用VM配置是须要考虑的问题。由于Redis是以磁盘为后备的内存数据库,在大多数状况下,只有RAM足够大时才使用Redis。但实际状况是,总会出现内存不够的状况:

  • 有“倾向”的数据访问。只有很小比例的key被常常访问(好比一个网站的在线用户),但同时在内存中却有全部的数据。
  • 不考虑访问状况以及大的value,内存不能加载全部数据。这种状况下,Redis能够视做on-disk DB,即全部的key在内存中,而访问value时要访问慢速的磁盘。

须要注意的一点是,Redis不能交换key。即若是是由于数据的key太大,value过小而形成内存不够,VM是不能解决这种状况的。当出现这种状况时,有时可使用Hash结构,将“manykeys with small values”的问题转换成“less keys but withvery values”的问题(对key进行hash,从而对数据进行“分组”)。

启用VM功能,只须要在redis.conf文件中使vm-enabled的值为yes。

20Redis实例分析

仍然以用户和图片的例子进行说明。假设将图片存储在文件系统中,即类Image中只保存图片的uri。类Image和类User的代码参见MongoDB部分的例子。下面给出测试类的关键代码:

在实现中,将类User的对象实例存放在一个set中,因此利用了SADD命令,在Java中即调用JRedis对象的sadd方法。smembers方法将返回指定key(这里是userSet)的集合中的全部value,这样就能够对每个元素进行操做。

21Redis命令总结

Redis提供了丰富的命令(command)对数据库和各类数据类型进行操做,这些command能够在Linux终端使用。在编程时,好比使用Redis 的Java语言包,这些命令都有对应的方法,好比上面例子中使用的sadd方法,就是对集合操做中的SADD命令。下面将Redis提供的命令作一总结。

21.1链接操做相关的命令

  • quit:关闭链接(connection)
  • auth:简单密码认证

21.2对value操做的命令

  • exists(key):确认一个key是否存在
  • del(key):删除一个key
  • type(key):返回值的类型
  • keys(pattern):返回知足给定pattern的全部key
  • randomkey:随机返回key空间的一个key
  • rename(oldname, newname):将key由oldname重命名为newname,若newname存在则删除newname表示的key
  • dbsize:返回当前数据库中key的数目
  • expire:设定一个key的活动时间(s)
  • ttl:得到一个key的活动时间
  • select(index):按索引查询
  • move(key, dbindex):将当前数据库中的key转移到有dbindex索引的数据库
  • flushdb:删除当前选择数据库中的全部key
  • flushall:删除全部数据库中的全部key

21.3对String操做的命令

  • set(key, value):给数据库中名称为key的string赋予值value
  • get(key):返回数据库中名称为key的string的value
  • getset(key, value):给名称为key的string赋予上一次的value
  • mget(key1, key2,…, key N):返回库中多个string(它们的名称为key1,key2…)的value
  • setnx(key, value):若是不存在名称为key的string,则向库中添加string,名称为key,值为value
  • setex(key, time,value):向库中添加string(名称为key,值为value)同时,设定过时时间time
  • mset(key1, value1, key2, value2,…key N, value N):同时给多个string赋值,名称为keyi的string赋值valuei
  • msetnx(key1, value1, key2, value2,…key N, value N):若是全部名称为keyi的string都不存在,则向库中添加string,名称keyi赋值为value i
  • incr(key):名称为key的string增1操做
  • incrby(key, integer):名称为key的string增长integer
  • decr(key):名称为key的string减1操做
  • decrby(key, integer):名称为key的string减小integer
  • append(key, value):名称为key的string的值附加value
  • substr(key, start, end):返回名称为key的string的value的子串

21.4对List操做的命令

  • rpush(key, value):在名称为key的list尾添加一个值为value的元素
  • lpush(key, value):在名称为key的list头添加一个值为value的元素
  • llen(key):返回名称为key的list的长度
  • lrange(key, start, end):返回名称为key的list中start至end之间的元素(下标从0开始,下同)
  • ltrim(key, start, end):截取名称为key的list,保留start至end之间的元素
  • lindex(key, index):返回名称为key的list中index位置的元素
  • lset(key, index, value):给名称为key的list中index位置的元素赋值为value
  • lrem(key, count, value):删除count个名称为key的list中值为value的元素。count为0,删除全部值为value的元素,count>0从头到尾删除count个值为value的元素,count<0从尾到头删除|count|个值为value的元素。
  • lpop(key):返回并删除名称为key的list中的首元素
  • rpop(key):返回并删除名称为key的list中的尾元素
  • blpop(key1, key2,… key N, timeout):lpop命令的block版本。即当timeout为0时,若遇到名称为keyi的list不存在或该list为空,则命令结束。若是timeout>0,则遇到上述状况时,等待timeout秒,若是问题没有解决,则对key i+1开始的list执行pop操做。
  • brpop(key1, key2,… key N, timeout):rpop的block版本。参考上一命令。
  • rpoplpush(srckey, dstkey):返回并删除名称为srckey的list的尾元素,并将该元素添加到名称为dstkey的list的头部

21.5对Set操做的命令

  • sadd(key, member):向名称为key的set中添加元素member
  • srem(key, member) :删除名称为key的set中的元素member
  • spop(key) :随机返回并删除名称为key的set中一个元素
  • smove(srckey, dstkey, member) :将member元素从名称为srckey的集合移到名称为dstkey的集合
  • scard(key) :返回名称为key的set的基数
  • sismember(key, member) :测试member是不是名称为key的set的元素
  • sinter(key1, key2,…key N) :求交集
  • sinterstore(dstkey, key1, key2,…key N) :求交集并将交集保存到dstkey的集合
  • sunion(key1, key2,…key N) :求并集
  • sunionstore(dstkey, key1, key2,…key N) :求并集并将并集保存到dstkey的集合
  • sdiff(key1, key2,…key N) :求差集
  • sdiffstore(dstkey, key1, key2,…key N) :求差集并将差集保存到dstkey的集合
  • smembers(key) :返回名称为key的set的全部元素
  • srandmember(key) :随机返回名称为key的set的一个元素

21.6对zset(sorted set)操做的命令

  • zadd(key, score, member):向名称为key的zset中添加元素member,score用于排序。若是该元素已经存在,则根据score更新该元素的顺序。
  • zrem(key, member) :删除名称为key的zset中的元素member
  • zincrby(key, increment, member) :若是在名称为key的zset中已经存在元素member,则该元素的score增长increment;不然向集合中添加该元素,其score的值为increment
  • zrank(key, member) :返回名称为key的zset(元素已按score从小到大排序)中member元素的rank(即index,从0开始),若没有member元素,返回“nil”
  • zrevrank(key, member) :返回名称为key的zset(元素已按score从大到小排序)中member元素的rank(即index,从0开始),若没有member元素,返回“nil”
  • zrange(key, start, end):返回名称为key的zset(元素已按score从小到大排序)中的index从start到end的全部元素
  • zrevrange(key, start, end):返回名称为key的zset(元素已按score从大到小排序)中的index从start到end的全部元素
  • zrangebyscore(key, min, max):返回名称为key的zset中score>= min且score <= max的全部元素
  • zcard(key):返回名称为key的zset的基数
  • zscore(key, element):返回名称为key的zset中元素element的score
  • zremrangebyrank(key, min, max):删除名称为key的zset中rank>= min且rank <= max的全部元素
  • zremrangebyscore(key, min, max) :删除名称为key的zset中score>= min且score <= max的全部元素
  • zunionstore/ zinterstore(dstkeyN, key1,…,keyN, WEIGHTS w1,…wN, AGGREGATE SUM|MIN|MAX):对N个zset求并集和交集,并将最后的集合保存在dstkeyN中。对于集合中每个元素的score,在进行AGGREGATE运算前,都要乘以对于的WEIGHT参数。若是没有提供WEIGHT,默认为1。默认的AGGREGATE是SUM,即结果集合中元素的score是全部集合对应元素进行SUM运算的值,而MIN和MAX是指,结果集合中元素的score是全部集合对应元素中最小值和最大值。

21.7对Hash操做的命令

  • hset(key, field, value):向名称为key的hash中添加元素field<—>value
  • hget(key, field):返回名称为key的hash中field对应的value
  • hmget(key, field1, …,field N):返回名称为key的hash中fieldi对应的value
  • hmset(key, field1, value1,…,field N, value N):向名称为key的hash中添加元素fieldi<—>value i
  • hincrby(key, field, integer):将名称为key的hash中field的value增长integer
  • hexists(key, field):名称为key的hash中是否存在键为field的域
  • hdel(key, field):删除名称为key的hash中键为field的域
  • hlen(key):返回名称为key的hash中元素个数
  • hkeys(key):返回名称为key的hash中全部键
  • hvals(key):返回名称为key的hash中全部键对应的value
  • hgetall(key):返回名称为key的hash中全部的键(field)及其对应的value

21.8持久化

  • save:将数据同步保存到磁盘
  • bgsave:将数据异步保存到磁盘
  • lastsave:返回上次成功将数据保存到磁盘的Unix时戳
  • shundown:将数据同步保存到磁盘,而后关闭服务

21.9远程服务控制

  • info:提供服务器的信息和统计
  • monitor:实时转储收到的请求
  • slaveof:改变复制策略设置
  • config:在运行时配置Redis服务器
转自: http://www.uml.org.cn/sjjm/201212205.asp
相关文章
相关标签/搜索