MongoDB实战第一章:为现代Web而生的数据库_MongoDB实战第一章:为现代Web而生的数据库试读-查字典图书网
查字典图书网
当前位置: 查字典 > 图书网 > 编程 > MongoDB实战 > 第一章:为现代Web而生的数据库

MongoDB实战——第一章:为现代Web而生的数据库

本章内容 MongoDB的历史、设计目标和关键特性 MongoDB Shell和驱动的简要介绍 MongoDB的使用场景及其局限性 近几年,如果构建Web应用程序,你可能会选择关系型数据库作为主要数据存储方案,而且它的表现通常也能接受。大多数开发者都熟悉SQL,能体会到精心正规化(normalized)后的数据模型所散发出的美感,了解事务的必要性,知道持久化存储引擎提供的保证。就算我们不喜欢直接和关系型数据库打交道,也能找出很多工具帮助我们降低复杂度,上至管理控制台,下至对象关系映射器。简言之,关系型数据库十分成熟且有口皆碑。因此,当一小群有主见的骨干开发者开始提倡另一种数据存储时,便有人提出了关于这些新技术的可行性和实用性的问题。这些新数据存储是关系型数据库的替代品吗?谁在生产环境中使用它们,为什么选择它们?在向非关系型数据库的迁移过程中又要做哪些权衡?上述问题的答案都可以建立在这个问题的答案上:为什么开发者对MongoDB感兴趣? MongoDB是一款为Web应用程序和互联网基础设施设计的数据库管理系统。MongoDB的数据模型和持久化策略的设计目标是提供高读写吞吐量,在易于伸缩的同时还能进行自动故障转移。无论应用程序只需要一个还是几十个数据库节点,MongoDB都能提供惊人的性能。如果你对扩展关系型数据库的艰辛深有体会,一定会觉得这是个好消息。但并非每个人都需要扩展数据库,也许需要的就只是一台数据库服务器,那么为什么要使用MongoDB呢? MongoDB之所以一下子这么引人注意,并不是因为它的扩展策略,而是因为它那直观的数据模型。假设基于文档的数据模型可以表示丰富的、有层级的数据结构,那么抛弃关系型数据库所强加的复杂的多表关联就成为了可能。举例来说,假设你正在为一个电子商务网站做产品建模,如果使用完全正规化的关系型数据模型,任何产品的信息可能都会被打散到多张表中。如果想要从数据库Shell里获得产品表述,我们需要写一句由join堆砌而成的复杂SQL查询。其结果就是,大多数开发者需要依赖软件中的辅助模块将数据组装成有意义的东西。 相比之下,使用文档模型的话,大多数产品信息都能放在一个文档里。打开MongoDB JavaScript Shell,可以轻松获得产品的完整表述,所有信息都按层级用一种类似JSON 的结构组织在一起。对于这样组织的所有信息,既可以做查询,也可以做其他操作。MongoDB的查询是专门为操作结构化文档而设计的,因此从关系型数据库切换过来的用户能有与之前类似的查询体验。此外,大多数开发者现在都使用面向对象的语言,他们想要一个能更好地映射到对象的数据存储。有了MongoDB,编程语言中定义的对象能被“原封不动”地持久化,消除掉一些对象映射程序的复杂性。 如果你对表列数据(tabular)和数据的对象表示之间的区别还很陌生,那么肯定会有很多问题。在本章末尾,我将给出MongoDB的特性和设计目标的完整概述,让你更清楚地明白为什么像Geek.net(SourceForge.net)和纽约时报(The New York Times)这样的公司的开发者要在他们的项目中使用MongoDB。我们将了解MongoDB的历史,认识它的主要特性。接下来,我们还要了解一些其他的数据库解决方案和所谓的NoSQL运动 ,我会解释MongoDB在其中发挥的作用。最后,我还将概括说明MongoDB适用于哪些场景,在哪些场景下其他数据存储又可能会更合适一些。 1.1 生于云端 MongoDB的历史虽然不长,但却值得回顾,它诞生于一个更宏伟的项目。在2007年年中,一个名为10gen的创业公司着手开发一个PaaS(Platform-as-a-Service)项目,它由应用服务器和数据库组成,用于托管Web应用程序并能按需伸缩。与谷歌的AppEngine类似,10gen的平台也设计成能够自动伸缩,自动管理硬件和软件基础设施,它解放了开发者,让他们能够专注于应用程序代码。10gen最终发现大多数开发者并不喜欢放弃对技术栈的掌控,但他们的确喜欢10gen的新数据库技术。后来10gen将精力集中到数据库上,就有了MongoDB。 随着越来越多的人在大大小小的项目中选择MongoDB并在生产环境中进行部署,10gen继续以开源项目的形式支持MongoDB数据库的开发。代码是公开的,而且可以自由修改和使用,只要遵循其开源协议的条款即可,而且10gen也鼓励社区报告缺陷和提交补丁。到目前为止,MongoDB的所有核心开发者不是10gen的创始人,就是10gen的员工,而这一项目的规划继续由用户社区的需求来决定,创造该数据库的最终目标是将关系型数据库中最好的特性和分布式键值存储结合起来。因此10gen的商业模式和其他知名开源公司毫无二致:支持开源产品的开发,并向最终用户提供订阅服务。 这段历史中有几点需要注意。首先,MongoDB最初是为一个要求数据库能在多台机器间优雅伸缩的平台而开发的。其次,MongoDB是作为Web应用程序的数据存储设计的。正如我们稍后会看到的,MongoDB被设计为可水平伸缩的主要数据存储,这一点把它和其他现代数据库系统区别开来。 1.2 MongoDB的主要特性 数据库在很大程度上是由其数据模型来定义的。本节中,我们将了解文档数据模型和MongoDB的特性,这些特性让我们能有效地操作文档数据模型。我们还会看到与运维相关的内容,重点介绍MongoDB的复制和水平伸缩策略。 1.2.1 文档数据模型 MongoDB的数据模型是面向文档的。如果你不熟悉数据库中文档的概念,那我们最好先看一个例子。 代码清单1-1 表示社交新闻网站中一个条目的文档 代码清单1-1是一个示例文档,表示社交新闻网站(比如Digg)上的一篇文章。如你所见,文档基本上是一组属性名和属性值的集合。属性的值可以是简单的数据类型,例如字符串、数字和日期。但这些值也可以是数组,甚至是其他文档 ,这让文档可以表示各种富数据结构。在示例文档中有一个属性tags ,其中用数组的形式保存了文章的标签。更有趣的是comments属性 ,它是一个评论文档的数组。 让我们花点时间把它和标准关系型数据库中相同数据的表述对比一下。图1-1是一个对应的关系型数据库的表述。既然数据表本质上来说是扁平的,那么要表示多个一对多关系就需要多张表。先从包含每篇文章核心信息的posts表开始,然后创建三张其他的表,每个表都包含一个post_id字段指向原始的文章。这种将对象的数据拆分到多张表里的技术称为正规化(normalization)。排除其他因素,正规化的数据集可以保证每个数据单元仅出现在一个地方。 图1-1 表示社交新闻网站中一个条目的基本关系数据模型 但严格的正规化是有代价的,特别是需要一些装配工作。为了显示我们刚刚提到的文章,需要在posts和tags表之间执行联结操作。还需要单独查询评论,或者也把它们放在一个join语句里。最终,是否需要严格正规化要取决于所建模的数据的类型,在第4章我会更深入地讨论这个问题。这里重点说一下,面向文档的数据模型很容易以聚合的形式来表示数据,让你能彻底和对象打交道:所有用来表示一篇文章的数据,从评论到标签,都能放进一个单独的数据库对象里。 你可能已经注意到了,除了提供丰富的结构,文档无需预先定义Schema。在关系型数据库中存储的是数据表中的行,每张表都有严格定义的Schema,规定了列和类型。如果表中的某一行需要一个额外的字段,那么就不得不显式地修改表结构。MongoDB把文档组织成集合,这种容器无需任何类型的Schema。理论上,集合中的每个文档都能拥有完全不同的结构。在实践中,一个集合里的文档相对统一,举例来说,文章集合里的文档都有表示标题、标签、评论等内容的字段。 这种做法带来了一定的优势。首先,是应用程序,而非数据库在保证数据结构。在Schema频繁变化的初期开发阶段,这能提升应用程序的开发效率。其次,更重要的是无Schema的模型允许用真正的可变属性来表示数据。举例来说,假设正在构建一个电子商务产品编目,没办法事先知道产品会有什么属性,因此应用程序需要处理这种可变性。在固定Schema的数据库中,传统的解决方案是使用实体—属性—值模式(entity-attribute-value pattern ),如图1-2所示。你所看到的内容选自Magento的数据模型,这是一个开源的电子商务框架。请注意,这些数据表基本上是一样的,value字段除外,该字段仅根据数据类型变化。该结构允许管理员定义附加的产品类型和属性,但却带来了很大的复杂性。试想打开MySQL Shell检查或更新一个用这种方式建模的产品,用于装配该产品的联结语句是何等复杂。以文档的方式建模,就不用做联结,还可以动态地添加新属性。 图1-2 PHP电子商务项目Magento的部分Schema,其中这些表用来辅助动态创建产品属性 1.2.2 即时查询 说一个系统支持即时查询(ad hoc query)的意思就是无需预先定义系统接受的查询类型。关系型数据库有这个能力,它们会严格遵照指示执行任何完备的SQL查询,无论有多少条件。如果你仅使用过关系型数据库,那么会认为即时查询是理所应当的。但是,并非所有的数据库都支持动态查询。举例来说,键值存储只能按一个维度来查询——键。和很多其他系统一样,键值存储牺牲了丰富的查询能力来换取一个简单的可伸缩模型。关系型数据库世界中,查询能力是再基础不过的事情,MongoDB的设计目标之一就是尽可能保留这种能力。 要了解MongoDB的查询语句如何工作,让我们先来看一个简单的例子,它涉及文章和评论。假设想要找到所有带politics标签、投票数大于10的文章,SQL查询大概会是这样的: MongoDB中的等效查询是用文档来做匹配的,特殊的$gt键表示“大于”: 请注意,这两个查询采用了不同的数据模型。SQL查询依赖于严格正规化的模型,其中文章和标签保存在不同的数据表中,而MongoDB的查询假定标签是存储在每个文章的文档中。两者都演示了对任意属性组合执行查询的能力,这是即时查询的本质。 正如之前提到的,一些数据库的数据模型过于简单,因此不支持即时查询。举例来说,你只能根据主键在键值存储中进行查询。对于查询而言,它并不知道这些键所对应的值。要根据第二属性进行查询,比如本例中的投票数,唯一的方法是自己写代码来构造条目,其中主键是指定的投票数,值是一个文档主键的列表,文档里包含了键中所指定的投票数。如果你在键值存储中使用了这种方法,那么一定会为此而深感愧疚,虽然这种做法在数据集较小时能管用,把多个索引塞进物理结构是单索引的存储中,这并不是一个好主意。而且,键值存储中基于散列的索引不支持范围查询,而在查询类似投票数这样的东西时,范围查询可能是必不可少的。 如果你之前是使用关系型数据库系统的,视即时查询为常态,那么应该会发现MongoDB提供了类似的查询能力。如果正在评估多种不同的数据库技术,请牢记不是所有的数据库都支持即时查询,要是你的确需要这种能力,MongoDB会是一个不错的选择。但光有即时查询是不够的,一旦数据集膨胀到一定程度,出于查询效率就必须使用索引。适当的索引能把查询和排序的速度提升一个数量级,所以支持即时查询的系统还应该要支持二级索引。 1.2.3 二级索引 理解数据库索引的最佳方法就是类比:很多书都有索引,把关键字和页码对应起来。假设你有一本菜谱,想要找到其中要用梨的菜(也许你有很多梨,不想它们坏掉)。最花时间的做法是一页页找过去,看每道菜的配料。大多数人都喜欢查书的索引,从中找到梨那一项,其中会指出所有包含梨的菜。数据库索引就是提供类似服务的数据结构。 MongoDB中的二级索引是用B树(B-tree)实现的,B树索引也是大多数关系型数据库的默认索引,针对多种查询做了优化,包括范围扫描和带排序子句的查询。通过允许使用多个二级索引,MongoDB让用户能对大量不同的查询进行优化。 在MongoDB里,每个集合最多可以创建64个索引。它支持能在RDBMS中找到的各种索引,升序、降序、唯一性、复合键索引,甚至地理空间索引都被支持。因为MongoDB和大多数RDBMS使用相同的索引数据结构,这些系统中有关管理索引的建议都是通用的。下一章里我们会开始介绍索引,因为了解索引对高效操作数据库至关重要,所以我会用整个第7章来讨论这个话题。 1.2.4 复制 MongoDB通过称为副本集(replica set)的拓扑结构提供了复制功能。副本集将数据分布在多台机器上以实现冗余,在服务器和网络故障时能提供自动故障转移。除此之外,复制功能还能用于扩展数据库的读能力。如果有一个读密集型的应用程序(Web上很常见),可以把数据库读操作分散到副本集集群中的各台机器上。 副本集由一个主节点(primary node)和一个或多个从节点(secondary node)构成。与你所熟悉的其他数据库中的主从复制(master-slave replication)类似,副本集的主节点既能接受读操作又能接受写操作,但从节点是只读的。让副本集与众不同的是它能支持自动故障转移:如果主节点出了问题,集群会选一个从节点自动将它提升为主节点。在先前的主节点恢复之后,它就会变成一个从节点。图1-3描述了这个过程。 图1-3 副本集的自动故障转移 我会在第8章里详细讨论复制。 1.2.5 速度和持久性 要理解MongoDB实现持久性的方法,需要先理解一些思想。在数据库系统领域内,写速度和持久性存在一种相反的关系。写速度可以理解为在给定时间内数据库可以处理的插入、更新和删除操作的数量。持久性则是指数据库保持这些写操作结果不变的时间长短。 举例来说,假设要向数据库写100条50 KB的记录,随后立即切断服务器的电源。机器重启后这些记录能恢复么?答案是——有可能,这取决于数据库系统和托管它的硬件。问题是写磁盘的速度要比写内存慢几个数量级。某些数据库,例如memcached,只写内存,这让它们速度很快,但数据完全易失。另一方面,几乎没有数据库只写磁盘,因为这样的操作性能过低,无法接受。因此,数据库设计者经常需要在速度和持久性中做出权衡,以平衡两者的关系。 在MongoDB中,用户可以选择写入语义,决定是否开启Journaling日志记录,通过这种方式来控制速度和持久性间的平衡。默认所有的写操作都是fire-and-forget 的,即写操作通过TCP套接字发送,不要求数据库应答。如果用户需要获得应答,可以使用特殊的安全模式发起写操作,所有驱动都提供这个安全模式。该模式强制数据库作出应答,确保数据库正确无误地接收到了写操作。安全模式是可配置的,还可用于阻塞操作,直到写操作被复制到特定数量的服务器。对于高容量、低价值的数据(例如点击流和日志),fire-and-forget风格的写操作是很理想的选择。对于重要的数据,则更倾向于安全模式。 在MongoDB 2.0中,Journaling日志是默认开启的。有了这个功能,所有写操作都会被提交到一个只能追加的日志里。即使服务器非正常关闭(比方说电源故障),该日志也能保证在重启服务器后MongoDB的数据文件被恢复到一致的状态。这是运行MongoDB最安全的方式。 事务日志 MySQL的InnoDB中有一个关于速度和持久性的折中。InnoDB是事务性存储引擎,根据定义,必须保证持久性。它通过向两个地方写入更新来实现这一目标:先写事务日志,再写内存缓冲池。事务日志会立刻同步到磁盘,而缓冲池则只会由后台线程最终同步。采取这种双重写入的原因是一般来讲随机I/O要比顺序I/O慢得多。因为向主数据文件的写操作构成随机I/O,所以先写内存会更快,可以后面再同步到磁盘上。但有些写操作(至磁盘)要保证持久性,保证写入是连续的这一点很重要,这就是事务日志的功能。在非正常关闭时,InnoDB能回放事务日志,并依此来更新主数据文件。这种做法在保证高持久性的同时也提供了能接受的性能。 可以在不记日志的情况下运行服务器,这样能提升写入的性能,但在服务器意外关闭后可能会损坏数据文件。其结果就是那些想要关闭Journaling日志功能的人必须使用复制功能,最好还能将数据复制到另一个数据中心,以此来增加失败时还能找回原始数据副本的可能性。 复制和持久性是一个很大的话题,第8章会详细展开讨论的。 1.2.6 数据库扩展 对大多数数据库而言,最简单的扩展方法就是升级硬件。如果应用程序运行在单个节点上,增加磁盘IOPS(Input/Output Operations Per Second,每秒输入输出操作)、内存和CPU通常都可以暂时消除数据库的性能瓶颈。提升单一节点的硬件来进行扩展称为垂直扩展或向上扩展。垂直扩展的优势在于简单、可靠,某种程度上而言还是比较划算的。如果你正在使用虚拟化硬件(比如亚马逊的EC2)上,可能会找不到足够大的实例。如果正在使用物理硬件,终会有一天,更强大的服务器的成本会让你望而却步。 这时就该考虑水平扩展或向外扩展了。水平扩展不是提升单一节点的性能,而是将数据库分布到多台机器上。因为水平扩展架构可以使用普通硬件,所以托管整个数据集的成本会显著降低。而且,将数据分布在多台服务器上可以降低故障带来的影响。有时机器的故障是难以避免的,如果采用的是垂直扩展,在机器发生故障时,你需要处理的就是自己大多数系统所依赖的那台服务器的故障。如果在复制的从服务器上有一份数据副本,问题还不算严重,但在单机故障仍需暂停整个系统时,这依然很棘手。水平扩展架构中的故障与之形成鲜明对比,单节点故障不会带来灾难性影响,因为从整体上看,它只代表了很小一部分数据。图1-4对比了水平扩展和垂直扩展。 图1-4 水平扩展与垂直扩展 MongoDB的水平扩展非常易于管理,它通过基于范围的分区机制,即自动分片(auto-sharding)来实现这一设计目标,自动分片机制会自动管理各个节点之间的数据分布。分片系统会处理分片节点的增加,帮助进行自动故障转移。单独的分片由一个副本集组成,其中包含至少两个节点 ,保证能够自动恢复,没有单点失败。综上所述,完全不需要编写应用程序代码来处理这些事情,应用程序的代码只要像和单个节点通信一样来访问分片集群就可以了。 我们已经讲到了MongoDB中大多数的重要特性,第2章将介绍其中一些特性在实践中是如何应用的。但此时此刻,让我们从更实用的角度来看看数据库。MongoDB的核心服务器自带了一套工具,下一节我们将介绍怎么使用这些工具以及一些输入输出数据的方式。 1.3 MongoDB的核心服务器和工具 MongoDB是用C++编写的,由10gen积极维护。该项目能在所有主流操作系统上编译,包括Mac OS X、Windows和大多数Linux。mongodb.org上提供了这些平台的预编译二进制包。MongoDB是开源的,遵循GNU-AGPL许可,GitHub上可以免费获取到源代码,而且经常会接受来自社区的贡献,但这一项目主要还是由10gen的核心服务器团队来领导的,绝大多数提交亦来自该团队。 GNU-AGPL GNU-AGPL是一个颇受争议的许可。实践中,它表示源代码能被免费获取,而且它鼓励社区的贡献。GNU-AGPL的主要局限是,出于社区的利益,任何对源代码的修改都必须公布出来。对于那些想保护其核心服务器增强特性的公司来说,10gen提供了特殊的商业许可。 MongoDB 1.0发布于2009年11月。基本每三个月人们便发布它的一个主要版本,偶数发行号 代表稳定分支,奇数代表开发分支。在本书编写时,最新版本是v2.0 。 下文概述了MongoDB自带的组件,并粗略描述了工具和面向MongoDB开发应用程序所需的语言驱动。 1.3.1 核心服务器 通过可执行文件mongod(Windows上是mongodb.exe)可以运行核心服务器。mongod服务器进程使用一个自定义的二进制协议从网络套接字上接收命令。mongod进程的所有数据文件默认都存储在/data/db 里。 mongod有多种运行模式,最常见的是作为副本集中的一员。因为推荐使用复制,通常副本集由两个副本组成,再加一个部署在第三台服务器上的仲裁进程 (arbiter process)。对于MongoDB的自动分片架构而言,其组件包含配置为预先分片的副本集的mongod进程,以及特殊的元数据服务器,称为配置服务器(config server)。另外还有单独的名为mongos的路由服务器向适当的分片发送请求。 相比其他的数据库系统,例如MySQL,配置一个mongod进程相对比较简单。虽然可以指定标准端口和数据目录,但没有什么调优数据库的选项。在大多数RDBMS中,数据库调优意味着通过一大堆参数来控制内存分配等内容,这已经变成了一门黑魔法。MongoDB的设计哲学指出,内存管理最好是由操作系统而非DBA或应用程序开发者来处理。如此一来,数据文件通过mmap()系统调用被映射成了系统的虚拟内存。这一举措行之有效地将内存管理的重任交给了操作系统内核。本书中我还会更多地阐述与mmap()相关的内容,不过目前你只需要知道缺少配置参数是一个系统设计亮点,而非缺陷。 1.3.2 JavaScript Shell MongoDB命令行Shell是一个基于JavaScript的工具,用于管理数据库和操作数据。可执行文件mongo会加载Shell并连接到指定的mongod进程。MongoDB Shell的功能和MySQL Shell差不多,主要的区别在于不使用SQL,大多数命令使用的是JavaScript表达式。举例来说,可以像下面这样选择一个数据库,向users集合中插入一个简单的文档: 第一条命令指明了想使用哪个数据库,MySQL的用户一定不会对此感到陌生。第二条命令是一个JavaScript表达式,插入一个简单的文档。要查看插入的结果,可以使用以下查询: find方法返回了之前插入的文档,其中添加了一个对象ID。所有文档都要有一个主键,存储在_id字段里。只要能保证唯一性,也可以输入一个自定义_id。如果省略了_id,则会自动插入一个MongoDB对象ID。 除了可以插入和查询数据,Shell还可以用于运行管理命令。例如,查看当前数据库操作、检查到从节点的复制状态,以及配置一个用于分片的集合。如你所见,MongoDB Shell着实是一个强大的工具,值得好好掌握。 说了这么多,你那些和MongoDB相关的大量工作都是通过特定编程语言编写的应用程序来完成的。想知道这究竟是如何办到的,必须先了解一下MongoDB语言驱动。 1.3.3 数据库驱动 如果之前把数据库驱动想象成捣腾低级设备的梦魇,那你大可放心,MongoDB的驱动很容易使用。MongoDB团队竭尽全力在提供符合特定语言风格的API,并同时保持跨语言的、相对统一的接口。举例来说,所有驱动都实现了向集合保存文档的类似方法,但不同语言里文档本身的表述通常会有所不同,驱动尽量会对特定语言表现得更自然一些。例如,在Ruby中就是使用一个Ruby散列,在Python中字典更合适一点,Java中缺少类似的语言原语,需要用一个实现了LinkedHashMap的特殊文档构建器类来表示文档。 因为驱动程序为数据库提供了一个以语言为中心的富接口,在构建应用程序时几乎不再需要驱动程序之外的抽象了。这与使用RDBMS的应用程序设计截然不同,在数据库的关系型数据模型和大多数现代编程语言的面向对象模型之间几乎都需要有一个库来做中介。虽然不需要对象关系映射器(object-relational mapper),但很多开发者都喜欢在驱动上做一层薄薄的封装,用它来处理关联、验证和类型检查 。 本书编写时,10gen官方支持C、C++、C#、Erlang、Haskell、Java、Perl、PHP、Python、Scala和Ruby的驱动,而且这个列表还在不断增长。如果你需要支持其他语言,通常都会有一个社区支持的驱动。如果对于某语言还没有社区支持的驱动,mongodb.org的文档里有用于构建新驱动的规范。官方支持的驱动被大量使用在生产环境中,而且这些驱动都遵循Apache许可,因此想要编写驱动的人可以免费获取到大量优秀的示例。 从第3章开始,我会描述驱动是如何工作的,以及如何使用它们编写程序。 1.3.4 命令行工具 MongoDB自带了很多命令行工具。  mongodump和mongorestore,备份和恢复数据库的标准工具。mongodump用原生的BSON格式将数据库的数据保存下来,因此最好只是用来做备份,其优势是热备时非常有用,备份后能方便地用mongorestore恢复。   mongoexport和mongoimport,用来导入导出JSON、CSV和TSV数据,数据需要支持多种格式时很有用。mongoimport还能用于大数据集的初始导入,但是在导入前顺便还要注意一下,为了能充分利用好MongoDB通常需要对数据模型做些调整。在这种情况下,通过使用驱动的自定义脚本来导入数据会更方便一些。 mongosniff,这是一个网络嗅探工具,用来观察发送到数据库的操作。基本就是把网络上传输的BSON转换为易于人们阅读的Shell语句。 mongostat,与iostat类似,持续轮询MongoDB和系统以便提供有帮助的统计信息,包括每秒操作数(插入、查询、更新、删除等)、分配的虚拟内存数量以及服务器的连接数。 稍后会在书中讨论另外两个工具:bsondump和mongofiles。 1.4 为什么选择MongoDB 为什么MongoDB对于你的项目来说是一个好的选择?我想我已经给出不少理由了。本节中,我会更明确地进行说明,首先说说MongoDB项目的总体设计目标。根据其作者的观点,MongoDB的设计是要结合键值存储和关系型数据库的最好特性。键值存储,因为非常简单,所以速度极快而且相对容易伸缩。关系型数据库较难伸缩,至少很难水平伸缩,但拥有富数据模型和强大的查询语言。如果MongoDB能介于两者之间,就能成为一款易伸缩、能存储丰富数据结构、提供复杂查询机制的数据库。 在使用场景方面,MongoDB非常适合用做以下应用程序的主要数据存储:Web应用程序、分析与记录应用程序,以及任何要求有中等级别缓存的应用程序。此外,由于它能方便地存储无Schema数据,MongoDB还很适合保存事先无法知晓其数据结构的数据。 之前所说的内容还不太足以让人信服,为了证实它们,我们大致了解一下目前市面上的众多数据库,并和MongoDB做个对比。接下来,我将讨论一些特殊的MongoDB使用场景,提供一些生产环境中的例子。最后,我还会讨论一些MongoDB实际使用中的重要注意事项。 1.4.1 MongoDB与其他数据库的对比 市面上的数据库数量成爆炸式增长,要在它们之间进行权衡是很困难的。幸运的是,它们之中的大多数数据库都能归在几个分类里。本节中,我会描述简单及复杂的键值存储、关系型数据库和文档数据库,并将它们与MongoDB做一个比较。下面来看表1-1。 表1-1 数据库家族 示  例 数据模型 伸缩性模型 使用场景 简单键值存储 memcached 键值对,其中值是一个二进制大字段 多种模型。memcached能跨多个节点进行伸缩,把所有可用内存变为一个巨大的数据存储 缓存、Web操作 复杂键值存储 Cassandra、Project Voldemort、Riak 多种模型。Cassandra使用名为列(column)的键值结构。Voldemort存储二进制大字段 最终一致性,多节点部署以获得高可用性和简单的故障转移 高吞吐量垂直内容(活动feed、消息队列)、缓存、Web操作 关系型数据库 Oracle数据库、MySQL、 PostgreSQL 数据表 垂直伸缩。对集群和手动分区支持有限 要求事务(银行、金融)或SQL的系统、正规化数据模型 1. 简单键值存储 简单键值存储正如其名,基于给定的键对值做索引。常见的场景是缓存。举例来说,假设需要缓存一个由应用程序呈现的HTML页面,此处的键可能是页面的URL,值是HTML本身。请注意,对键值存储而言,值就是一个不透明的字节数组。没有强加关系型数据库中的Schema,也没有任何数据类型的概念。这自然限制了键值存储允许的操作:可以放入一个新值,然后通过键将其找出或删除。拥有如此简单性的系统通常很快,而且具有可伸缩性。 最著名的简单键值存储是memcached(发音是mem-cash-dee)。memcached仅在内存里存储数据,用持久性来换取速度。它也是分布式的,跨多台服务器的memcached节点能像单个数据存储那样来使用,这消除了维护跨服务器缓存状态的复杂性。 与MongoDB相比,memcached这样的简单键值存储通常读写会更快。但与MongoDB不同,这些系统很少能充当主要数据存储。简单键值存储的最佳用途是附加存储,既可以作为传统数据库之上的缓存层,也可以作为任务队列之类的短暂服务的简单持久层。 2. 复杂键值存储 可以改进简单键值模型来处理复杂的读写Schema或提供更丰富的数据模型。如此一来,就有了复杂键值存储。广为流传的论文“Dynamo: Amazon’s Highly Available Key-value Store”中描述的亚马逊 Dynamo就是这样一个例子。Dynamo旨在成为一个健壮的数据库,在网络故障、数据中心停转及类似情况下仍能工作。这要求系统总是能够被读和写,本质上就是要求数据能自动跨多个节点进行复制。如果一个节点发生故障,系统的用户(在这里可能是一个使用亚马逊购物车的顾客)不会察觉到服务中断。当系统允许同一份数据被写到多个节点时,发生冲突的情况是不可避免的,Dynamo提供了一些解决冲突的方法。与此同时,Dynamo也很容易伸缩。因为没有主节点,所有节点都是对等的,所以很容易从整体上理解系统,能方便地添加节点。尽管Dynamo是一个私有系统,但其构建理念启发了很多NoSQL系统,包括Cassandra、Project Voldemort和Riak。 看看是谁开发了这些复杂键值存储,看看实践中它们的使用情况如何,你就能知道它们的优点了。以Cassandra为例,它实现了很多Dynamo的伸缩属性,同时还提供了与谷歌 BigTable类似的面向列的数据模型。Cassandra是一款开源的数据存储,是Facebook为其收件箱搜索功能开发的。该系统可以水平扩展,索引超过50 TB的收件箱数据,允许在收件箱中对关键字和收件人做检索。数据是根据用户ID做索引的,每条记录由一个用于关键字检索的搜索项数组和一个用于收件人检索的收件人ID数组构成。 这些复杂键值存储是由亚马逊、谷歌和Facebook这样的大型互联网公司开发的,用来管理系统的多个部分,拥有非常大的数据量。换言之,复杂键值存储管理了一个相对自包含的域,它对海量存储和可用性有一定要求。由于采用了无主节点的架构,这些系统能轻松地通过添加节点进行扩展。它们都选择了最终一致性,也就是说读请求不必返回最后一次写的内容。用户用较弱的一致性所换得的是在某一节点失效时仍能写入的能力。 这与MongoDB正好相反,MongoDB提供了强一致性、(每个分片)一个主节点、更丰富的数据模型,还有二级索引,最后两项特性总是一起出现的。如果一个系统允许跨多个域建模,例如构建完整Web应用程序时就会有此要求,那么查询就需要跨整个数据模型,这时就要用到二级索引了。 因为有丰富的数据模型,可以考虑把MongoDB作为更通用的大型、可伸缩Web应用程序的解决方案。MongoDB的伸缩架构有时也会受到非难,因为它并非源自Dynamo。但MongoDB针对不同域有不同的伸缩解决方案。MongoDB的自动分片受到了雅虎PNUTS数据存储和谷歌 BigTable的启发。读过发布这些数据存储的白皮书的人会发现,MongoDB实现伸缩的方法已经被实现了,而且还很成功。 3. 关系型数据库 本章已经介绍了不少关系型数据库的内容,简单起见,我只讨论RDBMS与MongoDB的相同点和不同点。尽管MySQL 使用固定Schema的数据表,MongoDB使用无Schema的文档,但两者都能表示丰富的数据模型。MySQL和MongoDB都支持B树索引,那些适用于MySQL索引的经验也同样适用于MongoDB。MySQL支持联结和事务,因此如果你必须使用SQL或者要求有事务,那么只能选择MySQL或其他RDBMS。也就是说,MongoDB的文档模型足以在不用联结查询的情况下表示对象。MongoDB中对单独文档的更新也是原子的,这提供了传统事务的一个子集。MongoDB和MySQL都支持复制。就可伸缩性而言,MongoDB设计成能水平扩展,能自动分片并处理故障转移。MySQL上的分片都需要手动管理,有一定的复杂性,更常见的是垂直扩展的MySQL系统。 4. 文档数据库 自称为文档数据库的产品还不多,在本书编写时,除了MongoDB之外,唯一的著名文档型数据库就是Apache CouchDB。尽管CouchDB的数据是使用JSON格式的纯文本存储的,而MongoDB是使用BSON二进制格式,但两者的文档模型是相似的。与MongoDB一样,CouchDB也支持二级索引,不同之处是CouchDB中的索引是通过编写MapReduce函数来定义的,这比MySQL和MongoDB使用的声明式语法更复杂一些。两者伸缩的方式也有所不同,CouchDB不会把数据分散到多台服务器上,每个CouchDB节点都是其他节点的完整副本。 1.4.2 使用场景和生产部署 老实说,我们不会仅根据数据库的特性做选择,还需要知道使用它的真实成功案例。这里,我提供一些广义上的MongoDB使用场景,以及一些生产环境中的示例 。 1. Web应用程序 MongoDB很适合作为Web应用程序的主要数据存储。就算是一个简单的Web应用程序也会有很多数据模型,用来管理用户、会话、应用特定的数据、上传和权限,更不用说非常重要的域了。正如它们能和关系型数据库的表列数据配合良好一样,它们也能获益于MongoDB的集合与文档模型。因为文档能表示丰富的数据结构,建模相同数据所需的集合数量通常会比使用完全正规化关系型模型的数据表数量要少。此外,动态查询和二级索引能让你轻松地实现SQL开发者所熟悉的大多数查询。最后,作为一个成长中的Web应用程序,MongoDB提供了清晰的扩展路线。 在生产环境中,MongoDB已经证明它能管理应用的方方面面,从主要数据领域到附加数据存储,比如日志和实时分析。这里的案例来自The Business Insider(TBI),它从2008年1月起将MongoDB作为主要数据存储使用。虽然TBI是一个新闻网站,但它流量很大,每天有超过一百万独立页面访问。这个案例中有意思的是除了处理站点的主要内容(文章、评论、用户等),MongoDB还处理并存储实时分析数据。这些分析被TBI用于生成动态热点地图,标明不同新闻故事的点击率。该站目前还没有太多的数据,因此尚不需要分片,但它有使用副本集来保证停电时的自动故障转移。 2. 敏捷开发 无论如何看待敏捷开发运动,你都很难否认对于快速构建应用程序的渴望。不少开发团队,包括Shutterfly和纽约时报的团队,都部分选择了MongoDB,因为相比关系型数据库,使用MongoDB能更快地开发应用程序。一个明显的原因是MongoDB没有固定的Schema,所有花在提交、沟通和实施Schema变更的时间都省下来了。 除此之外,不再需要花时间把数据的关系型表述硬塞进面向对象的数据模型里去了,也不用处理ORM生成的SQL的奇怪行为,或者对它做优化了。如此一来,MongoDB为项目带来了更短的开发周期和敏捷的、中等大小的团队。 3. 分析和日志 我之前已经暗示过MongoDB适用于分析和日志,将MongoDB用于这些方面的应用程序数量增长得很快。通常,发展成熟的公司都会选择将用于分析的特殊应用作为切入点,进入MongoDB的世界。这些公司包括GitHub、Disqus、Justin.tv和Gilt Groupe,还有其他公司就不再列举了。 MongoDB与分析的关联源自于它的速度和两个关键特性:目标原子更新和固定集合(capped collection)。原子更新让客户端能高效地增加计数器,将值放入数组。固定集合,常用于日志,特点是分配的大小固定,能实现自动过期。相比文件系统,将日志数据保存在数据库里更易组织,而且能提供更强大的查询能力。现在,抛开grep或自定义日志检索工具,用户可以使用他们熟悉并喜欢的MongoDB查询语言来查看日志输出。 4. 缓存 这是一种数据模型,它能更完整地表示对象,结合更快的平均查询速度,经常让MongoDB介于传统的MySQL与memcached之间。例如之前提到的TBI,它可以不使用memcached,直接通过MongoDB来响应页面请求。 5. 可变Schema 看看这段代码示例 : 这里从Twitter流上拉下一小段示例,并用管道将其直接导入MongoDB集合。因为流生成的是JSON文档,在把它发给数据库前就不需要预先处理数据了。mongoimport工具能直接将数据转换成BSON。这意味着每条推文都能保持其结构,原封不动地存储为集合中的单个文档。无论你是想查询、索引还是执行MapReduce聚合,都能立刻操作数据。而且,不需要事先声明数据的结构。 如果应用程序需要调用JSON API,那么拥有这样一个能轻松转换JSON的系统就太棒了。如果在存储之前无法预先了解数据的结构,MongoDB没有Schema约束的特性能大大简化数据模型。 1.5 提示与局限 有了这些优良的特性,你还需要牢记系统舍弃的特性与局限。在使用MonogDB构建真实的应用程序并用于生产环境之前,应该注意一些局限,它们大多数都是由于MongoDB使用内存映射文件(memory-mapped file)而导致的。 首先,MongoDB通常应该运行于64位的机器上。32位系统只能对4 GB内存做寻址。要知道,一般半数的内存都会被操作系统和程序进程占用,就只剩2 GB内存能用来映射数据文件。因此,如果运行在32位的服务器上,还定义了适当数量的索引,那么数据文件只能被限制在1.5 GB。大多数生产环境系统的要求都高很多,因此一个64位的系统是必需的。 使用虚拟内存映射的第二个后果是,数据占用的内存会自动按需分配。这样一来,想在共享环境中运行数据库会变得更加麻烦。要把MongoDB用于数据库服务器,最好是能让它运行在一台专门的服务器上。 最后,运行带复制功能的MongoDB是十分重要的,尤其是没有开启Journaling日志的时候。因为MongoDB使用了内存映射文件,不开启Journaling日志的话,mongod发生任何意外关闭都会导致数据损坏。因此,这时最好能有一个备份以做故障转移。对任何数据库而言这都是个不错的建议(重要的MySQL部署不做复制是很轻率的举动),这对没有Journaling日志的MongoDB尤为重要。 1.6 小结 本章我们讲述了很多内容。概括一下,MongoDB是一款开源的、基于文档的数据库管理系统,是针对现代互联网应用程序的数据和伸缩性要求而设计的,其特性包括动态查询、二级缓存、快速的原子更新和复杂的聚合,还支持基于自动故障转移的复制和用于水平扩展的自动分片。 说了这么多,你应该对这些功能都有了较好的了解。也许你对编码已经跃跃欲试了,毕竟讨论数据库的特性是一回事,在实践中使用数据库又是另一回事。接下来的两章我们就来实践一下。首先,你将接触到MongoDB JavaScript Shell,在与数据库交互时它太有用了。接下来,第3章将带你学习使用驱动,用Ruby构建一个简单的基于MongoDB的应用程序。

展开全文

推荐文章

猜你喜欢

附近的人在看

推荐阅读

拓展阅读

《MongoDB实战》其他试读目录

• 前  言
• 译 者 序
• 关 于 本 书
• 第一章:为现代Web而生的数据库 [当前]