MongoDB 系列

发布日期:2022-01-13 13:50    点击次数:65


本文转载自微信公多号「编程界」,作者五月君  。转载本文请有关编程界公多号。

几个话题

本文会按照以下几个话题进走商议与讲解,文中的现在录不十足和这几个话题相反,但当你浏览完本文后,坚信这些答案答该也有了,都在文中。

为什么要行使游标、什么时候行使? 关注服务器内存,游标什么时候关闭? 必要仔细的游标超时与容错处理 为什么不要肆意调整 batchSize 数目? 行使时需仔细 Mongoose 与原生 Node.js MongoDB 驱动程序的差别之处 解答群友题目时发现的一个关于游标的 Bug 扩展 - 为什么能够行使 for await of 遍历游标对象? 为什么要行使游标?

如许的写法 collection.find().toArray(),行家在学习 MongoDB 时答该见的也不少,它的原理是客户端驱动程序会自动把返回的一切数据一次性添载到行使程序内存中,理解首来相对浅易些,倘若数据量幼是没题目的,在一些数据处理的场景中,详细有多幼批据能够是未知的,有能够返回大量的数据,倘若通盘 hold 在内存,在服务端内存寸土寸金的地方,白白消耗服务内存不说,内存占用过高还能够造成服务 OOM。

MongoDB 内里的游标,有点相通于在 Node.js 里行使 Stream 处理文件数据,相比把整个文件读入内存在处理这栽模式,Stream 带来的收入是很大的。

很现象的一个图,来源:https://www.cnblogs.com/vajoy/p/6349817.html[1]

游标基本做事原理

当吾们行使 collection.find() 或 collection.aggregate() 返回的是一个指向该荟萃的指针,也称为游标(cursor),是不克直接访问数据的,只有当循环迭代这个游标时才会真实的从数据库荟萃读取数据。

在 Node.js 中行使很浅易,只要声援 for await of 语法,即可遍历游标返回的数据集,和平常行使 for of 遍历数组很相通,区别是 for await of 遍历的数据源是异步的。当循环迭代最先时驱动程序会行使 getMore() 命令批量从数据库荟萃中获取一批数据先缓存首来,例如 Node.js MongoDB 驱动程序每次默认批量获取 1000 条(仔细,第一次 getMore() 时实际乞求是 101 条),取决于 batchSize[2] 参数竖立,待这批数据处理完善之后,在向 MongoDB Server 实走 getMore() 赓续乞求直到游标耗尽。

以下为 Node.js 中的两栽行使示例,幼我比较选举 for await of 这栽写法。手段二 while 循环这栽写法在一个 MongoDB Node.js 驱动程序版本中存在一个 Bug 下文会介绍。

const userCursor = await collection.find();  // 倘若异国返回数据,必要做一些稀奇处理的,能够行使 userCursor.count() 或 userCursor.hasNext() if (!await userCursor.count()) {   // TODO: 挑前终结,做一些其它操作   return; }  // 手段一: for await (const user of userCursor) { }  // 手段二: while (await userCursor.hasNext()) {  const doc = userCursor.next(); } 

例如,数据库荟萃有 10000 条数据,每次批量获取 1000 条,I/O 消耗答该也为 10 次。终端链接至 MongoDB Server 竖立 db.setProfilingLevel(0, { slowms: 0 })记录一切的操作日志,之后在掀开 MongoDB Server 控制台日志,实走行使程序之后会望到如下日志新闻,每次 getMore 都指向了联相符个游标 ID getMore: 5098682199385946244。

游标读取效果.png

倘若必要修改 batchSize 效果的,经历 options 指定 batchSize 属性或调用 batchSize 手段都能够。

collection.find().batchSize(1100) // 或以着手段 collection.find({}, {   batchSize: 1100 }) 

紧记不要将 batchSize 竖立为 1,例如,10000 条数据每获取一条数据,客户端都将连接服务器读取,这将会产生 10000 次网络 IO,下图行使 mongostat 监控,展现了每秒查询游标时的 getMore 次数。

游标超时

倘若一个游标在一准时间内无人访问,超时之后会被回收,防止产生内存泄露,启动时可经历 mongod --setParameter cursorTimeoutMillis=300000 参数竖立,默认超时为 10 分钟,参见文档 cursorTimeoutMillis#Default: 600000 (10 minutes)[3]。

例如,统统查询 10000 条数据,第一次 getmore() 默认批量获取 1000 条数据,倘若在默认的 10 分钟内异国处理完善这 1000 条数据,游标会被关闭,待下次实走 getmore() 就会报错 cursor id 4011961159809892672 not found,清淡称之为游标超时。

如有遇到游标超时,可经历调整 cursorTimeoutMillis 参数或缩短 batchSize 数目选择正当于本身的程序配置,清淡默认配置是不必要调整的。例如,在遍历游标数据时调了一个外部接口,由于接口超时导致的游标超时这栽外部营业因为的,答先往优化营业本身,再考虑调整配置。

为晓畅决游标超时,你能够还见到过 cursor.addCursorFlag('noCursorTimeout', true) 如许的配置,这会禁用失踪游标的超时节制,只有等到游标耗尽或手动关闭 cursor.close() 游标才能够被开释,禁用超往往间这栽做法,很不选举行使,每个游标都存在额外的内存占用消耗,倘若由于无视遗忘手动关闭游标导致的 MongoDB Server 内存泄露就得不偿失了。

游标状态

登陆 MongoDB 客户端,实走 db.serverStatus().metrics.cursor 命令,查望现在游标行使状态。倘若真的展现游标导致的 MongoDB 服务器内存泄露,以下几个数据指标,做为运维人员在排查题目时,会有协助。

timedOut:指 MongoDB Server 进程启动到现在一切的游标超时数目,此指标逆映了行使程序由于处理耗时义务 或 游标掀开后由于报错异国表现关闭游标 这两栽情况导致的游标超时数目。 open.noTimeout:为了防止游标超时,MongoDB 挑供了一个配置 DBQuery.Option.noTimeout[4] 竖立永不超时,但倘若处理完毕遗忘表现关闭游标,会导致游标常驻内存,数目越大内存泄露的风险也越大,提出是尽量不要竖立 noTimeout。 open.pinned:“固定” 掀开游标的数目。 open.total:MongoDB Server 现在为客户端掀开的游标数目,当有游标耗尽,total 的数目也会赓续的缩短。
{  "timedOut" : NumberLong(4),  "open" : {   "noTimeout" : NumberLong(0),   "pinned" : NumberLong(0),   "total" : NumberLong(0)  } } 
游标与异步迭代器

JavaScript 在 ES6 语法挑供了一个功能叫迭代器,定义了一套联相符的接口,只要实现了该接口的数据类型,都可行使 for of 关键词遍历,例如数组、Map、Set 类型等,这些类型上有一个手段 Symbol.iterator 返回的就是一个迭代器对象,迭代器对象的 next() 手段返回值包含了 vlaue、done 两个属性,倘若 done 为 true 外示数据已遍历完善,但 Symbol.iterator 只声援同步的数据源。

而吾们从数据库荟萃获取数据涉及到网络 I/O,这是一个异步的操作,Symbol.iterator 就无法声援了,在ECMAScript 2018 标准中挑供了一个新的属性 Symbol.asyncIterator,这是一个异步迭代器,与 Symbol.iterator 差别的是 Symbol.asyncIterator 的 next() 手段返回的是一个包含 { value, done } 的 Promise 对象,倘若一个对象竖立了该属性,它就是异步可迭代对象,响答的吾们可行使 for await...of 循环遍历数据。

下面望下 MonogoDB Node.js 驱动程序在 v4.2.2 版本中的实现,同样也挑供了 Symbol.asyncIterator 接口,这也就是为什么吾们能够行使 for await...of 循环遍历。

// mongodb/lib/cursor/abstract_cursor.js class AbstractCursor extends mongo_types_1.TypedEventEmitter {   [Symbol.asyncIterator]( "Symbol.asyncIterator") {     return {       next: () => this.next().then(value => value != null ? { value, done: false }: { value: undefined, done: true })     };   } } 
容错处理

在遍历游标的过程中,for 循环体内倘若展现一些舛讹导致循环挑前终止,这个时候游标并不会被立刻烧毁,能够选择手动关闭游标或期待超过默认的游标超往往间后,游标也会被烧毁。

倘若竖立了 noCursorTimeout 属性为永不超时,这个时候就肯定记得要关闭游标,所以在上面也提出尽量不要做这个竖立。

const userCursor = await collection.find(); try {   for await (const user of userCursor) {     // 能够抛出舛讹 throw new Error('124')   } } catch (e) {   // 处理舛讹 } finally {  userCursor.close();   } 
Mongoose 必要仔细的地方

行使 mongoose 和原生声援的 mongodb 模块照样有许多迥异的,mongoose 的 find() 手段默认不会返回游标对象,必要在 find 后表现调用 cursor() 手段,且异国 cursor.count()、cursor.hasNext() 手段声援,对于一些想判定倘若游标异国数据做一些稀奇处理,处理首来不是很友益。

const userCursor = await User.find({}).cursor();  for await (const user of userCursor) { } 
一个关于游标的 Bug

在 Node.js 群里,一个群友发来新闻行使游标遇到了题目,后来也对这个题目做了一些查找和验证,下文会介绍,基于一个特定版本和特定的行使场景才会展现这个题目,放在这边也是期待用到的至交能少踩一个坑。

MongoDB Node.js 驱动程序在 3.5.4 版本基于游标迭代查询数据时,倘若用了 limit 节制返回的数据条现在,并且行使 hasNext(),存在一个 Bug,最先是从返回的游标对象掏出的 count 数偏差,其次是遍历出的数据条现在与实际 limit count 数对不上,倘若 limit 为奇数还会收到 MongoError: Cursor is closed 舛讹。

倘若必要调整每一次的 getMore() 数目,游标能够结相符 batchSize 行使。为什么用了游标还要行使 limit?这个也能够思考下。

const userCursor = await collection.find({}).limit(5); console.log('cursor count: ', await userCursor.count()); try {   while (await userCursor.hasNext()) {     const doc = await userCursor.next();     console.log(doc);   } } catch (err) {   console.error(err.stack); } userCursor.close(); 

mongodb@^3.5.4 版本输出效果:

cursor count:  10000 { _id: 61d6590b92058ddefbac6a14, userID: 0 } { _id: 61d6590b92058ddefbac6a15, userID: 1 } null MongoError: Cursor is closed     at Function.create (/test/node_modules/mongodb/lib/core/error.js:43:12)     at Cursor.hasNext (/test/node_modules/mongodb/lib/cursor.js:197:24)     at file:///test/index.mjs:42:27     at processTicksAndRejections (internal/process/task_queues.js:93:5) 

NPM 包 mongodb 受影响版本为 3.5.4 参见 issue jira.mongodb.org/browse/NODE-2483[5]NPM 包 mongoose 受影响版本为 5.9.4 参见 issue github.com/Automattic/mongoose/issues/8664[6]

参考原料

[1]https://www.cnblogs.com/vajoy/p/6349817.html: https://www.cnblogs.com/vajoy/p/6349817.html

[2]batchSize: https://docs.mongodb.com/manual/tutorial/iterate-a-cursor/#cursor-batches

[3]cursorTimeoutMillis#Default: 600000 (10 minutes): https://docs.mongodb.com/manual/reference/parameters/#mongodb-parameter-param.cursorTimeoutMillis

[4]DBQuery.Option.noTimeout: https://docs.mongodb.com/manual/reference/method/cursor.addOption/#mongodb-data-DBQuery.Option.noTimeout

[5]NPM 包 mongodb 受影响版本为 3.5.4 参见 issue jira.mongodb.org/browse/NODE-2483: https://jira.mongodb.org/browse/NODE-2483

[6]NPM 包 mongoose 受影响版本为 5.9.4 参见 issue github.com/Automattic/mongoose/issues/8664: https://github.com/Automattic/mongoose/issues/8664

【编辑选举】

鸿蒙官方战略配相符共建——HarmonyOS技术社区 数据分析的求职前景,你关心的都在这! 2022年数据中央走业发展展望 谷歌年度AI技术总结!Jeff Dean执笔,附赠20+开源工具数据大礼包 微柔:powerdir 漏洞批准访问macOS用户数据 实用:Spring的多租户数据源管理 AbstractRoutingDataSource!

Powered by 莉莉影视私人入口-看黄的免费应用软件手机官网-免费看日本生活 @2013-2021 RSS地图 HTML地图

Copyright 365站群 © 2013-2021 365建站器 版权所有