【IT168 案例】Hasen是一名熟知分布式技术、Go语言的开发者,他最近在自己的博客上发布了一篇文章,谈到为什么要选择CouchDB作为自己的数据库。
我一直痛恨SQL,所以我总是对NoSQL运动充满兴趣。
我知道2个基于JSON的NoSQL数据库:MongoDB和CouchDB。
我曾试着学习MongoDB,当时我也在学习NodeJS——巨大的错误,浪费我很多时间。不管怎么说,MongoDB的API不错,但是我不喜欢它的查询语言, 跟SQL差不多,拖沓、冗长、僵化难懂。最后我实在没法用MongoDB做出什么像样的东西,因为我试着用它跟Node一起,可我在NodeJS上门的体验实在不怎么样。究其原因,是因为NodeJS的设计,因此我没用起来MongoDB不是MongoDB的问题,而是NodeJS的问题。
我知道2个基于JSON的NoSQL数据库:MongoDB和CouchDB。
我曾试着学习MongoDB,当时我也在学习NodeJS——巨大的错误,浪费我很多时间。不管怎么说,MongoDB的API不错,但是我不喜欢它的查询语言, 跟SQL差不多,拖沓、冗长、僵化难懂。最后我实在没法用MongoDB做出什么像样的东西,因为我试着用它跟Node一起,可我在NodeJS上门的体验实在不怎么样。究其原因,是因为NodeJS的设计,因此我没用起来MongoDB不是MongoDB的问题,而是NodeJS的问题。
学习CouchDB,也是因为Hasen的一个舍友跟他提起的,他从看CouchDB的官方手册开始:http://guide.couchdb.org/。
打动Hasen的,是CouchDB的如下特性:
首先,它是纯JSON文档存储。也就是说所有的文档都是JSON,而且可以有任意数量的字段,你也可以随意向现有文档中加入新字段。没有schema,因此不需要管理迁移问题。
其次,它的“视图”系统。CouchDB中的视图可以创建第二索引,而且是执行查询的惟一方式(不是通过文档的 _id来加载它)。视图基本上是map函数的缓存结果。
举个例子,比如你在一个所有者和一堆物品之间有多对一的关系,每件物品都会在一个字段中保存所有者id。该如何按照所有者查询物品列表?你可以创建一个视图,匹配owner_id和item_id,使用owner_id查询该试图。这也就是说,对于任何查询,都要先创建一个视图。不能像SQL数据库或是MongoDB那样随意查询。这可能有好有坏,要看你的心情。我个人认为挺好的,因为这让索引变得简单。
我在传统SQL数据库中见到不少如下问题:有时候,一个极其庞杂的查询成为瓶颈,对性能产生极坏影响。有时候你需要抽丝剥茧,看看问题到底在哪里;有时候,你会发现是查询写得太烂了;有时候,你只要往某个表中加个索引即可。
这种悲剧我可不想碰见。
因此,视图系统的好处在于:强迫你把查询变简单,而且易于分析。
第三,CouchDB的架构在某种意义上是“分布式”的,与git的分布方式很类似。当然,总要从一个数据库示例开始,但是如果要加入其他数据库节点,CouchDB的设计让自己很容易做到分布式。
像Git,是因为所有节点都是“master”节点,没有“slave”节点;跟Git一样,没有哪个repo比其他ripo更重要。
同步是由一个节点向其他节点推送变化完成的,这很类似于Git的repo推送、拉取变更的过程。在CouchDB中,这种推送叫“复制(replication)”。它会把所有的“新”文档和现有文档的“新版本”推送出去。CouchDB中有修订版本概念。改变一个文档,CouchDB会在内部增加一个新的修订版本保存起来,不删除或覆盖已有修订版本。不过,CouchDB不保证任何旧文档会一直保存,它们被看成“垃圾”并会在以后回收。
不过,这种分布式特性有其代价:数据在不同节点间不一定总是一致的,可能有些节点数据过期,但如果经常复制,可以确保数据节点的最终一致性。
从整体上看,我不认为这是个大问题,而是一个特性,任何基于web服务的底层架构设计都应该以其为基础。
其次,它的“视图”系统。CouchDB中的视图可以创建第二索引,而且是执行查询的惟一方式(不是通过文档的 _id来加载它)。视图基本上是map函数的缓存结果。
举个例子,比如你在一个所有者和一堆物品之间有多对一的关系,每件物品都会在一个字段中保存所有者id。该如何按照所有者查询物品列表?你可以创建一个视图,匹配owner_id和item_id,使用owner_id查询该试图。这也就是说,对于任何查询,都要先创建一个视图。不能像SQL数据库或是MongoDB那样随意查询。这可能有好有坏,要看你的心情。我个人认为挺好的,因为这让索引变得简单。
我在传统SQL数据库中见到不少如下问题:有时候,一个极其庞杂的查询成为瓶颈,对性能产生极坏影响。有时候你需要抽丝剥茧,看看问题到底在哪里;有时候,你会发现是查询写得太烂了;有时候,你只要往某个表中加个索引即可。
这种悲剧我可不想碰见。
因此,视图系统的好处在于:强迫你把查询变简单,而且易于分析。
第三,CouchDB的架构在某种意义上是“分布式”的,与git的分布方式很类似。当然,总要从一个数据库示例开始,但是如果要加入其他数据库节点,CouchDB的设计让自己很容易做到分布式。
像Git,是因为所有节点都是“master”节点,没有“slave”节点;跟Git一样,没有哪个repo比其他ripo更重要。
同步是由一个节点向其他节点推送变化完成的,这很类似于Git的repo推送、拉取变更的过程。在CouchDB中,这种推送叫“复制(replication)”。它会把所有的“新”文档和现有文档的“新版本”推送出去。CouchDB中有修订版本概念。改变一个文档,CouchDB会在内部增加一个新的修订版本保存起来,不删除或覆盖已有修订版本。不过,CouchDB不保证任何旧文档会一直保存,它们被看成“垃圾”并会在以后回收。
不过,这种分布式特性有其代价:数据在不同节点间不一定总是一致的,可能有些节点数据过期,但如果经常复制,可以确保数据节点的最终一致性。
从整体上看,我不认为这是个大问题,而是一个特性,任何基于web服务的底层架构设计都应该以其为基础。
Hasen指出:web服务和应用都面临扩展性方面的问题,而且即使是一些简单的交互多媒体页面,也会让服务器承受很大压力。他接下来对比了单机视频游戏和Web服务在这方面的不同:
首先,web开发人员多使用动态解释语言,比如Python和Ruby,这些语言的设计目的不是为了运行高性能服务。
其次,视频游戏一次只需要处理一个大型复杂任务。而在web服务中,需要把一个相对简单的任务同时做几十万遍,而且是在一个机器上。当然,这么做很愚蠢,要加入更多机器节点,把负载分到这些节点上,各个节点之间不需要通信,每个节点可以处理分配给自己的任务。做不到这几点,就不是分布系统了。
不过,目前的数据库都不是我们应该需要的数据库:只有一个master,这就是瓶颈。即使只需要向一个master数据库写入,可以从其他节点读,仍然有瓶颈。
其次,视频游戏一次只需要处理一个大型复杂任务。而在web服务中,需要把一个相对简单的任务同时做几十万遍,而且是在一个机器上。当然,这么做很愚蠢,要加入更多机器节点,把负载分到这些节点上,各个节点之间不需要通信,每个节点可以处理分配给自己的任务。做不到这几点,就不是分布系统了。
不过,目前的数据库都不是我们应该需要的数据库:只有一个master,这就是瓶颈。即使只需要向一个master数据库写入,可以从其他节点读,仍然有瓶颈。
Hasen认为这不是正确的分布式做法,要想解决每秒处理几十万个简单的页面请求,架构上必须做到水平扩展:应该可以加入更多节点,而且每个节点都可以自行决策。
如果你只有一个master节点,你的分布式就做得有问题。
即使你必须保证user-id的全局唯一性,也可以以分布式方式实现。
即使你必须保证user-id的全局唯一性,也可以以分布式方式实现。
Hasen对Riak也很欣赏,认为Riak的分布式实现很简单,也容易掌握。不过他还是选择了CouchDB:
对我来说,Riak的问题是它没有CouchDB那样的“视图”。Riak中可以做map-reduce类似操作,但是太影响性能了,在CouchDB中,视图是预先计算好的map-reduce查询,而且效率很高。
Riak超出CouchDB的,是Riak内置集群支持。在CouchDB中,没有。……虽然这可能是Riak相对CouchDB的短处,但是我还是认为视图的好处超过这一点。
而且,还有BigCouch项目,它的分布式实现非常出色。
因此,如果要扩展到几十万用户,我可以选用BigCouch。也许将来BigCouch会合并到CouchDB中。我也可以自己实现集群和分布式。
Riak超出CouchDB的,是Riak内置集群支持。在CouchDB中,没有。……虽然这可能是Riak相对CouchDB的短处,但是我还是认为视图的好处超过这一点。
而且,还有BigCouch项目,它的分布式实现非常出色。
因此,如果要扩展到几十万用户,我可以选用BigCouch。也许将来BigCouch会合并到CouchDB中。我也可以自己实现集群和分布式。
Hasen的文章最初发表后,有人留言指出MongoDB也支持水平扩展,是以自动分片(automatic sharding)方式。
对此,Hasen的回复是:
使用CouchDB,你也可以分片或是分区。实际上,使用CouchDB更简单,因为用了一致性哈希。在Couch中做分区的问题是:如果查询一个视图,必须查询所有的分区。考虑到我上面说的“每个节点必须在本地自己完成计算”,这听起来不怎么样。
不过,要记住复制功能。你可以在北美有一个分布集群,在欧洲、亚洲各有一个分区集群。这些集群可以在任何时候保证互相复制。因此,视图查询在每个集群内部的本地化的,这正是分布式精神之所在。
在我目前看来,MongoDB还做不到这一点。
不过,要记住复制功能。你可以在北美有一个分布集群,在欧洲、亚洲各有一个分区集群。这些集群可以在任何时候保证互相复制。因此,视图查询在每个集群内部的本地化的,这正是分布式精神之所在。
在我目前看来,MongoDB还做不到这一点。