• [技术干货] mongodb与mysql的对比
    概观关系数据库几十年来一直是企业应用程序的基础,自1995年MySQL发布以来,它一直是一种流行且廉价的选择,特别是作为早期Web应用程序的普遍LAMP堆栈的一部分。今天,现代企业正在考虑更好的方式来存储和管理他们的数据 - 无论是为了获得更好的客户洞察力,适应不断变化的用户期望,还是通过新的应用程序和商业模式将竞争对手推向市场。因此,推动早期关系数据库开发的许多假设已经发生了变化: [*]要求更高的开发人员生产力和更快的上市时间,传统的僵化的关系数据模型和瀑布式开发的单片应用程序让位于敏捷方法,微服务和全栈开发,将发布周期从几个月,几年压缩到几天和几周。 [*]需要管理新的,快速变化的数据类型的大规模增长 - 由新型Web,移动,社交和物联网应用程序生成的结构化,半结构化和多态数据。 [*]全面转向分布式系统和云计算,使开发人员能够利用随需应变,高度可扩展的计算和存储基础架构,并且能够为全球任何地点的工作和观众提供服务,同时满足全新的监管需求数据主权。 因此,为了解决新应用程序的需求并实现现有工作负载的现代化,出现了像MongoDB这样的非关系型数据库。MongoDB 4.0将会在2018年夏季,计划的支持多文档ACID事务,将使开发人员更容易使用。什么是MySQL?MySQL是一个流行的开源关系数据库管理系统(RDBMS),由Oracle公司开发,分发和支持。像其他关系数据库一样,MySQL将数据存储在表中,并使用结构化查询语言(SQL)进行数据库访问。在MySQL中,您可以根据需求预先定义数据库模式,并设置规则来管理表中字段之间的关系。模式中的任何更改都需要执行迁移过程,以使数据库脱机或显着降低应用程序性能。什么是MongoDB?MongoDB是由MongoDB,Inc.开发的开源非关系数据库。MongoDB将数据存储为称为BSON(二进制JSON)的二进制表示形式的文档。相关信息通过MongoDB查询语言一起存储以便快速查询访问。字段可以因文档而异; 不需要向系统声明文档的结构 - 文档是自描述的。如果需要将新字段添加到文档中,则可以在不影响集合中的所有其他文档的情况下创建该字段,而无需更新中央系统目录,也不需要使系统脱机。或者,可以使用模式验证来对每个集合实施数据管理控制。MongoDB的文档数据模型自然映射到应用程序代码中的对象,使开发人员可以轻松学习和使用。文档使您可以轻松地表示层次关系来存储数组和其他更复杂的结构。为10种以上语言提供了原生的,惯用的驱动程序 - 社区已经构建了数十种语言 - 支持即时查询,实时聚合和丰富的索引,以提供强大的编程方式来访问和分析任何结构的数据。由于文档可以汇集相关数据,否则这些数据将在关系模式中通过单独的父 - 子表建模,因此MongoDB的原子单文档操作已经提供了符合大多数应用程序的数据完整性需求的事务语义。一个或多个字段可以在单个操作中写入,包括更新多个子文档和数组的元素。MongoDB提供的保证确保在文档更新时完全隔离; 任何错误都会导致操作回滚,以便客户端收到文档的一致视图。预定于2018年夏季的MongoDB 4.0将增加对多文档事务的支持,使其成为唯一一个将传统关系数据库的ACID保证,文档模型的速度,灵活性和强大功能与智能分布式系统设计来扩展并将数据放在需要的地方。通过快照隔离,事务将提供全局一致的数据视图,并执行全有或全无执行来维护数据完整性。MongoDB中的事务就像开发人员在MySQL中熟悉的事务一样。它们将是多语句,具有类似的语法(例如: start_transaction和commit_transaction),因此对于任何具有交易经验的人员都可以轻松添加到任何应用程序中。与MySQL和其他关系数据库不同,MongoDB建立在分布式系统架构上,而不是单一的单节点设计。因此,MongoDB提供了开箱即用的自动分片功能的横向扩展和数据本地化功能,以及具有高可用特性的副本集。术语和概念MySQL中的许多概念在MongoDB中都有类似的特征。下表概述了MySQL和MongoDB的通用概念。MySQLMongoDBACID事务ACID事务*[tr=rgb(248, 248, 248)]TableCollectionRowDocument[tr=rgb(248, 248, 248)]ColumnFieldSecondary IndexSecondary Index[tr=rgb(248, 248, 248)]JOINsEmbedded documents, $lookup & $graphLookupGROUP_BYAggregation PipelineACID事务计划为MongoDB 4.0 *功能比较和MySQL一样,MongoDB提供了一套丰富的特性和功能,远远超出了简单的NoSQL数据存储所提供的功能和功能。MongoDB具有丰富的查询语言,功能强大的二级索引(包括文本搜索和地理空间),强大的数据分析聚合框架,分面搜索,图形处理等等。使用MongoDB,您还可以在关系数据库中使用更多不同数据类型的这些功能,并且大规模使用。MySQL的MongoDB的NoSQL数据存储开源是是是[tr=rgb(248, 248, 248)]ACID事务是是* 1没有灵活丰富的数据模型没有是部分:架构灵活性,但仅支持简单的数据结构[tr=rgb(248, 248, 248)]模式管理是是没有expressive joins,分面搜索,图表查询,强大的聚合是是没有[tr=rgb(248, 248, 248)]原生语言驱动支持没有是没有数据局部性的水平扩展没有是部分:不控制数据局部性[tr=rgb(248, 248, 248)]对接分析系统和BI系统是是没有企业级安全和成熟的管理工具是是没有[tr=rgb(248, 248, 248)]数据库作为所有主要云端的服务是是没有[*]1预定MongoDB 4.0,2018年夏季 查询语言MySQL和MongoDB都有丰富的查询语言。可以在MongoDB文档中找到完整的语句列表。MySQL的MongoDB的INSERT INTO用户(user_id,年龄,状态)VALUES('bcd001',45,'A')db.users.insert({ user_id:'bcd001', 年龄:45岁, 状态:'A'})[tr=rgb(248, 248, 248)]SELECT * FROM用户db.users.find()更新用户SET状态='C'年龄> 25岁db.users.update( {年龄:{$ gt:25}}, {$ set:{status:'C'}}, {multi:true})为什么使用MongoDB而不是MySQL?各种规模的组织都在采用MongoDB,因为它使他们能够更快地构建应用程序,处理高度多样化的数据类型,并在规模上更高效地管理应用程序。随着MongoDB文档自然地映射到现代的面向对象的编程语言,开发被简化了。使用MongoDB将删除将代码中的对象转换为关系表的复杂对象关系映射(ORM)层。MongoDB灵活的数据模型也意味着您的数据库模式可以随业务需求而变化。MongoDB也可以扩展到多个分布式数据中心内部,从而提供以前在像MySQL这样的关系数据库中无法实现的新的可用性和可伸缩性级别。随着您的部署在数据量和吞吐量方面的增长,MongoDB无需停机即可轻松扩展,并且无需更改应用程序。相比之下,为了达到MySQL的规模,往往需要大量的定制工程工作。JSON文档的开发人员生产力事实证明,将数据作为灵活的JSON文档而不是刚性的行和列处理,可以帮助开发人员更快地移动。从关系型数据库迁移到MongoDB之后,找到能够将开发周期缩短4倍或5倍的团队并不困难。为什么是这样?: [*]文件是很自然的文档以与应用程序相同的方式表示数据。与关系数据库的表格行和列不同,数据可以使用数组和子文档进行结构化 - 与应用程序分别表示数据相同,分别作为列表和成员/实例变量。这使得开发人员可以更简单快速地模拟应用程序中的数据如何映射到存储在数据库中的数据。 [*]文件是灵活的每个文档都可以存储具有其他文档不同属性的数据。例如,考虑一个产品目录,其中存储男士服装细节的文档将存储来自存储平板电脑细节的文档的不同属性。这是一种通常称为“多态性”的属性。使用JSON文档,我们可以在需要时添加新属性,而无需更改集中式数据库模式。在最坏的情况下,这最多会导致关闭数据库中的停机时间显着增加。灵活性文档带来的开发人员可以更轻松地处理由现代移动,Web和物联网应用程序生成的半结构化和非结构化数据。 [*]文件使应用程序更快。对于存储在单个文档中的实体数据,而不是跨多个关系表分布,数据库只需要读取和写入一个地方。将对象的所有数据放在一个地方也使开发人员更容易理解和优化查询性能。 正是出于这些原因,MySQL和其他关系数据库已经增加了对JSON的支持。但是,简单地添加JSON数据类型并不会将开发人员的生产力优势从文档数据库带给MySQL。为什么?因为MySQL的方法可能会降低开发人员的生产力,而不是改进它。考虑以下: [*]专有扩展:查询和操作JSON文档的内容需要使用单独的MySQL特定的SQL函数来访问值,这对大多数开发人员来说并不熟悉。另外,它们不受第三方SQL工具(例如BI平台,数据仓库连接器,ETL和ESB管道等)的支持或认可。 [*]MongoDB与MongoDB有何不同: MongoDB API被广泛理解,并被行业标准工具和连接器所采用。几家大型供应商数据库公司甚至自己也采用了MongoDB API。 [*]传统关系开销:即使有JSON支持,MySQL用户仍然与多层SQL /关系功能绑定,以与JSON数据 - 低级JDBC / ODBC驱动程序和对象关系映射程序(ORM)进行交互。对于有经验的MySQL开发人员来说,这些层可能是熟悉的,但对于许多其他开发人员来说,他们想通过API来与文档和数据进行交互,而这些API对于他们的编程语言来说是自然和惯用的,这些层会带来很高的学习开销 对于性能和查询效率来说,ORM通常也很难被优化 - 即使对于有经验的关系型开发人员也是如此。另外,JSON数据的查询优化统计信息比常规关系数据类型的查询优化统计信息更加有限。 [*]MongoDB与MongoDB的不同之处在于: MongoDB驱动程序在开发人员使用的编程语言中习惯于自然的方法和函数中实现。 [*]复杂数据处理:当使用JSON数据时,MySQL驱动程序不具备将JSON正确和精确地转换为应用程序使用的有用本机数据类型的功能。这包括不同类型的数值(例如浮点,64位整数,小数)时间戳和日期,或Java中的映射或列表或Python中的字典或列表。相反,开发人员必须在他们的应用程序中手动转换基于文本的JSON,从而失去了在不同文档中使用多个数据类型的字段(多态性)的能力,并且使值的计算,排序和比较变得困难且容易出错。 [*]MongoDB如何不同: MongoDB及其驱动程序使用的二进制编码JSON(BSON)支持常规基于文本的JSON不支持的高级数据类型。 [*]没有数据治理: MySQL没有提供本机制来验证在数据库中**或更新的JSON模式,因此开发人员需要添加应用程序或数据库端功能以对数据应用治理控制。 [*]与MongoDB有什么不同: 基于JSON Schema IETF标准的Schema验证允许开发人员和DBA为每个MongoDB集合定义和实施规定的模式结构。 [*]架构刚性: MySQL用户仍然需要为常规关系数据定义架构。如果模式随后被修改以适应新的应用程序需求,则该表针对某些操作被锁定,直到现有数据被复制到新模式中,这就要求在模式迁移期间应用程序停顿。 [*]MongoDB的不同之处:开发人员和数据库管理员可以将完全动态模式的灵活性与某些应用程序所需的治理控制相结合,而不是只存储数据库中的所有数据。 用户通过MySQL选择MongoDB正如下面的例子所说明的,MongoDB在MySQL上的选择是由对开发人员生产力,应用程序性能和规模的彻底改进所驱动的: [*]百度于2012年开始使用MongoDB,在达到性能和可伸缩性限制后,最初从MySQL迁移其用户地址簿服务。现在,超过100种不同的产品和服务由运行在650节点集群上的MongoDB提供支持,部署在多个数据中心内,存储超过1PB的数据。MySQL灵活的文档数据模型可以扩展MongoDB的分布式设计和开发人员的生产力。 [*]Experian Health选择MongoDB作为MySQL和其他关系数据库的动力,为它的通用标识管理器提供支持,该标识管理器是公司用于唯一识别医疗保健客户的新应用程序。选择MongoDB是因为其灵活的数据模型,可扩展性,强大的性能和总体成本。在关系数据库上开发Universal Identity Manager意味着Experian Health开发人员需要执行多达10个SQL连接才能正确匹配患者的身份。使用MongoDB允许公司简化其模式以消除复杂性; 大幅减少查询次数; 提高性能; 并随着数据量的增加而简化增长。 [*]Viacom媒体网络,MTV,喜剧中心,Nickelodeon和其他几十个品牌的主场,在从MySQL迁移后,在MongoDB上构建了大量的轮询,投票和数据收集服务。MongoDB灵活的文档数据模型,横向扩展设计和丰富的聚合流水线使Viacom能够大规模捕获和分析数据。 [*]生物技术巨头Thermo Fisher 将从AWS Aurora上的MySQL迁移到MongoDB以及完全托管的Atlas数据库即服务后实验时间从几天缩短到几分钟。除了以40倍以下的代码提供6倍的性能以外,MongoDB还有助于降低应用程序的架构复杂性。 MongoDB的常见用例是什么?MongoDB是一个通用数据库,用于各种用例。MongoDB最常见的用例包括, 单一视图,物联网,移动,实时分析,个性化,目录和内容管理。随着多文档交易的增加,MongoDB将更加容易处理一系列的用例。MySQL何时更适合?虽然许多组织已经成功从RDBMS迁移到MongoDB,但您不能将MongoDB作为以关系数据模型和SQL为基础构建的遗留应用程序的替代品。然而,组织正从将关键任务,创收应用现代化到MongoDB中受益。例如,思科将其电子商务平台从传统关系数据库迁移到MongoDB。因此,它通过降低8倍延迟并消除系统升级期间的停机时间,改善了客户体验。其开发团队可以更快地构建和发布新的应用程序,而公司的电子商务平台可以利用云计算所带来的业务敏捷性。想要了解更多?将RDBMS获取到MongoDB迁移指南由于我们今天构建和运行应用程序的方式,以及数据源和用户负载的增长,关系数据库正在被超越其极限。为了应对这些挑战,像MTV和思科这样的公司已经成功地从关系数据库迁移到MongoDB。在本白皮书中,您将了解到: [*]一步一步如何从关系数据库迁移到MongoDB。 [*]相关的技术考虑因素,例如关系数据模型和文档数据模型之间的差异以及对模式设计的影响。 [*]索引,查询,应用程序集成和数据迁移。
  • [技术干货] 什么是分片集群(Sharding Cluster)
    本帖最后由 lipo 于 2018-4-8 22:06 编辑什么是分片集群 分片集可以说是MongoDB的高级形态,它里面存储数据的部分由副本集组成,比如每个片都是一个副本集,存储元数据(metadata)的config也是一个副本集,这样集群便具有了副本集的高可用,高容灾性。下图为一个典型的三分片副本集,图中每个蓝色块均可以理解为一个独立的数据库节点。 13690· 一个典型的 3 Shard分片集 分片集的应用场景:从图中不难理解,分片集对比副本集的优势,之前由一个副本集承担的读写任务,可以同时由三个分片共同承担。因此,分片集主要的应用场景为以下几个方面: [*]磁盘资源遭遇瓶颈 [*]写需求超过单个MongoDB节点的写服务能力 [*]活跃的数据集合超出单机内存容量,导致很多请求要从磁盘读取数据,影响性能 分片集的成员: [*]mongos mongos可以看做是副本集的入口和出口,mongos本身不存储任何数据,用户访问一个分片集群,做增删改查各种命令,都是通过mongos进行的,用户可以完全不用感知集群的其他成员组成,mongodb会帮你协调好一切。一般一个应用程序使用一个mongos,mongos本身不存储任何数据。 [*]shard shard本身是一个副本集,既然是副本集便要存储数据,用户使用分片集的时候,**的数据均存储在shard中,如果对目的数据**的集合不做任何限制,mongodb默认不会将此集合存储到多个分片,会全部存储在primary shard上。对集合开启分片设置后,对此集合的**和读取,则会均分到各shard上进行。 [*]config config本身是一个副本集,很明显config内也存储着数据,它里面存储的是元数据(metadata),元数据,通俗的说,就是记录我们真实**数据的“账本数据”,也就是说,元数据包含了我们设置的数据库用户、角色信息,更为关键的是,元数据记录了我们的数据在各分片的分布,这样当新数据**请求通过mongos下发后,config可以知道将新数据**哪个分片,当读取请求通过mongos下发到达后,config可以告诉mongos去哪个分片可以读到想要的数据。 [*]就各角色的功能,通俗一些的来说的话,如果把我们的数据比做一条鱼,那么我们有一条大鱼,由于太大了,要把鱼头,鱼身,鱼尾巴,分别放在仓库shard1,shard2,shard3中。放好以后,我们记性不太好,得把鱼头鱼身鱼尾巴到底放到哪个仓库去了,记在一个小本本上,这个小本本就是ConfigServer。如果发生查询操作,就像当前来了一个客户(Client),要买鱼头,客户直接去找我们任命的销售员(Mongos),销售员(Mongos)去查小本本(ConfigServer),然后查到鱼头放在仓库Shard1,就去Shard1取到鱼头,交给用户(Client)。
  • MongoDB 分片集群balancer浅谈
    本帖最后由 danglf 于 2018-4-8 20:24 编辑MongoDB 分片集群balancer浅谈本文基于MongoDB 3.2系列, 最新版本的MongoDB的balancer流程,应该会和3.2有不一致的地方。在开始之前,先说几个基本的概念:Shard: 分片,存储数据,提供高可用性和数据的一致性。分片集群中,每个shard一般都是一个复制集。mongs:客户端应用程序直接操作分片的接口。查询路由处理和定位操作到分片中并返回相关数据到客户端。一个分片集群包含多个查询路由来划分客户端的请求压力。configservers:配置服务器,存储集群中的元数据。这些数据包含集群数据到分片的映射。查询路由使用这些元数据定位操作到明确的分片中。共享集群需要有3台配置服务器。MongoDBbalancer 是一个运行在mongos中的后台线程, 该线程的主要作用就是监控shard上的chunk数量 ,如果发现某个shard上的Chunk达到一定的数量,则balancer会长期自动的将chunk数量迁移到其他shard,保证所有shard拥有的chunk数量基本一致。Balancer的整个运行过程对用户和应用完全是透明的,但是运行过程中,对整个集群的性能有一定的影响。 13688 任何一个集群中的mongos实例可以触发一次平衡过程, 当一个balancer进程激活的时候, 负责本次过程的mongos 节点, 会通过修改config数据库中的一个文档来获取锁, 每次都只有mongos负责一次balance。 Chunk 迁移过程可能会对集群的带宽和性能有一定的影响,, balancer一般通过如下方式来最小化这种影响:一次只迁移一个Chunk只有等shard 之间的chunk数量达到一定的门限值之后, 才会启动。另外管理员也可以零时的关闭balancer或者指定balancer的影响时间窗。当新加入一个shard到集群中的时候, 会导致一个不平衡, 因为新的shard上面没有任何的chunk, 所有Mongodb 开始将数据迁移到新的shard的时候,会花费一定时间。当把一个shard 从一个集群中移除后, 会发生类似的过程, balancer需要花费一定时间,把这个shard上的数据重分布到其他shard, 所以在移除shard的过程中,一定不要关闭被移除的服务器。 Chunk 迁移的大概流程: 1 balancer 首先向源shard发送一个moveChunk命令。2源shard内部开始数据迁移, 在迁移的整个过程中,对于该chunk的写入操作到落到源shard上面, 源shard接受所有写请求。3目标shard会建立数据索引。4目标shard会向源shard请求该chunk对应的文档,然后开始接受数据拷贝。5当接受到了该chunk的最后一个chunk,目标shard启动了一个同步过程,以便确保在迁移过程中的数据也都同步过来。6当同步过程结束后, 源shard会连接到config数据库, 然后更新下集群的元数据信息,这些信息包括了最新的chunk分布数据。7当源shard完成了元数据的更新后, 一旦该chunk上面没有打开的游标了, 源shard将会删除该chunk中的文档。迁移过程的限制 为了最小化平衡对集群的影响,balancer 触发条件有一定的限制,只有当多个shard之间(如果「拥有最多数量chunk的shard」与「拥有最少数量chunk的shard」的差值超过某个阈值,就会触发chunk迁移)到了一定程度才会触发迁移: 13689 异步chunk迁移清理为了迁移一个shard上的多个chunk,balancer一次迁移个chunk, 尽管如此, balancer不需要等待当前迁移过程的删除阶段结束后再去开启下一个chunk的迁移。这种方式可以加速对于那种非常不均衡集群的迁移。
  • [技术干货] Mongodump浅解(3.2版本)
    本帖最后由 叶亮 于 2018-4-8 20:38 编辑原文:https://docs.mongodb.com/v3.2/reference/program/mongodump/index.html 摘要 mongodump命令,用于数据库内容的导出(二进制),可以应用于mongod实例和mongos实例。 对于基于查询需求的数据转存,或者系统间的数据同步,或者单机的数据转存等,mongodump和mongorestore结合起来,可以作为完整备份方案的一部分。 然而,就集群和副本集来说,若要使用mongodump和mongorestore作为完整的备份方案,那么这两个工具命令仍然存在些许不足的地方。 命令行为 1,数据排除——mongodump命令,导出的备份文件中,不包含本地数据库的内容。 2,它仅捕获数据库中的文档数据,不备份索引数据。因此,用mongorestore或者mongod恢复后,必须进行索引重建。 版本兼容 版本2.2以后mongodump备份的数据,不适用于更早版本的mongod命令。因此,要避免使用新版的mongodump去备份旧版的数据库数据。 数据读取优先策略 1,副本集实例 版本3.2.0的修改:连接副本集时,--host参数指定的一个或多个目标,会影响到mongodump的数据读取策略。 如果--host的参数值,是以副本集的名字开头,那么mongodump会默认从副本集的主节点读取数据进行备份。如下: --host "replSet/rep1.example.net:27017,rep2.example.net:27017,rep3.example.net:27017" 如果--host的参数值,包含了一组mongod实例的信息,但不以副本集的名字开头。那么mongodump会默认从最近的节点读取数据进行备份。如下: --host "rep1.example.net:27017,rep2.example.net:27017,rep3.example.net:27017" 2,集群实例 版本3.0.5的修改:对于分片为副本集的集群实例,在mongs实例上运行mongodump时,不再优先从secondary节点读取数据。 对于集群,为--host参数指定host名称,mongodump从集群中每个副本集的主节点读取数据。 集群中没有分片的数据集合保存在主节点上,mongodump从主节点上读取还未分片的数据。 *每个数据库,都有自己的主分片。 文件覆盖 如果文件存在,mongodump覆盖已存在的文件。默认输出在mongo目录的dump文件夹下。 数据压缩处理 对于副本集实例,用的是WiredTiger存储引擎。mongodump输出的是未压缩的数据。 工作集 mongodump会影响数据库性能。如果数据大于系统内存,会导致内存溢出。 访问 在一个可访问的MongDB上运行mongodump,必须要对每个数据库有find权限。内嵌的备份角色,提供了对所有数据库的备份权限。 版本3.2.1修改:备份角色添加了对system.profile的备份权限。该文件生命周期存在于备份归档的过程中。而在此之前的备份过程,用户需要专门对该部分集合进行读取。
  • Mongodb查询中游标的使用及相关知识
    本帖最后由 Nosql_newbee 于 2018-4-6 17:03 编辑 游标的使用: MongoDB中,db.collection.find()方法返回一个游标,你可以通过迭代游标来访问文档的内容,在mongo shell中如果没将返回的cursor赋予局部变量(用var定义的变量),游标会自动迭代数据,默认为20行,这就是直接在shell中执行db.user.find()的结果。将返回结果赋予局部变量就可以手动的迭代访问cursor中的数据了。 首先在数据库中**测试数据 for(i=0;i100;i++){db.user.insert({"x":i});} var myCursor = db.user.find(); 可以通过next()方法来访问游标中数据 while(myCursor.hasNext()){ print(tojson(myCursor.next())); } hasNext()检查是否有后续结果存在。 还可以通过forEach()来迭代cursor访问文档, myCursor.forEach(printJson); https://docs.mongodb.com/manual/reference/method/#js-query-cursor-methods 可以在这个网址查看更多的cursor方法。 调用find时,shell并不立即查询数据库,而是等待真正开始要求获得结果时才发送查询,这样在执行之前可以给查询附加额外的选项,几乎游标对象的每个方法都 返回游标本身,这样就可以按任意顺序组成方法链。例如下面几种方法的表达式等价的: var myCursor = db.user.find().sort({"x":1}).limit(1).skip(10); var myCursor = db.user.find().limit(1).sort({"x":1}).skip(10); var myCursor = db.user.find().skip(10).limit(1).sort({"x":1}); 此时,查询还没有真正的执行,所有这些函数都只是构造查询,现在进行如下操作, myCursor.hasNext() 这时,查询被发往服务器。shell立刻获得前100个或前4M的数据(两者之间较小的),这样下次调用next()就不必再次链接服务器获取结果了,客户端使用完第一组数据时,shell会再一次联系数据库,使用getMore请求提取更多的结果。getMore请求包含一个查询标示符,项数据查询是否还有更多的结果,这个过程会一直持续到游标耗尽或者结果全部返回。 limit,skip和sort 要限制结果数量,可在find后使用limit函数,例如,只返回3个结果,可以这样限制 db.user.find().limit(3) 要是匹配的结果不到3个,则返回匹配数量的结果。limit指定的上限,skip与limit类似 db.find().skip(3) 该操作会忽略前三个匹配的文档,然后返回剩余的文档,如果集合中能匹配的文档少于3个,则不会返回任何文档。 sort接受一个对象作为参数,这个对象是一组键/值对,键对应文档的键名,值代表排序的方向。排序的方向1(升序),-1(降序),如果指定了多个键,则按照这些键被指定的顺序逐个排序,例如,按照"username"升序,按照"age"降序,可以这样写 db.user.find().sort({"username":1,"age":-1}) 这三个方法组合使用,对于分页非常有用,例如在线商店,要显示商品,每页50个,按照价格降序排列,可以这样写: db.user.find({"name":"mp3"}).limit(50).sort({"price":1}) 当用户点击下一页时, db.user.find({"name":"mp3"}).limit(50).sort({"price":1}).skip(50) 然而,skip过多的结果会导致性能问题。
  • [技术干货] Mongodb 版本升级与降级
    几年前mongodb云服务还未普及,许多客户都是自己购买虚拟机,然后再在虚拟机上搭建自己的mongodb 服务,自己运维mongodb,这是前些年大部分客户的业务场景。最近Mongodb 版本迭代数据越来越快,这不自己的版本才刚熟悉起来,可上官网一看,官网上最新的版本早已超出自己运营的版本好几代了,最新的版本又是各自新特性,各种性能的提高,看的心痒痒,要不咱的老版本也升级下?下面就详细说说升级的准备工作和步骤以及怎样确认升级是否成功,如果一旦升级失败,怎么降级来保障业务不受处理 升级前的准备工作先确认目前业务运营的mongodb的版本,在mongodb二进制目录下执行mongod --version,输出结果中有db version字段,这个字段显示的就是你当前的mongodb版本。然后确认下你想升级的mongodb版本。如果工作版本和目标版本相差比较大,那就必须一步一步的向上升级,最后达到目标版本。例如目前工作的版本是3.0,目标版本是3.6。那就必须先从3.0升级到3.2,再从3.2升级到3.4,最后在从3.4升级到3.6。在官网可以查到具体版本之间的升级,下图左面就是mongodb各个版本之间的关系了。13501 升级步骤 假设目前我们的工作版本是3.2,我们想升级的版本是3.4。我们点击Release Note for MongoDB 3.4,找到Upgrade Procedures章节13502Upgrade Procedures章节按照mongodb部署的三种形态做了区分,不同形态的升级方式不一样,我们先确认自己的部署形态然后做查看相应的升级章节(3.2升级到3.4的步骤详情可以参考我的前一篇文章: http://forum.huaweicloud.com/thread-7637-1-1.html) 版本升级成功后需要注意的问题版本兼容性是版本升级最重要的问题,当从低版本升级到高版本,高版本肯定有一些特性是低版本没有的,在低版本刚升级到高版本后,一定先让我们的业务程序运行一段时间,只有在线上正常运行一段时间后,我们才可以开始使用非兼容的新特性。我们在目标数据库上执行db.adminCommand( { setFeatureCompatibilityVersion:"3.4" } ),执行完这个命令后才表示打开3.4数据库版本的非兼容特性,你的应用程序才能开始使用3.4的非兼容新特性。 版本降级如果升级到高版本一段时间后,因为各种原因想继续使用以前的老版本,那就需要进行版本降级处理。版本降级中最复杂的问题还是兼容特性的处理,如果你已经使用了高版本的一些新特性,而这些特性在低版本中是不支持的,那么将版本降级到低版本mongodb肯定会运行出错。那么在替换 老版本的二进制程序前,我们首先需要移除高版本里面的非兼容特性,官网上有移除高版本非兼容特性的指导步骤,执行这些步骤就可以把非兼容的特性去掉,原理就是这些非兼容特性在生成的时候其实都在每个记录里面增加一个特殊的字段用于标明是新特性,查找含有这些标记的记录然后将其进行降级处理。昨晚非兼容特性降级处理后就可以执行二进制降级了,将高版本的二进制降级成低版本的二进制,步骤可以参考官网文档.13503
  • [技术干货] mongodb 查询命令及参数详解(1)
    本帖最后由 Nosql_newbee 于 2018-4-2 08:53 编辑 本文将详细介绍mongodb查询相关命令,参数,主要包括以下几个方面: 用find或者findone函数查询; $条件查询; 特定类型的查询。 首先**测试数据 db.user.insert([{"username":"test01","email":"test@01","age":10},{"username":"test02","email":"test@02","age":20},{"username":"test03","email":"test@03","age":30},{"username":"test04","email":"test@04","age":40}]) 使用find进行查询,返回一个集合中文档的子集,子集的范围从0个文档到整个集合; db.user.find(query, projection) 第一个参数是查询条件(可选),第二个参数是定义哪些信息需要返回(可选)。当参数为空时指查询集合中的所有文档: db.user.find() 查询简单的类型,只要指定想要查询的值就可以了,例如要查询collection 集合中username 为test的所有文档: db.user.find({"username":"test01"}) 有时并不需要将文档中的所有值返回,指定其中某些字段进行返回,例如要查询collection集合中用户的email: db.user.find({},{"email":1}) email的值为boolen类型,true为只返回该字段,fasle为返回除该字段的其他值,"_id"默认被返回,如果不想返回"_id" db.user.find({},{"_id":0}) 查询条件: "$lt","$lte","$gt","$gte"就是全部的比较操作符,分别对应.=,>,>=。例如查询age大于20的文档 db.user.find({"age":{"$gt":20}}), 支持多个条件与, db.user.find({"age":{"$gt":20,"$lt":40}}) 或查询: mongodb支持两种or查询方式:"$in"可以用来查询一个键的多个值,"$or"更通用些。 如果你个键需要与多个值进行匹配的话,就要用$in操作符,例如查找age为20,30,40的所有文档 db.user.find({"age":{"$in":[20,30,40]}}) $in 的匹配值中可以指定不同的类型,$nin 返回与数组中所有条件都不匹配的文档,例如,查找username为非test01的文档 db.user.find({"username":{"$nin":["test01"]}}) $in只能对单键尽心查询,要查询username为test01 或者age为30的文档,就需要用$or $or接受一个包含所有可能条件的数组作为参数,例如上面的例子可以写成: db.user.find({"$or":[{"username":"test01"},{"age":30}]}) $not $not是元条件句,即可以用在任何其他条件之上,用$not可以实现$nin的效果 db.user.find({"age":{$not:{$in:[20,30]}}}) 特定类型的查询: NULL: null类型有点奇怪,他确实可以与自身匹配,所以要是有个包含null的文档集合,通过查询语句可以查到。但是,null还会匹配不包含这个键的文档,例如查询没有address键值的文档 db.user.find({"adress":null}) 如果仅想匹配键值为null的文档,既要检查该键的值是否存在还要通过$exist条件判断该键值已存在 db.user.find({"address":{"$in":[null],"$exist":true}}) 这里对null的匹配并没有类似$eq的语句,使用$in效果一样的 正则表达式: 正则表达式可以灵活的匹配字符串,例如,要想查找username为test开头的文档 db.user.find({"username":/test/i}) MongDB使用Perl兼容的正则表达式(PCRE)库来匹配正则表达式,任何PCRE支持的正则表达式语法都能被mongoDB接受,建议在查询使用正则表达式前,先在JavaScript shell中检查下语法,确保结果是你想要的。
  • [技术干货] 使用Mongodb聚合操作
    聚合操作用来处理数据记录,然后返回计算过的结果。聚合操作针对一组数据也就是多个文档进行处理,可以对这组数据进行多种操作的运算,然后得到具体的结果。 Mongodb提供了3种方式的聚合功能: [*]聚合管道 [*]map-reduce [*]简单的聚合方法 聚合管道 Mongodb的聚合框架是一种数据处理管道的模型,每个文档通过由多个阶段组成的通道进行处理转化,然后得到聚合的结果。基本的管道操作就是一些过滤操作,比如查询,或者转化文档输出的格式。 $match 筛选条件,过滤掉不满足条件的文档,可以使用常规的查询操作符,如gt、lt、$in等 eg: db.orders.aggregate( {"$match": {"status":"A"}} ) $project 投射 1. 用于包含、排除字段: 设置要查询或者要过滤掉的字段,0: 要过滤掉的字段,不显示,1:需要查询的字段 2. 对字段重命名, 3. 在投射中使用一些【表达式】:数学表达式、日期表达式、字符串表达式、逻辑表达式(比较表达式、布尔表达式、控制语句) eg:db.orders.aggregate( {"$match": {"status": "A"} }, {"$project": { "cust_id": 0, "amount":1, "state": "$status", "amounts":{"$add": ["$amount", "$amount ", 100]}}} ) // $project: 当字段的值是0或者1时用于过滤字段,当键是一个自定义的字符串,值为$跟上原来的字段,表示要对该字段进行重命名 // 算数表达式:用于对一组数字 加减乘除取余$add: [exp1, exp2, ... expN]: 对数组中的多个元素进行相加, 示例是对amount字段的值 + amount字段的值 + 100 作为amounts字段的值 其他的管道操作还有一些分组,排序 以及聚合数组内容的功能。比如计算和或连接字符串。 $group: 分组 使用_id指定要分组的键,要分组的键也可以是多个,使用其他自定义的字段用于统计 _id:用于指定要分组的键 total是自定义字段,要统计的结果 13495 $sort: 用于对上一次处理的结果进行排序,1:升序 -1:降序 eg:db.orders.aggregate({ "$match": {"status": "A"}, "$sort ": {"amount": "-1"}} ) //将筛选后的结果按照amount降序排列 $limit:用于条数限制 $skip:跳过前N条文档,和limit结合可用于分页 eg:db.orders.aggregate({ "$match": {"status":"A"}, "$ limit ":10}, "$ skip ":5} ) 聚合管道使用mongodb原生命令提供了有效的数据聚合能力,是Mongodb聚合操作的首选。聚合操作可用于分片集合中。聚合操作也可以通过索引来提高性能。另外聚合管道也具有内部优化过程。 map-reduce map-reduce有两个过程,map阶段将每一个文档映射到一个或多个对象上,reduce阶段然后组合map操作的输出。一般map-reduce还有一个finalize操作,可以将处理的结果进行修改。map-reduce 可以定义查询条件去筛选输入的文档并且对输出的结果排序和限制。map-reduce使用自定义的java-script 脚本来执行map ,reduce 操作。以及可选的finalize 操作。与聚合管道相比, 定制化的java-script脚本更加灵活。但总的来说, map-reduce相比于聚合管道,效率低而且更复杂。如下示例: 13496 简单的聚合方法 count(): 求数量 eg:db.orders.count({"status": "A"}) distinct(“filedname”): 求某个字段不同的值eg: db.orders.distinct("cust_id ")13497 group(): 用于分组计算 / key: 用于指定要分组的键 // initial: 对于分组统计的字段设置键名和初始值 // reduce: 循环每个分组中的每个文档,一组循环完了会继续下一组, eg:db.articles.group({ "key": "cust_id", "initial": {"total": 0}, "reduce": function(doc, prev) { prev. total += doc.amount; } } 参考文档:[aggregation](https://docs.mongodb.com/v3.6/aggregation)
  • [技术干货] 【MongoDB】数据库状态查询之serverStatus命令详解
    本帖最后由 Mongo_奇 于 2018-4-1 22:14 编辑MongoDB数据库的状态查询有很多种方式可供选择(比如通过数据库状态查询命令,mongostat工具和MongoDB厂商的监控服务MongoDB Monitoring Service等),本文要介绍的是一个非常实用的状态查询命令:serverStatus。 关于serverStatus: 官方文档的说明为:The serverStatus command returns a document that provides an overview of the database’s state. Monitoring applications can run this command at a regular interval to collect statistics about the instance. 即serverStatus命令会返回一个数据库状态概览信息的稳定,监控数据库的应用程序可以通过定时运行该命令来收集数据库实例的统计信息。 serverStatus命令的使用: 举例:db.runCommand({serverStatus: 1}),当然也可以使用经过mongo shell封装的命令:db.serverStatus()。如果想要输出数据库连接数和锁的信息可以这样操作:db.runCommand({serverStatus: 1, connections: 1, locks: 1}) serverStatus命令的输出:常用字段及含义:Instance Information:"host" : , 主机名 "advisoryHostFQDNs" : , 合格的域名 "version" : , MongoDB版本号 "process" : "mongod"|"mongos">, 数据库进程的类型 "pid" : , 数据库进程的进程号 "uptime" : , 数据库进程存活的秒数 "uptimeMillis" : , 数据库进程存活的毫秒数 "uptimeEstimate" : , 数据库内部粗粒度计算进程正常的运行时间 "localTime" : ISODate(""), 当前的UTC时间戳 connections:"current" : , 当前的连接数 "available" : , 可用的连接数 "totalCreated" : NumberLong() 服务启动以来创建的所有连接数 globalLock"globalLock" : { "totalTime" : , 数据库上次启动并创建全局锁的时间 "currentQueue" : "total" : , 当前等待锁的操作个数 "readers" : , 当前等待写锁的操作个数 "writers" : 当前等待读锁的操作个数 }, "activeClients" : { "total" : , 当前活跃的客户端个数 "readers" : , 当前允许读操作的活跃的客户端个数 "writers" : 当前允许写操作活跃的客户端个数 }}, network:"network" : { "bytesIn" : , 服务端接收的字节数 "bytesOut" : , 服务端发送的字节数 "numRequests" : 接收的总的请求数}, opcounters:"opcounters" : { "insert" : , 写的操作数 "query" : , 查询的操作数 "update" : , 更新的操作数 "delete" : , 删除的操作数 "getmore" : , 从游标中取数据数 "command" : 命令的操作数}, 因为serverStatus返回的字段很多,以上仅仅列举的一些关键字段,后续会做补充。同时后面还会介绍其它两个关键的查询命令:rs.status(),sh.status().
  • MongoDB数据模型浅析-02
    本帖最后由 danglf 于 2018-3-31 18:09 编辑 1.1 规范化数据模型(引用) 规范化数据模型指的是通过使用 引用 来表达对象之间的关系,应用程序通过引用来访问那些具有关联关系的数据。13477一般来说,在下述情况下可以使用规范化模型:· 当内嵌数据会导致很多数据的重复,并且读性能的优势又不足于盖过数据重复的弊端时候。· 需要表达比较复杂的多对多关系的时候。· 大型多层次结构数据集。引用比内嵌要更加灵活一些。但客户端应用必须使用二次查询来解析文档内包含的引用。换句话说,对同样的操作来说,规范化模式会导致更多的网络请求发送到数据库服务器端。1.1.1 一对多的例子: 让我们来看一个针对于出版社和书籍关系建模的一个例子。通过这个例子中我们演示了相对于内嵌文档模型,使用文档引用模型可以有效的减少重复的出版社信息。我们可以看到,把出版社信息内嵌到每一个书籍记录里面会导致出版社信息的很多次 重复:{ title: "MongoDB: TheDefinitive Guide", author: [ "KristinaChodorow", "MikeDirolf" ], published_date: ISODate("2010-09-24"), pages: 216, language: "English", publisher: { name: "O´Reilly Media", founded: 1980, location: "CA" }} { title: "50 Tips andTricks for MongoDB Developer", author: "KristinaChodorow", published_date: ISODate("2011-05-06"), pages: 68, language: "English", publisher: { name: "O´Reilly Media", founded: 1980, location: "CA" }} 如果不希望重复出版社信息,我们可以使用 文档引用 的方式,把出版社的信息保存在一个单独的集合里面。当使用引用时,文档关系的数量级及增长性会决定我们要在哪里保存引用信息。如果每个出版社所出版的书的数量比较小并且不会增长太多,那么可以在出版社文档里保存所有该出版社所出版的书的引用。反之,如果每个出版社所出版的书籍数量很多或者可能增长很快那么这个书籍引用数组就会不断增长,如下所示:{ name: "O´ReillyMedia", founded: 1980, location: "CA", books: [12346789, 234567890, ...]} { _id: 123456789, title: "MongoDB: TheDefinitive Guide", author: [ "KristinaChodorow", "MikeDirolf" ], published_date: ISODate("2010-09-24"), pages: 216, language: "English"} { _id: 234567890, title: "50 Tips andTricks for MongoDB Developer", author: "KristinaChodorow", published_date: ISODate("2011-05-06"), pages: 68, language: "English"} { _id: "oreilly", name: "O´ReillyMedia", founded: 1980, location: "CA"} { _id: 123456789, title: "MongoDB: TheDefinitive Guide", author: [ "KristinaChodorow", "MikeDirolf" ], published_date: ISODate("2010-09-24"), pages: 216, language: "English", publisher_id: "oreilly"} { _id: 234567890, title: "50 Tips andTricks for MongoDB Developer", author: "KristinaChodorow", published_date: ISODate("2011-05-06"), pages: 68, language: "English", publisher_id: "oreilly"} 1.2 MongoDB特性和数据模型的关系MongoDB的数据建模不仅仅取决于应用程序的数据需求,也需要考虑到MongoDB本身的一些特性。例如,有些数据模型可以让查询更加有效,有些可以增加**或更新的并发量,有些则可以更有效地把事务均衡到各个分片服务器上。通常需要考虑如下特性:1.2.1 文档增长性 举例来说,如果你的程序的更新操作会导致文档大小增加, 比如说push操作,可能会新增数组中的某个元素, 或者新增一个文档中某个字段,那么你可能要重新设计下数据模型,在不同文档之间使用引用的方式而非内嵌、冗余的数据结构。1.2.2 原子性在MongoDB里面,所有操作在 document 级别具有原子性. 一个 单个 写操作最多只可以修改一个文档。 即使是一个会改变同一个集合中多个文档的命令,在同一时间也只会操作一个文档。尽可能保证那些需要在一个原子操作内进行修改的字段定义在同一个文档里面。如果你的应用程序允许对两个数据的非原子性更新操作,那么可以把这些数据定义在不同的文档内。把相关数据定义到同一个文档里面的内嵌方式有利于这种原子性操作。对于那些使用引用来关联相关数据的数据模型,应用程序必须再用额外的读和写的操作去取回和修改相关的数据。 1.2.3 分片MongoDB 使用 sharding (分片)来实现水平扩展。使用分片的集群可以支持海量的数据和高并发读写。用户可以使用分片技术把一个数据库内的某一个集合的数据进行分区,从而达到把数据分布到多个mongod 实例(或分片上)的目的。Mongodb 依据 分片键 分发数据和应用程序的事务请求。 选择一个合适的分片键会对性能有很大的影响,也会促进或者阻碍MongoDB的定向分片查询和增强的写性能。所以在选择分片键时候要仔细考量分片键所用的字段。1.2.4 索引 对常用操作可以使用索引来提高性能。对查询条件中常见的字段,以及需要排序的字段创建索引。MongoDB会对 _id 字段自动创建唯一索引。创建索引时,需要考虑索引的下述特征: [*]Each index requires at least 8 kB of data space. · 每增加一个索引,就会对写操作性能有一些影响。对于一个写多读少的集合,索引会变得很费时因为每个**必须要更新所有索引。· 每个索引都会占一定的硬盘空间和内存(对于活跃的索引)。索引有可能会用到很多这样的资源,因此对这些资源要进行管理和规划,特别是在计算热点数据大小的时候。1.2.5 集合的数量 在某些情况下,你可能会考虑把相关的数据保存到多个而不是一个集合里面。我们来看一个样本集合 logs ,用来存储不同环境和应用程序的日志。 这个 logs 集合里面的文档例子:{ log: "dev", ts: ..., info: ... }{ log: "debug", ts: ..., info: ...} If the total number of documents is low, you may group documentsinto collection by type. For logs, consider maintaining distinct logcollections, such as logs_dev and logs_debug. The logs_dev collection would contain only the documents related to the devenvironment. 一般来说,很大的集合数量对性能没有什么影响,反而在某些场景下有不错的性能。使用不同的集合在高并发批处理场景下会有很好的帮助。当使用有大量集合的数据模型时,请注意一下几点:· 每一个集合有几个KB的额外开销。· Each index, including the index on _id, requires at least 8kB of data space.· 每一个MongoDB的 database 有一个且仅一个命名文件(namespace file)(i.e. .ns) 。这个命名文件保存了数据库的所有元数据。每个索引和集合在这个文件里都有一条记录。这个文件的大小是有限制的,具体信息请参见: :limit:` 命名文件大小限制 `。· MongoDB 的命名文件有大小的限制: · 一个命名文件中可以容纳的命名记录数取决于命名文件 .ns 文件的大小。 命名文件默认的大小限制是16 MB。.2 具体应用建模举例2.1 原子性事务建模 MongoDB的一些写操作如 db.collection.update(), db.collection.findAndModify(),db.collection.remove() 等 具有文档级的事务原子性。对于必须同时修改的字段,把这些字段通过建模放在同一个文档内可以保证这些字段更新的原子性: 要么全部修改,要么一个都不修改。举例来说,假设你在设计一个图书馆的借书系统,你需要管理书的库存量以及出借记录。一本书的可借数量加上借出数量的和必须等于总的保有量,那么对这两个字段的更新必须是原子性的。把available 和 checkout 两个字段放到同一个文档里,就可以做到对这两个字段的原子性事务。{ _id: 123456789, title: "MongoDB: The Definitive Guide", author: [ "Kristina Chodorow", "Mike Dirolf" ], published_date: ISODate("2010-09-24"), pages: 216, language: "English", publisher_id: "oreilly", available: 3, checkout: [ { by: "joe", date: ISODate("2012-10-15") } ] } 在更新出借记录的时候,你可以用 db.collection.update() 的方法来对 available 和 checkout两个字段同时更新: db.books.update ( { _id: 123456789, available: { $gt: 0 } }, { $inc: { available: -1 }, $push: { checkout: { by: "abc", date: new Date() } } } ) 这个操作会返回一个包含操作结果信息的 WriteResult() 对象:WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 }) 其中 nMatched 字段表示 1 个文档匹配指定的条件。 nModified 字段表示 1 个文档被更新。If no document matched theupdate condition, then nMatched and nModified would be 0 and would indicate that you could notcheck out the book.2.2 关键词搜索建模Onthis page注解关键词搜索和文本搜索及全文检索不同,它不提供类似于断词或者词干提取等文本处理功能如果你的应用需要对某个文本字段进行查询,你可以用完全匹配或使用正则表达式 $regex 。但是很多情境下这些手段不能够满足应用的需求。下面这个范式介绍了一种在同一个文档内使用数组来保存关键词再对数组建多键索引 (multi-key index)的方式来实现关键词搜索。为实现关键词搜索,在文档内增加一个数组字段并把每一个关键词加到数组里。然后你可以对该字段建一个 多键索引。这样你就可以对数组里面的关键词进行查询了。例子假如你希望对图书馆的藏书实现一个按主题搜索的功能。 对每一本书,你可以加一个数组字段topics 并把这本书相关的主题都加到这个数组里。对于 Moby-Dick 这本书你可能会有以下这样的文档:{ title : "Moby-Dick" , author : "Herman Melville" , published : 1851 , ISBN : 0451526996 , topics : [ "whaling" , "allegory" , "revenge" , "American" , "novel" , "nautical" , "voyage" , "Cape Cod" ] } 然后对 topics 字段建多键索引:db.volumes.createIndex( { topics: 1 } ) 多键索引会对数组里的每一个值建立一个索引项。在这个例子里 whaling 和 allegory 个各有一个索引项。现在你可以按关键词进行搜索,如:db.volumes.findOne( { topics : "voyage" }, { title: 1 } ) 注解如果数组较大,达到几百或者几千以上的关键词,那么文档**操作时的索引维护开支会大大增加。2.2.1 关键词索引的局限性MongoDB 的这种关键词搜索方案和专门的全文搜索工具软件相比有以下的局限性:· 词干提取。MongoDB的关键词查询无法对相近词进行归并处理。· 同义词。 对同义词的处理目前必须在应用程序端完成。MongoDB无法支持这一点。· 权重。 关键词之间没有权重支持,所有关键词的重要性都一样,无法优化结果排序。 [*] 参考文档:https://docs.mongodb.com/v3.2/core/data-modeling-introduction/https://docs.mongodb.com/v3.2/tutorial/model-embedded-one-to-one-relationships-between-documents/
  • [技术干货] 什么是Replica Set(副本集)
    本帖最后由 lipo 于 2018-4-8 21:34 编辑什么是副本集 [*]副本集是MongoDB中的一个基础概念,你可以理解为关系型数据库(relational database)中主备实例的升级版。在MongoDB中,可以多个实例组成一个副本集,多个节点中,只能有一个主节点,其余为备节点。用户写入数据,先写入主节点,然后再由主节点同步至备节点。 [*]当主节点因为一些原因宕机时,副本集会从正常的备节点中,推举出一个成为新的主节点,这个过程被称为选举。整个过程中,客户端不会因为数据库节点的变化产生任何影响,也无需感知数据库节点变化,因此便达到了高可用的目的。 13478 · 一个典型的三节点副本集 [*]副本集成员: Primary:主节点 Secondary:副节点统称为Secondary。里面存放着实时同步的主节点数据。副节点默认是不能读和写的,可以通过设置使副节点可读,从而实现主节点写,副节点读。做到读写分离。 [*]副本集的一些特点: · 主副节点可能随时切换,取决于主节点是不是发生故障。若主节点故障,会自动选举副节点成为主节点。 · 关于选举,需要引入优先级概念,MongoDB可以设置节点的选举优先级: 默认为1; 优先级为0的节点永远不会被选为主节点。选举中会先比较数据新旧程度(通过oplog), 然后才会比较优先级。高优先级优先成为主节点。 · Hidden节点: 将一个副节点优先级设为0,然后再设其类型为Hidden,便得到一个Hidden节点。 客户端不会向隐藏节点发送请求,隐藏节点也不会成为复制源。 Hidden节点一般用来做一些特殊用途,比如对Hidden节点设置备份延迟, Hidden节点就不会再实时同步,而是一段时间后才开始。这样可以避免备份到恶性数据。 · 主节点不能强制设置,只能间接选出来 发生故障后,两秒内就会开始选举,选举过程只消耗几毫秒。 但是如果网络出现故障,心跳20秒就会超时。如果平局,等30秒后重新选。 · Oplog: Oplog是主节点local数据库的一个集合。里面用特殊方式记录了用户所有的数据库操作, 将Oplog重新执行,就可回放用户的操作。Oplog具有幂等性(即同一条语句执行一万次和执行一次,对数据库影响相同), MongoDB主备同步便是使用主节点的Oplog在备机上回放来实现的。 · 一个副本集最多拥有12个节点,最多7个节点拥有投票权。
  • [技术干货] 云服务商Mongodb内核版本升级的初步想法
    本帖最后由 shm 于 2018-3-27 09:52 编辑云服务商Mongodb内核版本升级的初步想法前言: Mongodb 3.6.0正式版本 于2017年12月 1日发布 Mongodb 3.4.0 正式版本 于2016年11月26日发布 Mongodb 3.2.0 正式版本 于2015年12月 4日发布 从上面可以看出Mongodb保持一年一个大版本,而每个大版本下面的小版本基本上就是一个月一个迭代版本用于解决各个bug. Mongodb公司2017年10月在纳斯达克上市,作为上市公司相对其他的开源数据库有更多的金钱和人力去发展Mongodb,估计后面Mongodb版本迭代的速度会更快,那作为Mongodb云服务商怎样去适应这种版本的快速迭代呢?我们先分析面对的问题,然后再提出相应的解决方案,当然方案不完美,只是一些初步的想法。 云服务商mongodb已上线版本面对的问题:1. 社区小版本重大bug的修复 对于社区某个版本的重大bug,Mongodb官方的建议一般是升级你的Mongodb到此大版本下的最新版本。由于都是相同的大版本不会存在版本兼容性问题,只需要按顺序替换不同节点的Mongodb二进制源码就行,看起来so easy。可是存在这个升级版本解决了一个重大bug但他本身却引入或含有没有修复的其他重大bug,那到时是不是又要升级更新版本? 2. 大版本之间的升级当用户想使用新的大版本中某个新特性或者一些大版本由于过于久远不再维护,就需要进行大版本升级,而大版本升级最大的问题在于非兼容特性的升级,以及升级失败后进行降级的处理(大版本升级步骤可见文档最后) 3.社区快速发布的大版本:快速增长的版本列表:由于社区大版本发布速度越来越快,云服务商如果跟踪社区大版本,那云服务商mongodb的版本也会越来越多,用户在选择版本的时候也会疑惑,到底选择哪个?维护版本需要的人手也会也来越多,有些版本可能只有极少用户用但也需要人力进行维护 问题总结就是:面对快速迭代的社区版本,云服务商采用什么样的策略,在给定的人力下提供给用户相对社区版更安全更满足用户需求的云服务商mongodb.云服务商mongodb内核版本迭代方案1. 确定社区大版本选择的标准各个大版本新增的特性在官网都有详细的说明,有些大版本相对前版本可能增加了一些新的操作,完善了客户端的可靠机制,这些虽然能提高用户使用的易用性,但在特性或性能或安全性上没有质的飞越,例如3.6版本更多的是更新了客户端的seesion机制提供因果一致性以及一些新的操作,这些新特性可能客户使用的比较少。而在2018年夏天即将推出的4.0,一个最大的特性是支持多文档事务机制,提供ACID,这个特性将颠覆Mongodb的弱一致性,可以看出Mongodb向金融业务这种对数据一致性要求极其严格领域提供服务的决心,这种大有质的飞越的特性云服务商就需要跟踪和支持。当然所谓质的飞越的特性见仁见智,需要各自确定选择标准。 2. 大版本下小版本的选择社区版本再发布一个大版本之后一段时间的的小版本都会存在很多的bug,有的bug会影响数据的可靠性。要达到能在现网环境使用最好要等待6个月以上,等待社区推出7-8个小版本。 3. 云服务商mongodb小版本的快速升级小版本出现严重bug升级最新的小版本,这种升级要解决的问题主要是快速移植以前版本修改的地方并进行测试,而测试需要修改Mongodb本身的测试工具用以适配内核的修改,如果有积累后面会快很多。 4. 云服务商mongodb大版本的快速升级对于两个连续的版本或者可以跨版本升级的可以进行升级,这个和前面的小版本升级没多大的区别。但面对的是严格要求前版本的版本号的呢?例如升级3.6版本,必须要求前一个版本是3.4,可能只能走mongoexport和mongoimport这类数据迁移工具进行跨版本的升级。升级到新的版本后如果出现问题怎么进行降级呢?降级后的版本里面含有新版本的特性怎么处理呢?这又是一块大问题,后续再写相关的文章来讨论下。 注释:3.4版本副本集升级流程:1. 当前运行的版本至少是3.2版本,如果不是请升级到3.22. 3.4在一些特性上有兼容性的改变,在升级3.4之前,检查你的应用程序是否有使用这些特性3. 一旦升级到3.4版本,对于3.2.7以及之前的版本是无法再回退回去的4. 首先停掉副本集中的secondary,然后将mongod二进制替换成3.4,重启secondary等待secondary状态正常。5. 连接到primary上执行rs.stepDown(),强制降级选主。当选出新的primary的时候,停掉旧primary并替换mongod二进制为3.4.然后重启6. 到这一步的时候,就可以使用3.4版本(除了3.4不兼容3.2的特性),强烈建议运行一段时间(不带3.4非兼容特性),以确保不会发生降级版本的情况7. 在admin数据库上执行db.adminCommand({ setFeatureCompatibilityVersion: "3.4" } ),升级3.4完成。
  • [技术干货] mongodb find命令执行流程
    简介:程序运行结果如下, 本文从代码级别走一遍 mongodb的查询流程。注意: 本文分析的是 mongodb 3.6.3的代码,其中实现跟3.2不相同。 阅读本文,请参照3.6.3的代码。> db.test1.find() { "_id" : "id_1", "v" : "v_1" }服务端对用户的程序相应,在mongo::ServiceEntryPointMongod::handleRequest函数处运行。DbResponse ServiceEntryPointMongod::handleRequest(OperationContext* opCtx, const Message& m) { // before we lock... NetworkOp op = m.operation(); bool isCommand = false; ​ DbMessage dbmsg(m); ​ Client& c = *opCtx->getClient(); if (c.isInDirectClient()) { invariant(!opCtx->lockState()->inAWriteUnitOfWork()); } else { LastError::get(c).startRequest(); AuthorizationSession::get(c)->startRequest(opCtx); ​ // We should not be holding any locks at this point invariant(!opCtx->lockState()->isLocked()); } ... [*]程序首先得到 m.operation(), 这个函数是Message 类自带的函数, 主要是返回这个message的操作类型 [*]NetworkOp 实际上就是一个int32的枚举类型, 用于约定一些client和server之间的操作类型 enum NetworkOp : int32_t { opInvalid = 0, opReply = 1, /* reply. responseTo is set. */ dbUpdate = 2001, /* update object */ dbInsert = 2002, // dbGetByOID = 2003, dbQuery = 2004, dbGetMore = 2005, dbDelete = 2006, dbKillCursors = 2007, // dbCommand_DEPRECATED = 2008, // // dbCommandReply_DEPRECATED = 2009, // dbCommand = 2010, dbCommandReply = 2011, dbCompressed = 2012, dbMsg = 2013, }; [*]db.test1.find() 函数的操作类型为 是 'dbMsg' [*]然后程序使用Message对象构建dbmsg, ServiceEntryPointMongod::handleRequest后续程序片段如下 CurOp& currentOp = *CurOp::get(opCtx); { stdx::lock_guard lk(*opCtx->getClient()); // Commands handling code will reset this if the operation is a command // which is logically a basic CRUD operation like query, insert, etc. currentOp.setNetworkOp_inlock(op); currentOp.setLogicalOp_inlock(networkOpToLogicalOp(op)); } ​ OpDebug& debug = currentOp.debug(); ​ long long logThresholdMs = serverGlobalParams.slowMS; bool shouldLogOpDebug = shouldLog(logger::LogSeverity::Debug(1)); [*]上面这段,是一些锁检查和变量设置, ServiceEntryPointMongod::handleRequest后续程序片段如下 ​ DbResponse dbresponse; if (op == dbMsg || op == dbCommand || (op == dbQuery && isCommand)) { dbresponse = runCommands(opCtx, m); } else if (op == dbQuery) { invariant(!isCommand); dbresponse = receivedQuery(opCtx, nsString, c, m); } else if (op == dbGetMore) { dbresponse = receivedGetMore(opCtx, m, currentOp, &shouldLogOpDebug); } else { // The remaining operations do not return any response. They are fire-and-forget. try { if (op == dbKillCursors) { currentOp.ensureStarted(); logThresholdMs = 10; receivedKillCursors(opCtx, m); } else if (op != dbInsert && op != dbUpdate && op != dbDelete) { log() " operation isn't supported: " static_cast(op); currentOp.done(); shouldLogOpDebug = true; } else { if (!opCtx->getClient()->isInDirectClient()) { uassert(18663, str::stream() "legacy writeOps not longer supported for " "versioned connections, ns: " nsString.ns() ", op: " networkOpToString(op), !ShardedConnectionInfo::get(&c, false)); } ​ if (!nsString.isValid()) { uassert(16257, str::stream() "Invalid ns [" ns "]", false); } else if (op == dbInsert) { receivedInsert(opCtx, nsString, m); } else if (op == dbUpdate) { receivedUpdate(opCtx, nsString, m); } else if (op == dbDelete) { receivedDelete(opCtx, nsString, m); } else { invariant(false); } } } catch (const AssertionException& ue) { LastError::get(c).setLastError(ue.code(), ue.reason()); LOG(3) " Caught Assertion in " networkOpToString(op) ", continuing " redact(ue); debug.exceptionInfo = ue.toStatus(); } } [*]DbResponse dbresponse 就是server给程序返回的信息, server首先构造出一个dbresponse出来, [*]然后根据不同的op,进入不同的函数处理,上面说过,此时的op是dbMsg, 所以进入runCommands函数。 [*]runCommands 函数如下 DbResponse runCommands(OperationContext* opCtx, const Message& message) { auto replyBuilder = rpc::makeReplyBuilder(rpc::protocolForMessage(message)); [&] { OpMsgRequest request; try { // Parse. request = rpc::opMsgRequestFromAnyProtocol(message); } catch (const DBException& ex) { // If this error needs to fail the connection, propagate it out. if (ErrorCodes::isConnectionFatalMessageParseError(ex.code())) throw; ​ auto operationTime = LogicalClock::get(opCtx)->getClusterTime(); BSONObjBuilder metadataBob; appendReplyMetadataOnError(opCtx, &metadataBob); // Otherwise, reply with the parse error. This is useful for cases where parsing fails // due to user-supplied input, such as the document too deep error. Since we failed // during parsing, we can't log anything about the command. LOG(1) "assertion while parsing command: " ex.toString(); _generateErrorResponse(opCtx, replyBuilder.get(), ex, metadataBob.obj(), operationTime); ​ return; // From lambda. Don't try executing if parsing failed. } ​ try { // Execute. curOpCommandSetup(opCtx, request); ​ Command* c = nullptr; // In the absence of a Command object, no redaction is possible. Therefore // to avoid displaying potentially sensitive information in the logs, // we restrict the log message to the name of the unrecognized command. // However, the complete command object will still be echoed to the client. if (!(c = Command::findCommand(request.getCommandName()))) { Command::unknownCommands.increment(); std::string msg = str::stream() "no such command: '" request.getCommandName() "'"; LOG(2) msg; uasserted(ErrorCodes::CommandNotFound, str::stream() msg ", bad cmd: '" redact(request.body) "'"); } ​ LOG(2) "run command " request.getDatabase() ".$cmd" ' ' c->getRedactedCopyForLogging(request.body); ​ { // Try to set this as early as possible, as soon as we have figured out the command. stdx::lock_guard lk(*opCtx->getClient()); CurOp::get(opCtx)->setLogicalOp_inlock(c->getLogicalOp()); } ​ execCommandDatabase(opCtx, c, request, replyBuilder.get()); } catch (const DBException& ex) { BSONObjBuilder metadataBob; appendReplyMetadataOnError(opCtx, &metadataBob); auto operationTime = LogicalClock::get(opCtx)->getClusterTime(); LOG(1) "assertion while executing command '" request.getCommandName() "' " "on database '" request.getDatabase() "': " ex.toString(); ​ _generateErrorResponse(opCtx, replyBuilder.get(), ex, metadataBob.obj(), operationTime); } }(); ​ if (OpMsg::isFlagSet(message, OpMsg::kMoreToCome)) { // Close the connection to get client to go through server selection again. uassert(ErrorCodes::NotMaster, "Not-master error during fire-and-forget command processing", !LastError::get(opCtx->getClient()).hadNotMasterError()); ​ return {}; // Don't reply. } ​ auto response = replyBuilder->done(); CurOp::get(opCtx)->debug().responseLength = response.header().dataLen(); ​ // TODO exhaust return DbResponse{std::move(response)}; } [*][&]{}(); 这里表示的是匿名lambda函数的定义和运行, 之所以这样写,我猜是为了方便在函数体内使用returen吧。 c++ lambda语法,可以参考, http://zh.cppreference.com/w/cpp/language/lambda [*]runCommands第一步,创建一个replyBuilder, 这个很显然,是放了一个response可以用的builder。 [*]第二部,就进入了匿名函数内部,首先调用request = rpc::opMsgRequestFromAnyProtocol(message);函数进行request的获取, opMsgRequestFromAnyProtocol如下所示, 因为知道op type是dbMsg, 那么程序会继续执行到OpMsgRequest::parse内部。 OpMsgRequest opMsgRequestFromAnyProtocol(const Message& unownedMessage) { switch (unownedMessage.operation()) { case mongo::dbMsg: return OpMsgRequest::parse(unownedMessage); case mongo::dbQuery: return opMsgRequestFromLegacyRequest(unownedMessage); case mongo::dbCommand: return opMsgRequestFromCommandRequest(unownedMessage); default: uasserted(ErrorCodes::UnsupportedFormat, str::stream() "Received a reply message with unexpected opcode: " unownedMessage.operation()); } } [*]OpMsgRequest::parse代码如下,可以看出,直接调用了OpMsg::parse进行处理 static OpMsgRequest parse(const Message& message) { return OpMsgRequest(OpMsg::parse(message)); } [*]OpMsg::parse的函数如下 OpMsg OpMsg::parse(const Message& message) try { // It is the caller's responsibility to call the correct parser for a given message type. invariant(!message.empty()); invariant(message.operation() == dbMsg); ​ const uint32_t flags = OpMsg::flags(message); uassert(ErrorCodes::IllegalOpMsgFlag, str::stream() "Message contains illegal flags value: Ob" std::bitset32>(flags).to_string(), !containsUnknownRequiredFlags(flags)); ​ constexpr int kCrc32Size = 4; const bool haveChecksum = flags & kChecksumPresent; const int checksumSize = haveChecksum ? kCrc32Size : 0; ​ // The sections begin after the flags and before the checksum (if present). BufReader sectionsBuf(message.singleData().data() + sizeof(flags), message.dataSize() - sizeof(flags) - checksumSize); ​ // TODO some validation may make more sense in the IDL parser. I've tagged them with comments. bool haveBody = false; OpMsg msg; while (!sectionsBuf.atEof()) { const auto sectionKind = sectionsBuf.read(); switch (sectionKind) { case Section::kBody: { uassert(40430, "Multiple body sections in message", !haveBody); haveBody = true; msg.body = sectionsBuf.read>(); break; } ​ case Section::kDocSequence: { // We use an O(N^2) algorithm here and an O(N*M) algorithm below. These are fastest // for the current small values of N, but would be problematic if it is large. // If we need more document sequences, raise the limit and use a better algorithm. uassert(ErrorCodes::TooManyDocumentSequences, "Too many document sequences in OP_MSG", msg.sequences.size() 2); // Limit is =2 since we are about to add one. ​ // The first 4 bytes are the total size, including themselves. const auto remainingSize = sectionsBuf.read>() - sizeof(int32_t); BufReader seqBuf(sectionsBuf.skip(remainingSize), remainingSize); const auto name = seqBuf.readCStr(); uassert(40431, str::stream() "Duplicate document sequence: " name, !msg.getSequence(name)); // TODO IDL ​ msg.sequences.push_back({name.toString()}); while (!seqBuf.atEof()) { msg.sequences.back().objs.push_back(seqBuf.read>()); } break; } ​ default: // Using uint32_t so we append as a decimal number rather than as a char. uasserted(40432, str::stream() "Unknown section kind " uint32_t(sectionKind)); } } ​ uassert(40587, "OP_MSG messages must have a body", haveBody); ​ // Detect duplicates between doc sequences and body. TODO IDL // Technically this is O(N*M) but N is at most 2. for (const auto& docSeq : msg.sequences) { const char* name = docSeq.name.c_str(); // Pointer is redirected by next call. auto inBody = !dotted_path_support::extractElementAtPathOrArrayAlongPath(msg.body, name).eoo(); uassert(40433, str::stream() "Duplicate field between body and document sequence " docSeq.name, !inBody); } ​ return msg; } catch (const DBException& ex) { LOG(1) "invalid message: " ex.code() " " redact(ex) " -- " redact(hexdump(message.singleData().view2ptr(), message.size())); throw; } 未完待续 13233
  • [技术干货] 如何启用mongodb认证机制
    本帖最后由 Nosql_newbee 于 2018-4-21 17:49 编辑MongoDB默认没有认证,只要能链接到该服务器,就可以对数据库进行操作,下面简单总结MongoDB如何启用安全认证。要使用认证权限,首先要有个用户,你可以在启用认证之前或者之后创建用户,如果是在启用认证之后创建用户,MongoDB允许你在admin数据库中创建一个admin用户,该用户拥有userAdmin或者userAdminAnyDatabase角色,利用该用户可以创建其他用户和角色。启用mongodb认证机制时,需要在启动mongod时,添加--auth参数mongod.exe --port 27030 --dbpath "D:\MongoDB\data3\db" --logpath "D:\MongoDB\data3\MongDB.log" --logappend --auth 此时,通过mongo shell链接到数据库时,并没有权限进行操作 这时你是处于先启用认证机制,后创建用户的场景,mongodb允许你在admin数据库创建admin用户use admindb.createUser({user:"admin",pwd:"test001",roles:[{role:"userAdminAnyDatabase",db:"admin"}]})admin用户创建成功后,通过认证你就可以对数据库进行操作了。 用户的认证方式有两种,除了上面讲到的,先用mongo shell登录到数据库,再用db.auth(“admin”,”password”),还可以在链接数据库时直接进行认证mongo.exe --port 27030 -u “admin"-p "test001" --authenticationDatabase "admin"该命令在链接上数据库时,直接对admin用户进行了认证admin虽然是超级管理员,但是对具体的数据库,是没有权限进行操作的,还是需要有对应的用户,用户是和数据库走的,因此还需要建立test数据库的用户,例如以下就是针对test数据库创建一个mytest用户,该用户拥有对test数据进行读写的权限。use testdb.createUser( { user: "mytest", pwd: "test001", roles: [ { role: "readWrite", db:"test" } ] })如果用户在test数据库上对mytest用户进行认证,就可以进行读写操作了。
  • [技术干货] Mongodb副本集选举机制
    本帖最后由 昵称 于 2018-3-26 00:21 编辑Mongodb 副本集是一种典型的高可用部署模式,副本集是一组服务器,一般是一个主节点(Primary)用来处理客户端的读写请求,多个副本节点(Secondary)节点对主节点的数据进行备份,以防止主节点宕机导致的单点故障。一旦主节点宕机后,那么整个副本集会进行一次新的选举,选举出一个新的节点成为主服务器。这种副本集部署模式,在主节点挂掉后,仍然可以从副本节点进行读取数据。保证业务的可用性。 要想了解副本集的选举机制,首先要简单了解一下mongodb 副本集之间心跳检测。概括为以下几点: 心跳检测 一旦一个副本集创建成功,那么每一个节点之间都保持着通信,每2s会向整个副本集的其他节点发一次心跳通知,也就是一个 pings包。在任意一个节点的数据库内部,维护着整个副本集节点的状态信息,一旦某一个节点超过10s不能收到回应,就认为这个节点不能访问。另外,对于主节点而言,除了维护这个状态信息外,还要判断是否和大多数节点可以正常通信,如果不能,则要主动降级。 选举条件 前提条件,参与选举的节点必须大于等于 N/2 + 1 个节点,如果正常节点数已经小于一半,则整个副本集的节点都只能为只读状态,整个副本集将不能支持写入。也不能够进行选举。 [*]初始化副本集 [*]主节点断开连接,主要说的是网络问题导致的主备不通 [*]主节点发生宕机 选举的规则 a) Bully 算法。 Bully算法是一种协调者(主节点)竞选算法,主要思想是集群的每个成员都可以声明它是主节点并通知其他节点。别的节点可以选择接受这个声称或是拒绝并进入主节点竞争。被其他所有节点接受的节点才能成为主节点。 对于副本集来说,每一个副本节点根据自己维护的节点状态信息,如果判断出整个副本集没有了主节点,而且本身具备升主条件,则会引发一次投票选举。投票发起者向副本集成员发起Elect请求,成员在收到请求后经过会一系列检查,如果通过检查则为发起者投一票。一轮选举中每个成员最多投一票,用30秒“选举锁”避免为其他发起者重复投票,这导致了如果新选举的Primary挂掉,可能30秒内不会有新的Primary选举产生。 b) 大多数原则。 如果投票发起者获得超过半数的投票,则选举通过成为Primary节点,否则重新发起投票。这个半数是基于初始化时整个副本集节点的数量的一半,而不是当时可以正常连接的节点。 c) 一票否决权。 所有成员都可以投否决票,一个否决票会将得票数减少10000,所以一般可以认为只要有成员反对,则发起选举节点不能成为Primary。投否决票一般需要判断下面三个条件。 如果下面有任意一个条件满足,则会停止选举过程。原计划升主的节点将仍旧保持Secondary状态。 (1)副本集中是否有其他节点已经是primary了? (2)自己的数据是否比请求成为主节点的副本节点上的数据更新? (3)副本集中其他节点的数据是否比请求成为主节点的副本节点的数据更新? 影响选举的主要因素 a) 心跳检测。 副本集节点之间每2s会发一次心跳通知,一旦某一个节点超过10s不能收到回应,就认为这个节点不能访问。 b) 优先级。 优先级为0 的成员肯定不能成为主节点,但可以具备选举权。优先级最高的成员一般会被选举为主。 c) Optime。 optime自身是一个时间戳,某个节点的optime 为整个副本集中最新的,该节点最可能被选举为主。optime主要与同步机制有关系,详细的同步机制可以参考以下文档 mongodb副本及发生回滚(rollback)情景与规避措施 这里不再赘述。 d) 网络。 如果网络连接是可靠的,则整个副本之前的连接是正常的,那么大多数的连接是可以建立的。一般可以选举出新主,否则,整个副本集将不正常。 e) 网路分区。 如果主挂掉了,不同网络分区的成员分布比较平均,都不占大多数,选举不会被触发,所有成员状态变为只读,官方推荐副本集的成员数量为奇数,主要就是这方面的原因。假设四个节点被分成两个IDC,每个IDC各两台机器。这样就出现了个问题,如果两个IDC网络断掉,在上面选举中提到只要主节点和副本集中大部分节点断开连接就会开始一轮新的选举操作,不过mongodb副本集两边都只有两个节点,但是选举要求参与的节点数量必须大于一半,这样所有集群节点都没办法参与选举,只会处于只读状态。但是如果是奇数节点就不会出现这个问题,假设5个节点,只要其中一个IDC部署3个节点,那么这3个节点就可以选举出新主。