CouchDB是用Erlang开发的面向文档的数据库系统,最近刚刚发布了0.7版本,这也是第一次公开发布的版本。CouchDB不是一个传统的关系数据库,而是面向文档的数据库,其数据存储方式有点类似lucene的inde文件格式,CouchDB最大的意义在于它是一个面向web应用的新一代存储系统,事实上,CouchDB的口号就是:下一代的Web应用存储系统,那么让我们来一一分析CouchDB的特点:
一、CouchDB是分布式的数据库,他可以把存储系统分布到n台物理的节点上面,并且很好的协调和同步节点之间的数据读写一致性。这当然也得以于Erlang无与伦比的并发特性才能做到。对于基于web的大规模应用文档应用,然的分布式可以让它不必像传统的关系数据库那样分库拆表,在应用代码层进行大量的改动。
二、CouchDB是面向文档的数据库,存储半结构化的数据,比较类似lucene的index结构,特别适合存储文档,因此很适合CMS,电话本,地址本等应用,在这些应用场合,文档数据库要比关系数据库更加方便,性能更好。
三、CouchDB支持REST API,可以让用户使用JavaS
特性
CouchDB能够适应非常广泛的应用场景,在某些偶尔连接网络的应用中,我们可以用CouchDB暂存数据,随后进行同步。也可以在Cloud环境中,作为大型的分布式的数据存储。CouchDB提供了基于 HTTP的API的访问方式,这样,保证了所有的常见的语言都可以使用CouchDB。
底层存储结构
CouchDB是一个"面向文档"的数据库,文档的格式是一个JSON字符串(也可包含二进制附件)。 底层结构是由一个"存储"(storeage) ,以及多个"视图索引"(view indexs)。 "储存"用来储存文件, "视图索引"用于查询处理。
CouchDB落实到最底层的数据结构就是两类B+Tree 。
第一类B+Tree,by_id_index (使用document的id为key)。常用来通过document的id来查找document的位置,实际上,他是指向的一个reversion列表集合。
第二类B+Tree, by_seqnum_index (使用序列号来作为key,说得具体点就是记录最新的reversion来作为key) 。当document进行更新的时候,就会产生一个新的序列号。 (值得注意的是,所有更新操作都是一个串行的方式,因此序列号反映了序列的非同步更新)。 同步复制的追踪,数据的显示以及更新与它都密不可分。
一切皆追加
所有的更新操作(包括document的创建,修改和删除)都是以在couch文件尾部追加的方式(即Append方式)进行。而不是修改现有的文件。在此之后, B+树节点也修改为指向新的文件的位置。修改操作实际上就是在现有的B+树的末尾附加一个新的文件。这反过来又引发修改父节点的B+树节点,造成一个新的副本父节点...直到所有的方式回到根B+树节点。最后修改文件头以指向新的跟节点。
这意味着所有更新将触发1次写入文件(除删除)和logN写入每个B +树节点的页面。 因此,复杂度为O ( logN )。
一切皆最佳的操作提供了一个有趣的MVCC (多版本并发控制)模型,因为该文件保存了所有以前的历史文件版本信息。 只要客户端持有先前根节点的B +树索引,它就可以得到的快照视图。即便是更新不断发生,客户将不会看到任何的最新变化。这种一致性快照在在线备份以及在线"瘦身"方面是非常有用的。
值得注意的是,读操作是并行的,写操作是串行的。 换句话说,在任何时候只有一个文件可以进行更新操作(但是,如果是写入附件的话,可以在一个文件中进行并行操作。)
GET document
当客户端向CouchDB的发出的HTTP的REST的GET请求,DBServer将做如下操作...
PUT document(修改操作)
当客户端向CouchDB的发出的HTTP的REST的PUT请求,DBServer将做如下操作...
请注意, by_seqnum B +树索引总是指向最新版本,以前的修改会自动被覆盖。
PUT/POST document(创建)
当客户端向CouchDB的发出的HTTP的REST的PUT请求,DBServer将做如下操作...
删除文件(修改)
当客户端向CouchDB的发出的HTTP的REST的DELETE请求,DBServer将做如下操作...
On
作为一个一切皆追加的存储方式,存储文件会随着时间的推移与日俱增。 因此,我们需要对其进行"瘦身",删除那些旧的Document数据。这个过程称为 Compaction。
打开一个新的存储文件
从by_seqnum B +树索引中找到最新reversion的document
复制document到新的存储文件中(在新的存储文件中自动更新相应的B +树索引)。
在Compation的过程,数据库仍然可用,只是请注意,在Compation的时候,是通过遍历DBName.couch文件, 将最新的数据拷贝到一个DBName.compat文件中, 因此这个过程可能会耗费很大的存储空间,如果您在系统繁忙(主要是write)的情况下进 行Compation,可能会导致你的硬盘空间耗尽。推荐系统在一个写操作不是很繁忙的情况下进行Compation。
View Indexs
CouchDB支持类似数据库的View的概念。所不同的是CouchDB是采用Map/Reduce的方式来表现的。(请注意,reduce语义与谷歌的Map/Reduce模型有着很大的不同)。Map()是一个用户定义的函数,它用来将每个文件处理成中间结果。Reduce()是另外的一种用户定义的函数,它用户将Map()函数所产生的中间结果进行收集汇总而生成最终结果。
Map()的中间对象和Reduce()后的结果存储在View Indexs中。随着存储得到更新, 以前的结果也会随着更新。
每一个View的定义就是一个Map函数和一个可选的Reduce函数。View存储在design Document中。
(map函数,必须)
function(doc) {
emit(null, doc);
}
(reduce函数,可选)
function (key, values, rereduce) {
return sum(values);
}
请注意这里design Document和View Index是不同的。design Document保存的是view的定义,View Index保存的是针对某个Database进行View操作,产生的结果。
起初,View文件是空的(View尚未创建) , 当查询执行的时候会触发下面一系列的处理。
1.CouchDB将遍历存储文件的by_seqnum B +树索引。
2.在此基础上, CouchDB获得所有现有文件的最新reversion
3.CouchDB通过seqnum获取document,然后将每份document反馈到View Server 上用于进行Map操作。
4.View Server 调用map(doc)函数,遍历调用emit(key,value),一个中间实体就是被这么创建出来的。
5.最后,将map(doc)函数所产生和结果集返回给CouchDB 。
6.CouchDb将这些实体加入到B +树索引,Key = emit_key + doc_id 。 遍历每一个B +树的叶节点。
7.CouchDB将View Servier所获得的Map()结果集进行"Reduce" 操作。
8.View Server 调用Reduce(key,value)函数。
9.将reduce计算的结果返回给CouchDB
10.CouchDb将更新叶子上的B +树节点,将其指向reduce的值。
11.在此之后, CouchDb移动到父节点的叶子上的B +树节点。 遍历每个B +树父节点, CouchDB将相应reduce子节点的数据发送到View Server,再次进行reduce的操作(rereduce)。
12.View Server再次调用 reduce(key, value)函数。
13.最后, 再将rereduce计算出的结果返回给CouchDB 。
14.CouchDB将更新父B +树节点,将其指向rereduce的值。
CouchDB继续上升一个等级,并重复计算rereduce结果。 最后,直到更新完根节点的rereduce的结果为止。
当处理完成后,View Index 看上去就是下面这种样子
增量视图更新
CouchDB更新view indexes采用懒加载和增量的方式。 也即是说,当文件被更新了,CouchDB不会立即刷新view index,直到下一次查询到来的时候才进行更新。
CouchDB刷新index采用如下的方式:
由于一致的快照的特性,在进行数据更新操作的过程中,view 查询需要花费较长时间。 查询需要等待索引更新完成后才能看到一致的结果。 还有一个选择(开发中) ,立即返回一个陈旧的副本,这对客户来说是可以容忍的。