codecamp

第五章 - MongoDB 适用场景

现在你应该有感觉,何时何地把 MongoDB 融入你现有的系统是最棒的了。这有超多的新的类似的存储技术,肯定会让你在选择的时候晕头转向。

对我来说,最重要的教训,跟 MongoDB 无关,是说你不用再依赖单一的解决案来处理你的数据了。毫无疑问,一个单一的解决案有明显的优势,对于许多项目来说 - 或者说大多数 - 单一解决案是一个明智的选择。意思不是说你 必须 使用不同的技术,而是说你 可以。 只有你自己才知道,引进新技术是否利大于弊。

说了那么多,我希望你到目前为止学到知识让你觉得 MongoDB 是一个通用的解决案。我们已经提到很多次了,面向文档的数据库和关系型数据库有很多方面类似。因此,与其绕开这些相同点,不如我们可以简单的这样认为, MongoDB 是关系型数据库的一个代替案。比如说用 Lucene 作为关系型数据库的全文检索索引的加强,或者用 Redis 作为持久型 key-value 存储,MongoDB 就是用来保存你的数据的。

注意,我没有说用 MongoDB 取代 关系型数据库,而是 代替 案。它能做的有很多工具也能做。有些事情 MongoDB 可以做的更好,另外一些 MongoDB 做得差点。我们来进一步来讨论一下。

无模式(Flexible Schema)

面向文档数据库经常吹嘘的一个好处就是,它不需要一个固定的模式。这使得他们比传统的数据库表要灵活得多。我同意无模式是一个很不错的特性,但不是大多数人说的那样。

人们讲到无模式的时候,好像你就会把一堆乱七八糟的数据统统存起来一样。确实有些领域有些数据用关系型数据库来建模很痛苦,不过我觉得这些都是不常见的特例。无模式是酷,可是大多数情况下你的数据结构还是应当好好设计的。真正需要处理混乱时是不错,比如当你添加一个新功能的时候,不过事实是,大多数情况下,一个空列基本可以解决问题。

对我来说,动态模式的真正好处在于无需很多设置以及可以降低在 OOP 中使用的阻力。这在你使用静态语言的时候尤其明显。我在 C# 和 Ruby 中用过 MongoDB ,差异非常明显。Ruby 的动态特性以及它的流行的 ActiveRecord 实现,已经大幅降低面向对象/关系开发之间差异所带来的阻力。这不是说 MongoDB 和 Ruby 不配,而是是说它们太配了。真的,我觉得许多 Ruby 开发者眼中的的 MongoDB 只是有些许改进而已,而在 C# 或者 Java 开发者眼中,MongoDB 带来的是处理数据交互方式的翻天覆地变化。

假设从驱动开发者角度来看这个问题。你想保存一个对象?把它串行化成 JSON (严格来说是 BSON, 不过差不多) 然后把它传给 MongoDB。不需要做任何属性映射或者类型映射。这种简单性的好处就这样传递给了你,终端开发者。

写操作(Writes)

MongoDB 可以胜任的一个特殊角色是在日志领域。有两点使得 MongoDB 的写操作非常快。首先,你可以选择发送了写操作命令之后立刻返回,而无须等到操作完成。其次,你可以控制数据持久性的写行为。这些设置,加上,可以定义一个成功的提交,需要在多少台服务器上成功拿到你的数据之后才算成功,并且每个写操作都是可设置, 这就给予你很高的权限用以控制写性能和数据持久性。

除了这些性能因素,日志数据还是这样一种数据集,用无模式集合更有优势。最后,MongoDB 还提供了 受限集合(capped collection)。到目前为止,所有我们默认创建的集合都是普通集合。我们可以通过 db.createCollection 命令来创建一个受限集合并标记它的限制:

//limit our capped collection to 1 megabyte
db.createCollection('logs', {capped: true,
    size: 1048576})

当我们的受限集合到达 1MB 上限的时候,旧文档会被自动清除。另外一种限制可以基于文档个数,而不是大小,用 max标记。受限集合有一些非常有趣的属性。比如说,你可以更新文档但是你不能改变它的大小。插入顺序是被设置好了的,因此不需要另外提供一个索引来获取基于时间的排序,你可以 "tail" 一个受限集合,就和你在 Unix 中通过 tail -f 来处理文件一样,获取最新的数据,如果存在数据的话,而不需要重新查询它。

如果想让你的数据 "过期" ,基于时间而不是整个集合的大小,你可以用 TTL 索引 ,所谓 TTL 是 "time-to-live" 的缩写。

持久性(Durability)

在 1.8 之前的版本,MongoDB 不支持单服务器持久性。就是说,如果一个服务器崩溃了,可能会导致数据的丢失或者损坏。解决案是在多服务器上运行 MongoDB 副本 (MongoDB 支持复制)。日志(Journaling)是 1.8 版追加的一个非常重要的功能。从 2.0 版的 MongoDB 开始,日志是默认启动的,该功能允许快速恢复服务器,比如遭遇到了服务器崩溃或者停电的情况。

持久性在这里只是提一下,因为围绕 MongoDB 过去缺乏单服务器持久的问题,人们取得了众多成果。这个话题在以后的 Google 检索中也许还会继续出现。但是关于缺少日志功能这一缺点的信息,都是过时了的。

全文检索(Full Text Search)

真正的全文检索是在最近加入到 MongoDB 中的。它支持十五国语言,支持词形变化(stemming)和干扰字(stop words)。除了原生的 MongoDB 的全文检索支持,如果你需要一个更强大更全面的全文检索引擎的话,你需要另找方案。

事务(Transactions)

MongoDB 不支持事务。这有两个代替案,一个很好用但有限制,另外一个比较麻烦但灵活。

第一个方案,就是各种原子更新操作。只要能解决你的问题,都挺不错。我们已经看过几个简单的了,比如 $inc 和$set。还有像 findAndModify 命令,可以更新或删除文档之后,自动返回修改过的文档。

第二个方案,当原子操作不能满足的时候,回到两段提交上来。对于事务,两段提交就好像给链接手工解引用。这是一个和存储无关的解决方案。两段提交实际上在关系型数据库世界中非常常用,用来实现多数据库之间的事务。 MongoDB 网站 有个例子 演示了最典型的场合 (资金转账)。通常的想法是,把事务的状态保存到实际的原子更新的文档中,然后手工的进行 init-pending-commit/rollback 处理。

MongoDB 支持内嵌文档以及它灵活的 schema 设计,让两步提交没那么痛苦,但是它仍然不是一个好处理,特别是当你刚开始接触它的时候。

数据处理(Data Processing)

在2.2 版本之前的 MongoDB 依赖 MapReduce 来解决大部分数据处理工作。在 2.2 版本,它追加了一个强力的功能,叫做aggregation framework or pipeline,因此你只要对那些尚未支持管道的,需要使用复杂方法的,不常见的聚合使用 MapReduce。下一章我们将看看聚合管道和 MapReduce 的细节。现在,你可以把他们想象成功能强大的,用不同方法实现的 group by (打个比方)。对于非常大的数据的处理,你可能要用到其他的工具,比如 Hadoop。值得庆幸的是,这两个系统是相辅相成的,这里有个 MongoDB connector for Hadoop

当然,关系型数据库也不擅长并行数据处理。MongoDB 有计划在未来的版本中,改善增加处理大数据集的能力。

地理空间查询(Geospatial)

一个很强大的功能就是 MongoDB 支持 geospatial 索引。这允许你保存 geoJSON 或者 x 和 y 坐标到文档,并查询文档,用如 $near 来获取坐标集,或者 $within 来获取一个矩形或圆中的点。这个特性最好通过一些可视化例子来演示,所以如果你想学更多的话,可以试试看 5 minute geospatial interactive tutorial

工具和成熟度

你应该已经知道这个问题的答案了,MongoDB 确实比大多数的关系型数据要年轻很多。这个问题确实是你应当考虑的,但是到底有多重要,这取决于你要做什么,怎么做。不管怎么说,一个好的评估,不可能忽略 MongoDB 年轻这一事实,而可用的工具也不是很好 (虽然成熟的关系型数据库工具有些也非常渣!)。举个例子,它缺乏对十进制浮点数的支持,在处理货币的系统来说,明显是一个问题 (尽管也不是致命的) 。

积极的一方面,它为大多数语言提供了驱动,协议现代而简约,开发速度相当快。MongoDB 被众多公司用到了生产环境中,虽然有所担心,但经过验证后,担心很快就变成了过去。

小结

本章要说的是,MongoDB,大多数情况下,可以取代关系型数据库。它更简单更直接;更快速并且通常对应用开发者的约束更少。不过缺乏事务支持也许值得慎重考虑。当人们说起 MongoDB 在新的数据库阵营中到底处在什么位置? 时,答案很简单: 中庸(2)。

第四章 - 数据建模
第六章 - 数据聚合
温馨提示
下载编程狮App,免费阅读超1000+编程语言教程
取消
确定
目录

关闭

MIP.setData({ 'pageTheme' : getCookie('pageTheme') || {'day':true, 'night':false}, 'pageFontSize' : getCookie('pageFontSize') || 20 }); MIP.watch('pageTheme', function(newValue){ setCookie('pageTheme', JSON.stringify(newValue)) }); MIP.watch('pageFontSize', function(newValue){ setCookie('pageFontSize', newValue) }); function setCookie(name, value){ var days = 1; var exp = new Date(); exp.setTime(exp.getTime() + days*24*60*60*1000); document.cookie = name + '=' + value + ';expires=' + exp.toUTCString(); } function getCookie(name){ var reg = new RegExp('(^| )' + name + '=([^;]*)(;|$)'); return document.cookie.match(reg) ? JSON.parse(document.cookie.match(reg)[2]) : null; }