-
本帖最后由 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中检查下语法,确保结果是你想要的。
Nosql_newbee 发表于2018-04-02 08:50:12 2018-04-02 08:50:12 最后回复 Nosql_newbee 2018-04-02 08:50:12
6644 0 -
聚合操作用来处理数据记录,然后返回计算过的结果。聚合操作针对一组数据也就是多个文档进行处理,可以对这组数据进行多种操作的运算,然后得到具体的结果。 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)
-
本帖最后由 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().
-
本帖最后由 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/
-
本帖最后由 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个节点拥有投票权。
-
本帖最后由 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的查询流程。注意: 本文分析的是 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
-
本帖最后由 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用户进行认证,就可以进行读写操作了。
Nosql_newbee 发表于2018-03-26 00:15:47 2018-03-26 00:15:47 最后回复 Nosql_newbee 2018-03-26 00:15:47
6687 0 -
本帖最后由 昵称 于 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个节点就可以选举出新主。
-
本帖最后由 lipo 于 2018-3-25 23:58 编辑Write Concern 写入安全(Write Concern)是一种由客户端设置的,用于控制写入安全级别的机制,通过使用写入安全机制可以提高数据的可靠性。 Write Concern包含如下几个配置项: [code]{ w: , j: , wtimeout: }[/code] [*]w配置项: 表示当数据写入n个节点时会返回给客户端写入成功的结果,w=1时,适用于单节点或者副本集的primary节点。w>1时,只适用于副本集。 [*]j配置项: 数据写入后,并且jurnal日志成功持久化,才会返回写入成功。 [*]wtimeout配置项: 很容以看出此配置项为写入超时时间的配置项,单位为ms。值得注意的是,此配置项在w的值大于1时,才会生效,即副本集模式。 长话短说,对官方文档进行总结后,得到如下表格: 13232 Write Concern的使用 [*]在每条修改语句中使用: 可以在每条修改语句后添加配置,确保此语句的写入安全。 [code]db.test.insert({x: 1}, {writeConcern: {w: 2, wtimeout: 60000}})[/code] [*]修改副本集配置: 不想每条修改语句都配置write concern的话,也可以修改副本集配置文件。 [code]cfg = rs.conf() cfg.settings = {} cfg.settings.getLastErrorDefaults = {w: 2, wtimeout: 60000} rs.reconfig(cfg)[/code] Write Concern的坑 Write Concern也不是处处适用。当配置 w:n的时候,mongodb副本集计算n的时候会将Arbiter节点也计算在内,而Arbiter节点是无法写数据的。因此当Arbiter被计算入w写入的对象内时,因为无法写入便会报错。 因此,当副本集内有Arbiter时且必须用Write Concern时,须预先考虑规避上面这种情况的措施,保证可写入数据的节点大于等于n。 参考: 官方文档 [Write Concern]
-
本帖最后由 Mongo_奇 于 2018-3-25 23:04 编辑关于profile日志: 官方文档的说明为:The database profiler collects detailed information about Database Commands executed against a running mongod instance. 即database profiler收集正在运行的mongod实例上执行的数据库命令的详细信息。注意:只能是mongod,不包含mongos profile日志的作用: profile日志其实就是把之前进行的数据库操作的详细信息记录到system.profile集合中。随着业务数据的不断增多,数据库的查询也会越来越慢,如果这时需要定位具体是哪些操作导致的数据库变慢,第一个想到可以定位问题的方法就是开启profile日志功能了。因为profile日志几乎可以记录已经执行数据库操作的所有详细信息,而且还支持按查询时间排序等操作,可以帮助用户很快的定位到究竟哪些操作才是效率低下的元凶。 profile日志的级别: profile日志可以设置的级别为(0,1,2) 0:默认的日志级别就是0,指的是不打印数据库具体的访问日志。 1:表示只记录时间大于slowms的数据库操作(记录慢日志)。 2:表示记录所有数据库操作。(数据访问量大时会影响iops,慎用) 一般想要查询profile日志需要经过以下几步: 1. 切到想要查询的数据库use testProfile: 2. 可以通过db.getProfilingStatus()来查询当前profile的设置状态: 3. 设置profile日志级别可以使用db.setProfilingLevel(1,100) (记录执行时间超过100ms的操作) 4. 查询慢日志db.system.profile.find() profile日志样例分析: 样例 { "op" : "insert", "ns" : "testslowlog.person", "query" : { "insert" : "person", "documents" : [ { "_id" : ObjectId("5a9d31e1e9ab426b4767e186"), "Uid" : 1000, "Name" : "testProfile", "Age" : 13, "Date" : ISODate("2018-03-05T12:02:41.496Z") } ], "ordered" : true }, "ninserted" : 1, "keyUpdates" : 0, "writeConflicts" : 0, "numYield" : 0, "locks" : { "Global" : { "acquireCount" : { "r" : NumberLong(4), "w" : NumberLong(4) } }, "Database" : { "acquireCount" : { "w" : NumberLong(3), "W" : NumberLong(1) } }, "Collection" : { "acquireCount" : { "W" : NumberLong(1) } }, "Metadata" : { "acquireCount" : { "w" : NumberLong(2) } }, "oplog" : { "acquireCount" : { "w" : NumberLong(2) } } }, "responseLength" : 85, "protocol" : "op_command", "millis" : 132, "execStats" : { }, "ts" : ISODate("2018-03-05T12:02:41.629Z"), "client" : "192.168.**.***", "allUsers" : [ { "user" : "rwuser", "db" : "admin" } ], "user" : "rwuser@admin" } 常用字段及其含义: 1. system.profile.op 定义:操作类型 取值范围:insert,query,update, remove,getmore, command 2. system.profile.ns 定义:进行操作的命名空间(数据库,集合名称) 3. system.profile.ts 定义:语句执行的时间戳。 4. system.profile.millis 定义:操作消耗的时间。 5. system.profile.query 定义:查询文档或**操作**的文档。如果文档超过50千字节,则该值是该对象的字符串摘要。 6. system.profile.command 定义:命令操作。如果命令文档超过50千字节,则该值为该对象的字符串摘要。 7. system.profile.updateobj 定义:更新操作期间传入的文档。如果文档超过50千字节,则该值是该对象的字符串摘要。 8. system.profile.cursorid 定义:query和getmore的游标ID 9. system.profile.key**amined 定义:MongoDB为了执行操作而扫描的索引键的数量。 10. system.profile.doc**amined 定义:MongoDB为了执行操作而扫描的集合中的文档数量。 11. system.profile.ndeleted 定义:操作删除的文档数。 12. system.profile.ninserted 定义:**操作的文档数 13. system.profile.nMatched 定义:符合查询条件的更新操作文档数 14. system.profile.nModified 定义:更新操作改变的文档数 15. system.profile.writeConflicts 定义:写入操作期间遇到的冲突次数;例如更新操作会尝试修改与另一个更新操作相同的文档。 16. system.profile.numYield 定义:该查询为其他查询让出锁的次数为允许其它操作完成而产生操作的次数 17. system.profile.locks 定义:信息,R:全局读锁;W:全局写锁;r:特定数据库的读锁;w:特定数据库的写锁 18.system.profile.nreturned 定义:返回文档的数量。 华为云DDS服务已经提供了慢日志功能(默认记录超过100ms的慢操作),客户可以通过界面一键点击轻松查询。在展示数据库慢日志的同时还会对用户的数据进行加密处理,保证用户的放心使用。
-
本帖最后由 danglf 于 2018-3-23 15:18 编辑副本集方式是MongoDB最常用的部署方式,副本集使用之前必须先初始化, 副本集初始化是通过执行replSetInitiate完成的,本文主要分析副本集收到该命令后背后的逻辑(本文基于MongoDB3.2.18分析)。1.1 入口 当用户执行replSetInitiate 命令后, 最终会调用到CmdReplSetInitiate类的 run 方法,CmdReplSetInitiate 在Replset_commands.cpp里面定义。对于Mongod 启动后, 用户执行各种命令和查询操作, 是如何运行起来的,及command内部原理和体系结构 ,后续会写一篇专门的文章去分析。代码如下: 13173 13174 总体流程如下1 校验mongod的配置文件是不是配置配置副本集参数。2 对于副本集配置参数进行补齐3 核心语句, 调用getGlobalReplicationCoordinator()->processReplSetInitiat, 进入复制集初始化流程。1.2 主体流程: 1 ) 判断当前状态是不是可以进行初始化,必须是副本集之前没有初始化过才可以,否则报错 2) 构建内部初始化需要的配置数据, 并判断初始化副本集中的副本集名称和配置文件中的是不是一致,初次以外还有其他的逻辑校验 3 ) 判断当前实例状态, 发起选举,选出主 备节点 4)启动所有后台线程: a)后台拉取主节点oplog线程 b) 向主节点同步自己复制状态的线程 c) 定时快照线程。 相关流程对应代码如下 1)状态判断 replication_coordinator_impl.cpp –>processReplSetInitiate 13175 2) 部分参数校验 13176 3) 发起选举 processReplSetInitiate->_finishReplSetInitiate->_performPostMemberStateUpdateAction->_startElectSelfV1 4) 启动后台线程: 13177 以上就是replSetInitiate 命令后,mongod 内部的大致流程, 分析比较粗, 实际上每一个流程,如选举的内部机制, 初始化同步过程, 增量同步过程, 备节点如何应用oplog,每一个都可以作为专项来分析,后面会持续进行, 文中如有不正确的地方,也欢迎指出并讨论。
-
Mongodb引擎RocksDB系列----初识RocksDBRocksDB简介RocksDB由facebook开创和维护(项目git址:https://github.com/facebook/rocksdb), 该项目旨在开发一个与快速存储器(尤其是闪存)存储数据性能相当的数据库软件,以应对高负载服务。RocksDB官网(https://rocksdb.org)首页头条注明了其项目的宗旨:RocksDB是一个可嵌入的,持久型的key-value存储,为了更快速的存储环境而生。RocksDB起源Leavel DB,在LevelDB的基础上优化而成,其在写性能上有了很大的提升。Mongodb和RocksDB的关系mongodb从3.2版本开始支持多数据存储引擎(WiredTiger,MMAPv1, In-Memory),其中WiredTiger为MongDB默认的存储引擎。所谓的多数据储存引擎形象的说就是拔插式引擎,你想使用哪个引擎就**这个引擎并拔掉前面的引擎就可以使用了,简单方便快捷。Mongodb提供整个数据库集群的管理工作,而真正将数据持久化的则是数据存储引擎。 12651 Mongodb和存储引擎之间的关系Mongodb 集成RocksDB要将RocksDB集成到Mongodb中,首先需要RocksDB实现Mongodb 的Engine API,这样mongodb通过操作Engine API 来存储数据而不用关注真正的数据存储引擎是什么。RocksDB由于是一款独立的数据存储引擎,facebook并没有实现mongodb Engine API, 因此需要使用第三方提供的开源软件Mongo-rocks(git:https://github.com/mongodb-partners/mongo-rocks).12652 安装和编译RocksDB:RocksDB依赖一些开源第三方软件,需要先安装这些软件:gflags: unzipgflags-2.0.zipcd gflags./configure&& make && sudo make install Snappy: tar -xzvfsnappy-1.1.4.tar.gzcdsnappy-1.1.4./configure&& make && sudo make install zlib, zlib-devel, bzip2, bzip2-devel: sudo yum installzlibsudo yuminstall zlib-develsudo yuminstall bzip2sudo yuminstall bzip2-devel zstd: tar zxvfzstd-1.1.3.tar.gzcdzstd-1.1.3make&& sudo make install 编译 rocksdb:注意:rocksdb版本向后兼容的,因此最新的版本都是可以集成的,本文档以5.4.10版本为例。其中rocksdb-rocksdb-5.4.10为编译的rockdb源码目录. cd rocksdb-rocksdb-5.4.10;USE_RTTI=1 CFLAGS=-fPIC make shared_lib;sudo INSTALL_PATH=/usr make install 编译mongo-rock ,mongod: 注意:其中mongo-r3.2.18为mongodb源码目录,mongo-rocks-r3.2.18为mongo-rocks的源码目录.mkdir -p/data/mongodb/shm/mongo-r3.2.18/src/mongo/db/modules/;ln -sf/data/mongodb/shm/mongo-rocks-r3.2.18 /data/mongodb/shm/mongo-r3.2.18/src/mongo/db/modules/rock;cd/data/mongodb/shm/mongo-r3.2.18; scons MONGO_VERSION=3.2.18 all --ssl -j 16 --opt=off; 启动mongod: 执行完上述的编译步骤后,编译出来的mongod的存储引擎就是RocksDB了,启mongod的时候带参数--storageEngine=rocksdb即可(详尽的参数可以参考文章https://www.percona.com/doc/percona-server-for-mongodb/3.4/mongorocks.html).
-
本帖最后由 Nosql_newbee 于 2018-3-19 09:01 编辑MongoDB作为分布式数据库,其支持复制功能,复制功能保证将数据的副本保存到多台服务器上。 在创建一个副本集之后就可以使用复制功能了。副本集是一组服务器,其中有一个主服务器(primary),用于处理客户端请求,还有多个备份服务器(secondary),用于保存主服务器的数据副本。如果主服务器崩溃了,备份服务器会自动将其中一个成员升级为新的主服务器。 本文介绍快速搭建三成员的副本集。 在mongodb官方网站上下载mongodb Windows版本,并安装。一般在产品级应用中,每个副本集成员单独在一个机器上。这里为了快速搭建,在同一台机器上起三个mongod进程,使用默认绑定的ip地址(127.0.0.1)。 本例子将mongodb安装在D:\MongoDB 目录下 1、首先创建三个目录存储数据,例如 D:\MongoDB\data\db, D:\MongoDB\data1\db, D:\MongoDB\data2\db 2、开启三个cmd窗口,分别启动三个服务器: mongod.exe --port 27017 --dbpath "D:\MongoDB\data\db" --logpath "D:\MongoDB\data\MongoDB.log" --replSet rs0 --logappend 按照此方法启动另外两个服务器: mongod.exe --port 27018 --dbpath "D:\MongoDB\data1\db" --logpath "D:\MongoDB\data1\MongoDB.log" --replSet rs0 --logappend mongod.exe --port 27019 --dbpath "D:\MongoDB\data2\db" --logpath "D:\MongoDB\data2\MongoDB.log" --replSet rs0 --logappend 其中,--port 指定端口 --replSet 指定副本集的名称,三个服务的进程的副本集名称要一致 3、链接其中一个服务器并初始化副本集 三个服务启动后,cmd窗口不要关闭,另开一个cmd窗口,链接到27017端口的服务器(连接其他的端口也可以): use test 定义副本集的配置 rs.initiate({"_id":"rs0","members":[{"_id":0,host:"127.0.0.1:27017"},{"_id":1,host:"127.0.0.1:27018"},{"_id":2,host:"127.0.0.1:27019"}]}) _id 的值是副本集名称,成员分别是三个服务器 执行完成后通过 rs.status() 查看副本集状态,其中可以看出副本集的primary节点和连个secondary节点 到此,最简单的副本集就搭建完成了,接下来链接primary 节点就可以**数据进行验证了 链接primary节点,并**验证数据: 在primary节点**数据成功后,可以在任意的secondary节点查看数据。由于主备节点数据复制存在时间差,MongoDB为了防止用户在secondary节点读入脏数据,默认情况下禁止从secondary节点读入数据,可以通过执行 setSlaveOk 命令来允许在secondary节点读入数据: 副本集的复制功能也验证完了。 接下来介绍如何添加或去掉副本集节点成员,通过配置文件启动服务,配置文件中常用项的介绍。
Nosql_newbee 发表于2018-03-19 08:56:01 2018-03-19 08:56:01 最后回复 Nosql_newbee 2018-03-19 08:56:01
5744 0 -
本帖最后由 lipo 于 2018-3-18 21:58 编辑[*]关于回滚: 官方文档的说明为:A rollback reverts write operations on a former primary when the member rejoins its replica set after a failover. 即rollback发生在主备故障切换后,会回滚掉前一个主节点的部分写入数据。这明显属于丢数据,对于某些应用场景,简直不可忍受。那么我们就需要了解这个过程怎么发生的,才能做出针对性的对应措施。 [*]mongodb发生回滚的过程分析 1. 下图为一个正常工作的副本集,绿色的为主节点,蓝色为备节点,数字代表我们依次写入的数据。 那么此时下图中,A为主节点,BC为备节点,我们成功写入数据至13,此时A已写入,BC正在同步数据,一切看起来很正常,很祥和。 在这里,我们还要着重简单说明一下主备同步的机制,简单的说,就是备节点会去主节点的Oplog(operation log)内去寻找自己最新的Oplog,找到后,就知道自己落后主节点多少operation,此时备节点会把自己落后的主节点的新的oplog拿过来,在自己这边重新执行一遍(别担心,每一条Oplog都具有幂等性),这样主节点的数据和状态就会更新过来了。 12646 2. 此时,如下图,意外发生了,由于一些不可知的原因,A与BC断开了网络链接,而BC之间通信正常,BC很快发现了这一点,于是我们mongodb强大的failover机制起作用了,A被踢出了主权限,BC将会商量选出新的主节点,我们假设将选出的主节点为B。 12647 3. 经过短暂的两三秒后,新的主节点B诞生了。整个过程我们客户端没有任何感知,继续高兴的执行我们写入计划,好的,继续写入数据14。14成功的被写入了,接下来是15,16,17...当然,这些新数据都会写到我们的新主节点B内。 12648 4. 非常幸运的,我们修复好了A节点的网络故障!A又回来了!此时A会发现,自己的最后的Oplog时间,也就是写入数据13的时间,落后与当前主节点B的Oplog最后时间,也就是数据17的写入时间。A决定要尽快新的主节点保持同步,赶上B节点的最新数据。于是,A用自己本地最新的数据去与新的主节点做比对,首先是数据13,它发现B内竟然没有数据13,哦,数据13肯定是脏数据,等会扔掉。然后是12,哎哟,12也是脏数据,接着是11...最后,它发现,它与主节点最近的相同数据就是数据10。谢天谢地,总算和老大找到共同点了。 12649 5. 在于老大(主节点B)找到共同点后,为了保持和主节点一直,节点A非常没有节操的扔掉了它认为的脏数据。扔完脏数据后,节点ABC达到如下的节点状态: 12650 6. 那么,A节点丢脏数据这个过程,也就是官方文档所说的rollback。 [*]RollBack规避措施: 了解了这个过程,我们就容易发现,想数据不发生回滚,那我们只要保证主备节点的数据随时一致就行了!这句话说起来简单,实现起来却无比头大。 不过万幸的是!mongodb提供了一个非常强大选项:Write Concern,当设置Write Concern为majority时,会在主节点写入后,保证备节点也同步了该数据,才返回写入成功,这样就再也不怕failover时发生数据回滚了! 至于Write Concern到底是个什么东西,还有既然有Write Concern,是不是还有Read Concern?这个就有待下回分解啦!
上滑加载中