您好,欢迎访问一九零五行业门户网

视觉中国的NoSQL之路:从MySQL到MongoDB

http://www.programmer.com.cn/4199/ 起因 视觉中国网站(www.chinavisual.com)是国内最大的创意人群的专业网站。2009年以前,同很多公司一样,我们的cms和社区产品都构建于php+nginx+mysql之上;mysql使用了master+master的部署方案;前端使用自己的php框架
http://www.programmer.com.cn/4199/
起因
视觉中国网站(www.chinavisual.com)是国内最大的创意人群的专业网站。2009年以前,同很多公司一样,我们的cms和社区产品都构建于php+nginx+mysql之上;mysql使用了master+master的部署方案;前端使用自己的php框架进行开发;memcached作为缓存;nginx进行web服务和负载均衡;gearman进行异步任务处理。在传统的基于静态内容(如文章,资讯,帖子)的产品,这个体系运行良好。通过分级的缓存,数据库端实际负载很轻。2009年初,我们进行了新产品的开发。此时,我们遇到了如下一些问题。
用户数据激增:我们的mysql某个信息表上线1个月的数据就达到千万。我们之前忽略的很多数据,在新形势下需要跟踪记录,这也导致了数据量的激增;
用户对于信息的实时性要求更高:对信息的响应速度和更新频度就要求更高。简单通过缓存解决的灵丹妙药不复存在;
对于scale-out的要求更高:有些创新产品的增长速度是惊人的。因此要求能够无痛的升级扩展,否则一旦停机,那么用户流失的速度也是惊人的;
大量文件的备份工作:我们面向的是创意人群,产生的内容是以图片为主。需要能够对这些图片及不同尺寸的缩略图进行有效的备份管理。我们之前使用的linux inotify+rsync的增量备份方案效果不佳;
需求变化频繁:开发要更加敏捷,开发成本和维护成本要更低,要能够快速地更新进化,新功能要在最短的周期内上线。
最初,我们试图完全通过优化现有的技术架构来解决以上问题:对数据时效性进一步分级分层缓存,减小缓存粒度;改进缓存更新机制(线上实时和线下异步更新)提高缓存命中率;尝试对业务数据的特点按照水平和垂直进行分表;使用mogilefs进行分布存储;进一步优化mysql的性能,同时增加mysql节点等。但很快发现,即便实施了上述方案,也很难完全解决存在的问题:过度依赖memcached导致数据表面一致性的维护过于复杂,应用程序开发需要很小心,很多时候出现memcached的失效会瞬间导致后端数据库压力过大;不同类型数据的特点不同,数据量差别也很大;分表的机制和方式在效率平衡上很难取舍;mogilefs对我们而言是脚小鞋大,维护成本远远超过了实际的效益;引入更多的mysql数据库节点增大了我们的维护量,如何有效监控和管理这些节点又成了新的问题。虽然虚拟化可以解决部分问题,但还是不能令人满意;
除了mysql,能否找到一个更为简单、轻便的瑞士军刀呢?我们的目光投向了nosql的方案。
候选方案
最初,对于nosql的候选方案,我依据关注和熟悉程度,并且在甄别和选择合适的方案时特别制定了一些原则:是否节省系统资源,对于cpu等资源是否消耗过大;客户端/api支持,这直接影响应用开发的效率;文档是否齐全,社区是否活跃;部署是否简单;未来扩展能力。按以上几点经过一段测试后,我们候选名单中剩下redis、mongodb和flare。
redis对丰富数据类型的操作很吸引人,可以轻松解决一些应用场景,其读写性能也相当高,唯一缺点就是存储能力和内存挂钩,这样如果存储大量的数据需要消耗太多的内存(最新的版本已经不存在这个问题)。
flare的集群管理能力令人印象深刻,它可以支持节点的动态部署,支持节点的基于权重的负载均衡,支持数据分区。同时允许存储大的数据,其key的长度也不受memcached的限制。而这些对于客户端是透明的,客户端使用memcached协议链接到flare的proxy节点就可以了。由于使用集群,flare支持fail-over,当某个数据节点宕掉,对于这个节点的访问都会自动被proxy节点forward到对应的后备节点,恢复后还可以自动同步。flare的缺点是实际应用案例较少,文档较为简单,目前只在geek使用。
以上方案都打算作为一个优化方案,我从未想过完全放弃mysql。然而,用mongodb做产品的设计原型后,我彻底被征服了,决定全面从mysql迁移到mongodb。
为什么mongodb可以替代mysql?
mongodb是一个面向文档的数据库,目前由10gen开发并维护,它的功能丰富,齐全,完全可以替代mysql。在使用mongodb做产品原型的过程中,我们总结了monogdb的一些亮点:
使用json风格语法,易于掌握和理解:mongodb使用json的变种bson作为内部存储的格式和语法。针对mongodb的操作都使用json风格语法,客户端提交或接收的数据都使用json形式来展现。相对于sql来说,更加直观,容易理解和掌握。
schema-less,支持嵌入子文档:mongodb是一个schema-free的文档数据库。一个数据库可以有多个collection,每个collection是documents的集合。collection和document和传统数据库的table和row并不对等。无需事先定义collection,随时可以创建。
collection中可以包含具有不同schema的文档记录。 这意味着,你上一条记录中的文档有3个属性,而下一条记录的文档可以有10个属性,属性的类型既可以是基本的数据类型(如数字、字符串、日期等),也可以是数组或者散列,甚至还可以是一个子文档(embed document)。这样,可以实现逆规范化(denormalizing)的数据模型,提高查询的速度。
图1 mongodb是一个schema-free的文档数据库
图2是一个例子,作品和评论可以设计为一个collection,评论作为子文档内嵌在art的comments属性中,评论的回复则作为comment子文档的子文档内嵌于replies属性。按照这种设计模式,只需要按照作品id检索一次,即可获得所有相关的信息了。在mongodb中,不强调一定对数据进行normalize ,很多场合都建议de-normalize,开发人员可以扔掉传统关系数据库各种范式的限制,不需要把所有的实体都映射为一个collection,只需定义最顶级的class。mongodb的文档模型可以让我们很轻松就能将自己的object映射到collection中实现存储。
图2 mongodb支持嵌入子文档
简单易用的查询方式:mongodb中的查询让人很舒适,没有sql难记的语法,直接使用json,相当的直观。对不同的开发语言,你可以使用它最基本的数组或散列格式进行查询。配合附加的operator,mongodb支持范围查询,正则表达式查询,对子文档内属性的查询,可以取代原来大多数任务的sql查询。
crud更加简单,支持in-place update:只要定义一个数组,然后传递给mongodb的insert/update方法就可自动插入或更新;对于更新模式,mongodb支持一个upsert选项,即:“如果记录存在那么更新,否则插入”。mongodb的update方法还支持modifier,通过modifier可实现在服务端即时更新,省去客户端和服务端的通讯。这些modifer可以让mongodb具有和redis、memcached等kv类似的功能:较之mysql,monodb更加简单快速。modifier也是mongodb可以作为对用户行为跟踪的容器。在实际中使用modifier来将用户的交互行为快速保存到mongodb中以便后期进行统计分析和个性化定制。
所有的属性类型都支持索引,甚至数组:这可以让某些任务实现起来非常的轻松。在mongodb中,“_id”属性是主键,默认mongodb会对_id创建一个唯一索引。
服务端脚本和map/reduce:mongodb允许在服务端执行脚本,可以用javascript编写某个函数,直接在服务端执行,也可以把函数的定义存储在服务端,下次直接调用即可。mongodb不支持事务级别的锁定,对于某些需要自定义的“原子性”操作,可以使用server side脚本来实现,此时整个mongodb处于锁定状态。map/reduce也是mongodb中比较吸引人的特性。map/reduce可以对大数据量的表进行统计、分类、合并的工作,完成原先sql的groupby等聚合函数的功能。并且mapper和reducer的定义都是用javascript来定义服务端脚本。
性能高效,速度快: mongodb使用c++/boost编写,在多数场合,其查询速度对比mysql要快的多,对于cpu占用非常小。部署也很简单,对大多数系统,只需下载后二进制包解压就可以直接运行,几乎是零配置。
支持多种复制模式: mongodb支持不同的服务器间进行复制,包括双机互备的容错方案。
master-slave是最常见的。通过master-slave可以实现数据的备份。在我们的实践中,我们使用的是master-slave模式,slave只用于后备,实际的读写都是从master节点执行。
replica pairs/replica sets允许2个mongodb相互监听,实现双机互备的容错。
mongodb只能支持有限的双主模式(master-master),实际可用性不强,可忽略。
内置gridfs,支持大容量的存储:这个特点是最吸引我眼球的,也是让我放弃其他nosql的一个原因。gridfs具体实现其实很简单,本质仍然是将文件分块后存储到files.file和files.chunk 2个collection中,在各个主流的driver实现中,都封装了对于gridfs的操作。由于gridfs自身也是一个collection,你可以直接对文件的属性进行定义和管理,通过这些属性就可以快速找到所需要的文件,轻松管理海量的文件,无需费神如何hash才能避免文件系统检索性能问题, 结合下面的auto-sharding,gridfs的扩展能力是足够我们使用了。在实践中,我们用mongodb的gridfs存储图片和各种尺寸的缩略图。
图3 mongodb的auto-sharding结构
内置sharding,提供基于range的auto sharding机制:一个collection可按照记录的范围,分成若干个段,切分到不同的shard上。shards可以和复制结合,配合replica sets能够实现sharding+fail-over,不同的shard之间可以负载均衡。查询是对客户端是透明的。客户端执行查询,统计,mapreduce等操作,这些会被mongodb自动路由到后端的数据节点。这让我们关注于自己的业务,适当的时候可以无痛的升级。mongodb的sharding设计能力最大可支持约20 petabytes,足以支撑一般应用。
第三方支持丰富: mongodb社区非常活跃,很多开发框架都迅速提供了对mongdb的支持。不少知名大公司和网站也在生产环境中使用mongodb,越来越多的创新型企业转而使用mongodb作为和django,ror来搭配的技术方案。
实施结果
实施monodb的过程是令人愉快的。我们对自己的php开发框架进行了修改以适应mongodb。在php中,对mongodb的查询、更新都是围绕array进行的,实现代码变得很简洁。由于无需建表,monodb运行测试单元所需要的时间大大缩短,对于tdd敏捷开发的效率也提高了。当然,由于mongodb的文档模型和关系数据库有很大不同,在实践中也有很多的困惑,幸运的是,mongodb开源社区给了我们很大帮助。最终,我们使用了2周就完成了从mysql到mongodb的代码移植比预期的开发时间大大缩短。从我们的测试结果看也是非常惊人,数据量约2千万,数据库300g的情况下,读写2000rps,cpu等系统消耗是相当的低(我们的数据量还偏小,目前陆续有些公司也展示了他们的经典案例:mongodb存储的数据量已超过 50亿,>1.5tb)。目前,我们将mongodb和其他服务共同部署在一起,大大节约了资源。
一些小提示
切实领会mongodb的document模型,从实际出发,扔掉关系数据库的范式思维定义,重新设计类;在服务端运行的javascript代码避免使用遍历记录这种耗时的操作,相反要用map/reduce来完成这种表数据的处理;属性的类型插入和查询时应该保持一致。若插入时是字符串“1”,则查询时用数字1是不匹配的;优化mongodb的性能可以从磁盘速度和内存着手;mongodb对每个document的限制是最大不超过4mb;在符合上述条件下多启用embed document, 避免使用databasereference;内部缓存可以避免n+1次查询问题(mongodb不支持joins)。
用capped collection解决需要高速写入的场合,如实时日志;大数据量情况下,新建同步时要调高oplogsize的大小,并且自己预先生成数据文件,避免出现客户端超时;collection+index合计数量默认不能超过24000;当前版本(结束语
mongodb的里程碑是1.6版本,预计今年7月份发布,届时,mongodb的sharding将首次具备在生产环境中使用的条件。作为mongodb的受益者,我们目前也在积极参与mongodb社区活动,改进perl/php对于mongodb的技术方案。在1.6版本后也将年内推出基于mongodb的一些开源项目。
对于那些刚刚起步,或者正在开发创新型互联网应用的公司来说,mongodb的快速、灵活、轻量和强大扩展性,正适合我们快速开发产品,快速迭代,适应用户迅速变化和更新的种种需求。
总而言之,mongodb是一个最适合替代mysql的全功能的nosql产品,使用mongodb+perl/php/django/ror的组合将很快成为开发web2.0、3.0的产品的最佳组合,就像当年mysql替代oracle/db2/informix一样,历史总是惊人的相似,让我们拭目以待吧!
作者简介:
潘凡(nightsailer,n.s.), 视觉中国网站技术总监,联合创始人,家有1狗2猫。目前负责网站平台设计和底层产品研发工作。当前关注:apps平台设计、分布式文件存储、nosql、高性能后现代的perl编程。twitter:@nightsailer  blog:http://nightsailer.com/
其它类似信息

推荐信息