MongoDB 最常见的错误说法

互联网后端架构 2021-02-23 22:52

原文链接:https://developer.mongodb.com/article/everything-you-know-is-wrong


我加入 MongoDB 还不到一年,但学到了很多东西。在面试这家公司前,我从未真正使用过 MongoDB,尽管我听过一些有关它的演讲,并且它的简单易用给我留下深刻印象。


但和大多数人一样,我也听说过这些可怕的说法:“它不支持关系模型。如果你想存储文档,那没问题,但如果以后想要进行聚合该怎么办?你将被困在这个错误的数据库里!而且不管怎样,它不支持事务!”


当我开始查找这些信息的来源,才意识到两个问题:首先,这些帖子大多是十年前的,所以他们所说的是一个刚刚推出三年的产品,而不是今天这个成熟、经过实战考验的数据库。其次,他们说的几乎都不再是对的,而且在某些情况下从来都没有对过。


所以我决定做一个演讲(并写了这篇文章)来逐一澄清这些错误的说法。

迷思 0:MongoDB 是 Web 可伸缩的


YouTube 上有个视频,视频里有几只狗(看起来应该是狗吧?)。你可能已经看到了——其中一只是盲目的新技术追随者,它盲目相信 MongoDB,没有真正加以了解。另一只狗比较理性,第一只狗的盲从让它感到沮丧。



在加入 MongoDB 第一天,我的一个朋友给我发了这个视频链接,但此前,我已经看过了。看看日期,这段视频已经流传十多年了。当时真的很有趣,但现在呢?里面几乎所有的东西都过时了。


事实上,MongoDB 公司很多人的 T 恤或笔记本电脑贴纸上都有这个角色,它是 MongoDB 的非官方吉祥物。

MongoDB 到底是什么?


在开始讲“MongoDB 不是什么”之前,我们来总结一下 MongoDB 到底是什么。


MongoDB 是一个分布式文档数据库。集群(我们称之为复本集)大多是自我管理的——当集群中的每台机器都知道了其他服务器的所在,如果其中一个节点宕机或网络出现问题,它们会自动加以处理。如果其中一台机器被关闭或发生崩溃,其他机器会接管。要实现仲裁,群集中至少需要有 3 个节点。集群中的每台服务器都有数据库所有数据的完整副本。



集群是为了冗余,而不是为了可伸缩性。所有的客户端通常只连接到一台服务器——被选举出来的主服务器,它负责执行查询和更新,并将数据变更传输到备用机器。


你可以直接连接到备用服务器做一些有趣的事情,比如运行分析查询。但一般来说,直接连接到备用节点意味着你可能会遇到一些旧数据,所以不应该连接到备用节点,除非你可以接受旧数据。


讲完“分布式”,接下来讲讲“文档数据库”。


MongoDB 与传统关系型数据库的不同之处在于,它不是将数据保存成扁平的行,然后存储在数据库的表中,而是在文档中存储分层结构的数据——基本上与 JSON 对象类似。文档存储在集合中,集合实际上就是一堆文档。每个文档可以具有与集合中其他文档不同的结构或模式。你还可以(而且应该)根据将要运行的查询类型和存储数据对集合中的文档进行索引。如果你想要确保集合中的所有文档都遵循一种结构,可以将 JSON 模式应用于集合,作为验证器。


{   '_id': ObjectId('573a1390f29313caabcd4135'),   'title': 'Blacksmith Scene',   'fullplot': 'A stationary camera looks at a large anvil with a   blacksmith behind it and one on either side.',   'cast': ['Charles Kayser', 'John Ott'],   'countries': ['USA'],   'directors': ['William K.L. Dickson'],   'genres': ['Short'],   'imdb': {'id': 5, 'rating': 6.2, 'votes': 1189},   'released': datetime.datetime(1893, 5, 9, 0, 0),   'runtime': 1,   'year': 1893}

复制代码


上面的文档是一个例子,显示了 1893 年的一部电影的信息!文档是用PyMongo驱动程序获取的。


注意,这里有些值是数组,比如“countries”和“cast”,而有些值是对象(我们称它们为子文档)。这就是 MongoDB 文档的层次结构——它们不像关系数据库中的表行那样扁平。


还需要注意的是,它还包含一个原生 Python datetime 类型的“released”字段,以及一个特殊的 ObjectId 类型的值。或许这个不是真正意义上的 JSON 文档?我们待会再来说这个……


上面的文档是一个例子,显示了 1893 年的一部电影的信息!文档是用PyMongo驱动程序获取的。


注意,这里有些值是数组,比如“countries”和“cast”,而有些值是对象(我们称它们为子文档)。这就是 MongoDB 文档的层次结构——它们不像关系数据库中的表行那样扁平。


还需要注意的是,它还包含一个原生 Python datetime 类型的“released”字段,以及一个特殊的 ObjectId 类型的值。或许这个不是真正意义上的 JSON 文档?我们待会再来说这个……

迷思 1:MongoDB 的版本是 3.2


如果你在 Debian Stretch 上使用apt get mongodb安装 MongoDB,它会安装 3.2 版本。不幸的是,这个版本已经是 5 年前的了!从那时起,已经发布了 5 个主要版本,它们包含大量的新特性,以及安全性、性能和可伸缩性方面的改进。


MongoDB 的当前版本是 4.4(截至 2020 年末)。如果你要安装,应该安装MongoDB社区服务器版,但最好请先了解一下MongoDB Atlas,也就是我们的托管数据库即服务(DBaaS)产品!

迷思 2:MongoDB 是一个 JSON 数据库


你肯定听说过 MongoDB 是一个 JSON 数据库的说法,特别是如果你最近访问过 MongoDB 的主页。



不过,正如前面提到的,MongoDB 不是一个 JSON 数据库。它支持额外的数据类型,比如对象、原生日期对象、更多的数字类型、地理原语和高效的二进制类型等等。


MongoDB 实际上是一个 BSON 数据库。


这似乎是一个微不足道的区别,但却很重要。与基于文本的格式相比,它在存储、传输和遍历方面更高效,而且比 JSON 支持更多的数据类型。


· MongoDB 存储的是 BSON 文档

· 用于查找的实际上也是 BSON 文档

· 结果以 BSON 文档的形式输出

· BSON 甚至是 MongoDB 的有线协议


如果你习惯了使用 JSON,可以将 MongoDB 看成是 JSON 数据库,这就是为什么我们有时会这样描述它!等你使用 MongoDB 一段时间,就会开始对 BSON 的优势大加赞许。

迷思 3:MongoDB 不支持事务


一些第三方文章将 MongoDB 描述成 BASE 数据库。BASE 是指“基本可用、软状态、最终一致”。


但这不是真的,从来都不是!MongoDB 从来都不是“最终一致”的。对主文档的读写是强一致性的,对单个文档的更新始终是原子的。软状态是指需要持续不断的更新数据,否则数据就会过期,但 MongoDB 并非如此。


最后,如果太多的节点不可用,无法达成仲裁,MongoDB 将进入只读状态(降低可用性)。这是有意这么设计的,因为这样可以确保在出现问题时保持一致性。


MongoDB 是一个 ACID 数据库。它支持原子性、一致性、隔离性和持久性。


对单个文档的更新始终是原子的,从 4.0 版本开始,MongoDB 也支持跨多个文档和集合的事务。从 4.2 开始,甚至支持分片集群的跨分片事务。


虽然 MongoDB 支持事务,但在使用它时仍然要谨慎。事务是以性能为代价的,而且由于 MongoDB 支持丰富的分层文档,如果你的模式设计正确,就没有必要经常跨多个文档更新数据。

迷思 4:MongoDB 不支持关系


关于 MongoDB 另一个过时的说法是,你不能在集合或文档之间建立关系。你可以使用聚合管道来连接文档,它们非常强大,你可以使用直观的查询模型来查询和转换来自多个集合的数据。


MongoDB 从 2.2 开始就支持查找(连接)。


下面的示例文档演示了将订单集合和库存集合连接之后,在返回的订单文档中包含了嵌入到数组的库存文档。



我认为,能够在返回的主文档中嵌入相关文档比在关系连接中为每个关系复制行更直观。

迷思 5:MongoDB 完全是关于分片


你可能听人说过分片是 MongoDB 的一个很酷的特性。这绝对是 MongoDB 的一个很酷的核心特性。


分片是指将数据分成片段,并将每个片段放到不同的复制集或集群中。这是一种处理庞大数据集的技术。MongoDB 可以自动将数据和请求发送到正确的副本集,并将来自多个分片的结果合并起来。


但分片存在一个很根本的问题。


我在前面提到过,副本集的最小节点数是 3,这样才能实现仲裁。在进行分片时,至少需要两个副本集,因此至少需要 6 台服务器。除此之外,你还需要运行多个 mongos 实例。mongos 是分片集群的代理,负责路由请求和响应。要实现高可用性,至少需要两个 mongos 实例。



因此,这意味着一个分片集群最少需要 8 台服务器,每增加一个分片,就会增加至少 3 台服务器。


分片集群还会使数据变得难以管理,并且会限制可执行的查询类型。如果你确实需要进行分片,那么分片对你来说确实是有用的,但更简单的办法是直接升级硬件,通常这样更便宜、更容易。


数据的伸缩主要与 RAM 有关,所以如果可以的话,请购买更多的 RAM。如果 CPU 是瓶颈,那就升级 CPU。如果磁盘容量是个问题,那就买一个更大的磁盘。


MongoDB 的分片功能一直都在,如果对内存的需求超过了一台计算机的容量,可以使用分片。你还可以使用分片功能做一些有趣的事情,比如地理位置固定,可以将用户数据存储在离用户位置更近的地方,从而减少延迟。


在决定使用分片进行数据伸缩时,应该先考虑一下硬件升级是否是一个更有效的选择。


你可以看看 MongoDB Atlas,MongoDB 的托管数据库即服务(DBaaS)产品。MongoDB Atlas 不仅可以在你选择的云平台上托管数据库,还可以根据需要伸缩数据库,保持可用性,同时保持较低的成本。它将负责处理备份和冗余,还提供了额外的功能,如图表、文本搜索、无服务器功能等等。

迷思 6:MongoDB 不安全


关于 MongoDB 的另一个误解,是说它不安全。我个人认为,这对 MongoDB 来说是一种不公平的评判,但不可否认的是,在互联网上有很多不安全的 MongoDB 实例,而且发生了几起涉及数据泄露的事故。


从历史上看,这是由于 MongoDB 的发行方式导致的。一些 Linux 发行版曾经在发行 MongoDB 时启用网络并禁用身份验证。


因此,如果你没有防火墙,或者打开了防火墙上的 MongoDB 端口,那么你的数据就有可能被窃取。现如今,机器人很可能会找到你的数据,把你的数据加密,然后再加上一个文档向你勒索比特币。


我想说的是,如果你将一个不受保护的数据库服务器放到互联网上,那就是你的错——这种情况肯定已经发生过很多次了,而且肯定有办法不让事态变得如此糟糕。


我们对 MongoDB 3.6 的一些默认设置进行了修复。MongoDB 不会连接到网络,除非启用身份验证,或者你给服务器设置了一个特定标志来覆盖这个行为。所以,如果使用不当,仍然可能不安全。不管怎样,你至少要先阅读一下手册。


除此之外,在安全方面,MongoDB 还使用了行业标准,例如使用 TLS 对传输中的数据进行加密,使用 SCRAM-SHA-256 对用户进行安全认证。


MongoDB 还提供了客户端字段级加密(FLE)功能,数据在客户端和传输过程中都是经过加密的。也就是说,如果第三方拿到数据库服务器的访问权限,但没有客户端访问权限,也无法读取加密的数据。

迷思 7:MongoDB 会丢失数据


这在 Hacker News 上是一个很经典的问题。有人发帖子分享他们如何成功地用 MongoDB 开发了一些东西,然后立刻有人评论说:“我知道这个人曾经把 MongoDB 里所有的数据都弄丢了,所以请避免同样的事情发生。”


如果你继续联系这些用户,并让他们提交问题,他们就消失了。


MongoDB 被广泛应用于那些非常关心数据保存问题的行业,从摩根士丹利、巴克莱和汇丰等大型银行,到福布斯等大型出版品牌。我们从未收到过有关大规模数据丢失的报告。

迷思 8:MongoDB 只是一个玩具


看到这里,你就已经知道这是一个迷思了。


MongoDB 是一个用于存储文档的通用数据库,它可以安全、原子地更新数据,可以与其他文档连接,提供了丰富、强大和直观的查询语言。当你的数据规模大到一台机器装不下,可以进行分片,它还支持高级功能,例如用于保护敏感数据的客户端字段级别加密、可以让应用程序立即响应变更数据的变更流。


- END -


往期回顾

美团即时物流的分布式系统架构设计

计算机领域的经典教材有哪些?

急需降低系统复杂性,我们从 Kafka 迁移到了 Pulsar