mongodb部署方式 mongodb 部署模式_nosql

MongoDB是一个非关系文档数据库,支持类似JSON的存储。其灵活的数据模型使您可以轻松存储非结构化数据。它于2009年首次发布,是最常用的NoSQL数据库。它的下载量已超过325亿次。

 MongoDB在开发人员中很受欢迎,因为它很容易上手。多年来,MongoDB引入了许多功能,这些功能将数据库变成了一个强大的解决方案,能够为应用程序存储TB级的数据。

与任何数据库一样,使用MongoDB的开发人员和DBA应该考虑如何优化其数据库的性能,尤其是在云服务中,处理,传输和存储的每个字节都需要花钱。能够如此快速地开始使用MongoDB意味着很容易忽略潜在的问题或错过简单的性能改进。

在本文中,我们将介绍10种基本技术,您可以应用这些技术为您的应用程序充分利用MongoDB。 

▌MongoDB最佳实践#1:从一开始就在数据库上启用授权和身份验证

数据库越大,泄漏造成的损害就越大。由于首次部署MongoDB时默认禁用授权和身份验证的简单事实,因此发生了大量数据泄漏。虽然它不是性能提示,但从一开始就启用授权和身份验证至关重要,因为它将随着时间的推移为您节省由于未经授权的访问或数据泄漏而导致的任何潜在痛苦。

部署 MongoDB 的新实例时,默认情况下该实例没有用户、密码或访问控制。在最近的MongoDB版本中,默认IP绑定更改为127.0.0.1,并添加了localhost异常,这减少了安装数据库时数据库暴露的可能性。

但是,从安全角度来看,这仍然不理想。

第一条建议是创建管理员用户,然后在启用授权选项的情况下重新启动实例。这可以防止对实例进行任何未经授权的访问。

创建管理员:

> use admin
switched to db admin
> db.createUser({
...   user: "zelmar",
...   pwd: "password",
...   roles : [ "root" ]
... })
Successfully added user: { "user" : "zelmar", "roles" : [ "root" ] }

然后,您需要启用授权并重启实例。如果要从命令行部署MongoDB:

mongod --port 27017 --dbpath /data/db --auth

或者,如果您使用配置文件部署MongoDB,则需要包括:

security:
    authorization: "enabled"

▌MongoDB最佳实践#2:不要在生产实例中使用“不推荐版本”或“生命周期结束版本”并保持更新

这看起来应该很明显,但我们在生产实例中看到的最常见的问题之一是由于开发人员运行的MongoDB版本实际上首先不适合生产。这可能是由于版本已过时,例如应更新为包含所有必要错误修复的较新版本的已停用版本。

或者可能是由于版本太早,尚未经过足够的测试以供生产使用。作为开发人员,我们通常热衷于使用我们工具的最新和最好的版本。我们还希望在开发的所有阶段保持一致,从初始构建和测试到生产,因为这减少了我们必须支持的变量数量、问题的可能性以及管理所有实例的成本。

对于某些人来说,这可能意味着使用尚未签署生产部署的版本。对于其他人来说,这可能意味着坚持使用经过尝试和信任的特定版本。从故障排除的角度来看,当问题在已批准用于生产但尚未部署的更高版本的 MongoDB 中修复时,这是一个问题。或者,您可能会忘记在后台“正常工作”的数据库实例,并在需要实现补丁时错过。

为此,您应该使用每个版本的发行说明定期检查您的版本是否适合生产。例如,MongoDB 5.0在其发行说明中提供了以下指导: 

https://www.mongodb.com/docs/upcoming/release-notes/5.0/

mongodb部署方式 mongodb 部署模式_服务器_02

此处的指导是使用 MongoDB 5.0.11,因为此版本具有所需的更新。如果不更新到此版本,将面临丢失数据的风险。

虽然坚持使用一个版本可能很诱人,但跟上升级对于防止生产中出现问题至关重要。您可能希望利用新添加的功能,但应首先通过测试过程对这些功能进行测试。在将它们投入生产之前,您希望查看它们是否会带来任何可能影响整体性能的问题。

最后,您应该检查MongoDB软件生命周期计划,并在每个版本的生命周期结束之前预测集群的升级:

https://www.mongodb.com/support-policy/lifecycles

生命周期结束版本不会收到补丁、错误修复或任何类型的改进。这可能会使您的数据库实例暴露并容易受到攻击。

从性能的角度来看,为您的生产应用程序获得正确版本的MongoDB需要“恰到好处” :不要超前,以至于您会遇到错误或其他问题,但也不要落后到会错过重要的更新。

▌MongoDB最佳实践#3:使用MongoDB复制来确保HA并经常检查副本的状态

副本集是一组 MongoDB 进程,它在用于应用程序的所有节点上维护相同的数据。它为您的数据提供冗余和数据可用性。当您在不同的数据库服务器上(甚至更好的是,在世界各地的不同数据中心)上拥有多个数据副本时,复制可在发生灾难时提供高级别的容错能力。

MongoDB副本集使用一个写入器节点(也称为主服务器)。最佳做法建议是始终具有奇数个成员。传统上,副本集至少具有三个实例:

· 主(写入器节点)

· 辅助(读取器节点)

· 辅助(读取器节点)

副本集的所有节点将协同工作,因为主节点将从应用服务器接收写入,然后将数据复制到辅助节点。如果主节点出现问题,副本集将选择辅助节点作为新的主节点。为了使此过程更高效地工作并确保顺利故障转移,副本集的所有节点必须具有相同的硬件配置。副本集的另一个优点是可以将读取操作发送到辅助服务器,从而提高数据库的读取可伸缩性。

将副本集部署到生产环境后,检查副本和节点的运行状况非常重要。MongoDB有两个重要的命令用于此目的:

  1. rs.status()使用从副本集的其他成员发送的检测信号数据包派生的数据,提供有关副本集当前状态的信息。这是一个非常有用的工具,用于检查副本集中所有节点的状态。
  2. rs.printSecondaryReplicationInfo()提供副本集状态的格式化报告。在数据复制时检查是否有任何辅助数据库位于主数据库后面非常有用,因为这会影响您在出现问题时恢复所有数据的能力。如果辅助数据库远远落后于主数据库,那么最终丢失的数据可能会比您满意的要多得多。

但是,请注意,这些命令提供的是时间点信息,而不是持续监视副本集的运行状况。在实际的生产环境中,或者如果要检查许多集群,运行这些命令可能会非常耗时且令人厌烦。因此,我们建议使用像Percona PMM这样的监控系统来监控您的集群。

▌MongoDB最佳实践#4:仅在必要时使用$regex查询,并在可能的情况下选择文本搜索

有时在数据库中搜索内容的最简单方法是使用正则表达式或$regex操作。许多开发人员选择这个选项,但实际上使用正则表达式会大规模地损害您的搜索操作。应避免使用$regex查询,尤其是当你的数据库很大的时候。

$regex查询会消耗大量的CPU时间,通常会非常缓慢和低效。创建索引并没有太大帮助,有时有索引的性能还不如没有索引的性能。

例如,让我们对1000万个文档的集合运行一个$regex查询,并使用.explain(true)来查看该查询需要多少毫秒。

没有索引:

> db.people.find({"name":{$regex: "Zelmar"}}).explain(true)
- -   Output omitted  - -
"executionStats" : {
"nReturned" : 19851,
"executionTimeMillis" : 4171,
"totalKeysExamined" : 0,
"totalDocsExamined" : 10000000,
- -   Output omitted  - -

如果我们在“名称”上创建了一个索引:

db.people.find({"name":{$regex: "Zelmar"}}).explain(true)
- -   Output omitted  - -
"executionStats" : {
"nReturned" : 19851,
"executionTimeMillis" : 4283,
"totalKeysExamined" : 10000000,
"totalDocsExamined" : 19851,
- -   Output omitted  - -

在这个例子中,我们可以看到索引并没有帮助提高$regex的性能。

新应用程序对搜索请求使用$regex操作是很常见的。这是因为当集合的大小很小且应用程序的用户很少时,开发人员和DBA在一开始都不会注意到任何性能问题。

然而,当集合变大,应用程序聚集更多用户时,$regex操作开始降低集群速度,成为团队的噩梦。随着时间的推移,随着应用程序的扩展,越来越多的用户想要执行搜索请求,性能水平可能会显著下降。

与其使用$regex查询,不如使用文本索引来支持文本搜索。文本搜索比$regex更有效,但需要提前向数据集添加文本索引。索引可以包括值为字符串或字符串元素数组的任何字段。一个集合只能有一个文本搜索索引,但该索引可以覆盖多个字段。

使用与上面示例相同的集合,我们可以使用文本搜索测试相同查询的执行时间:

> db.people.find({$text:{$search: "Zelmar"}}).explain(true)
- -   Output omitted  - -
"executionStages" : {
"nReturned" : 19851,
"executionTimeMillisEstimate" : 445,
"works" : 19852,
"advanced" : 19851,
- -   Output omitted  - -

实际上,同样的查询,使用文本搜索比使用$regex少花了4秒的时间。“数据库时间”中的四秒钟,更不用说在线申请时间。

总之,如果可以使用文本搜索解决查询,那么就这样做。将$regex查询限制在真正需要的用例中。

▌MongoDB最佳实践#5:明智地考虑您的索引策略

在一开始就对查询进行一些思考,随着时间的推移,会对性能产生巨大影响。首先,您需要了解您的应用程序以及您希望作为服务的一部分处理的查询类型。在此基础上,您可以创建一个支持它们的索引。

索引可以帮助加快读查询,但它会带来额外的存储成本,并且会降低写操作的速度。因此,您需要考虑应该为哪些字段建立索引,以避免创建过多的索引。

例如,如果您正在创建一个复合索引,那么必须遵循ESR (equal, Sort, Range)规则,并且使用索引对结果进行排序可以提高查询的速度。

同样,您总是可以检查您的查询是否真的使用了用.explain()创建的索引。有时我们会看到创建了索引的集合,但是查询要么不使用索引,要么完全使用了错误的索引。请务必仅创建实际用于读取查询的索引。拥有永远不会使用的索引是对存储空间的浪费,并且会降低写操作的速度。

在查看.explain()输出时,有三个重要的字段需要观察。例如:

keysExamined:0
docsExamined:207254
nreturned:0

在本例中,没有使用索引。我们知道这一点是因为检查的键的数量是0,而检查的文档的数量是207254。理想情况下,查询的比率应该是nreturned/ keysexamining =1。例如:

keysExamined:5
docsExamined: 0
nreturned:5

最后,如果.explain()显示特定查询使用了错误的索引,则可以使用.hint()强制该查询使用特定索引。在查询中调用.hint()方法会覆盖MongoDB的默认索引选择和查询优化过程,允许您指定所使用的索引,或者执行正向收集或反向收集扫描。

▌MongoDB最佳实践#6:经常检查查询和索引

每个数据库都是独一无二的,并且特定于其应用程序,因此它随时间增长和变化的方式也是如此。没有人知道应用程序在数月和数年内将如何增长,或者查询将如何变化。无论您做出什么假设,您的预测都不可避免地是错误的,因此定期检查数据库和索引至关重要。

例如,您可能计划使用特定的查询优化方法和特定的索引,但一年后意识到很少有查询使用该索引,因此不再需要。继续使用此方法将花费更多的存储成本,同时不会提高应用程序性能。

因此,有必要执行查询优化并经常查看每个集合的索引。

MongoDB有一些工具来进行查询优化,如数据库分析器或.explain()方法。我们建议使用它们来发现哪些查询速度较慢,查询如何使用索引,以及您可能需要在哪些方面改进优化。除了删除没有有效使用的索引外,还要注意不需要运行的重复索引。

在Percona,我们使用脚本来检查是否存在重复的索引,或者是否有任何未使用的索引。您可以在我们在 GitHub 上的存储库中找到它们:

https://github.com/percona/support-snippets/tree/master/mongodb/scripts

同样,您可以考虑要从查询中获得多少结果,因为提供太多结果会影响性能。有时,您只需要查询的前五个结果,而不是数十个或数百个响应。在这些情况下,可以使用.limit()限制查询结果的数量。

另一种有用的方法是使用投影来仅获取必要的数据。如果只需要文档的一个字段,请使用投影而不是检索整个文档,然后在应用程序端进行筛选。

最后,如果需要对查询结果进行排序,请确保使用的是索引并利用它来提高效率。

▌MongoDB 最佳实践 #7:不要在同一台服务器上运行多个 mongod 或 mongos 实例

即使可以在同一台服务器上使用不同的进程和端口运行多个 mongod 或 mongos 实例,我们也强烈建议不要这样做。

当您在同一台服务器上运行多个 mongod 或 mongos 进程时,很难监控它们以及它们消耗的资源(CPU、RAM、网络等)。因此,当出现问题时,很难找出正在发生的事情并找到问题的根本原因。

我们看到很多情况下,客户在服务器上遇到了资源问题,但由于他们正在运行多个 mongod 或 mongos 实例,即使发现哪个特定进程有问题也很困难。这使得故障排除极具挑战性。

同样,在某些情况下,开发人员已经实现了分片集群来扩展其应用程序数据,我们看到多个分片在同一台服务器上运行。在这些情况下,路由器将向同一节点发送大量查询,使节点过载并导致性能不佳——这与分片策略想要实现的目标完全相反。

此处最坏的情况涉及副本集。想象一下,运行副本集以实现复原能力和可用性,然后发现副本集的两个或多个成员在同一台服务器上运行。这是灾难和数据丢失的秘诀。您不会为复原能力构建应用程序,而是使整个部署更有可能失败。

▌MongoDB最佳实践#8:经常备份

因此,您有一个具有复制功能的群集,但您想睡得更好吗?经常运行数据备份。如果需要从计划外事件中恢复,频繁备份可以使您从较早的时刻恢复数据。

备份MongoDB数据有许多不同的选项:

Mongodump / Mongorestore

Mongodump从MongoDB读取数据并创建一个BSON文件,Mongorestore可以使用它来填充MongoDB数据库。这些为备份小型MongoDB部署提供了有效的工具。从好的方面来说,您可以选择特定的数据库或集合进行有效备份,并且此方法不需要停止节点上的写入。但是,此方法不会备份已创建的任何索引,因此在还原时,需要再次重新创建这些索引。逻辑备份通常非常缓慢且耗时,因此您必须在还原过程中考虑该时间。最后,对于部署更复杂的分片集群,不建议使用此方法。

Percona Backup for MongoDB

Percona Backup for MongoDB是一种开源,分布式和低影响的解决方案,用于MongoDB分片集群和副本集的一致备份。它支持 MongoDB 服务器、副本集和分片集群的备份。它可以支持逻辑、物理和时间点恢复备份,以及备份到任何位置,包括 AWS S3、Azure 或文件系统存储类型。

但是,它确实需要在要保护的所有节点上进行初始设置和配置。

物理/文件系统备份

您可以通过复制MongoDB的底层数据文件来创建MongoDB部署的备份。您可以对这种类型的备份使用不同的方法,从手动复制数据文件到逻辑卷管理 (LVM) 快照,再到基于云的快照。这些通常比逻辑备份更快,并且可以将它们复制或共享到远程服务器。这种方法特别推荐用于大型数据集,在同一集群上构建新节点时很方便。

不利的一面是,还原时无法选择特定的数据库或集合,并且无法执行增量备份。此外,建议运行专用节点进行备份,因为它需要停止写入,这会影响应用程序性能。

▌MongoDB最佳实践#9:知道何时对副本集进行分片并仔细选择一个分片键

分片是您可以使用MongoDB部署的最复杂的架构。

随着数据库的增长,您将需要向服务器添加更多容量。这可能涉及添加更多 RAM、更多 I/O 容量,甚至更强大的 CPU 来处理。这称为垂直缩放。

但是,如果数据库增长太多,超过了单台计算机的容量,则可能需要拆分工作负载。有几个原因可能导致这种情况。例如,可能没有足够大的物理服务器来处理工作负载,或者服务器实例的成本太高,以至于运行起来负担不起。在这些情况下,您需要开始考虑水平缩放。

水平扩展涉及将数据库划分到多个服务器上,并根据需要添加其他服务器以增加容量。对于MongoDB,此过程称为分片,它依赖于Shard Key(分片键)来管理如何在机器之间分配工作负载。 

选择分片键可能是您在管理 MongoDB 时面临的最困难的任务。在选择键之前,有必要研究数据集和查询并提前计划,因为一旦执行分片,就很难恢复分片。对于MongoDB 4.2及更早版本,分配分片键是一个无法撤消的单向过程。对于 MongoDB 4.4 及更高版本,可以优化分片键,而 MongoDB 5.0 及更高版本允许您使用reshardCollection命令更改分片键。

如果您选择错误的分片键,那么很大一部分文档可能会转到其中一个分片,而只有少数文档会转到另一个分片。这将使分片集群不平衡,从而随着时间的推移影响性能。当选择单调增长的键来分片集合时,通常会发生不平衡的集群,因为超过给定值的所有文件都将转到一个分片,而不是均匀分布。

除了查看用于分片数据的值外,您还需要考虑将在分片中发生的查询。查询必须使用分片键,以便 mongos 进程在分片集群中分发查询。如果查询不使用分片键,则 mongos 会将查询发送到集群的每个分片,从而影响性能并使分片策略效率低下。

▌MongoDB最佳实践#10:不要在问题上砸钱

最后但并非最不重要的一点是,经常看到团队在数据库问题上投入资金。但是,与其立即伸手去拿信用卡,不如先尝试横向思考并想象更好的解决方案。

添加更多 RAM、添加更多 CPU 或移动到更大的实例或更大的计算机可以克服性能问题。但是,如果不首先分析根本问题或瓶颈,这样做可能会导致将来出现更多相同类型的问题。在大多数情况下,答案不是在资源上花费更多的钱,而是研究如何优化您的实现,以便在相同级别上获得更好的性能。

尽管云服务可以轻松扩展实例,但效率低下的成本可能会迅速增加。更糟糕的是,这是一项持续的费用,将随着时间的推移而持续下去。通过首先查看查询优化和性能等领域,可以避免额外的支出。对于与我们合作过的一些客户来说,降级其 EC2 实例的能力为他们的公司节省了大量月费。

作为一般建议,请采取节省成本的思维方式,在添加硬件或增强云实例之前,请花时间分析问题并考虑更好的长期解决方案。