提高nodejs中promise的性能

提高nodejs中promise的性能

我们先来看一个常见问题,假设我们有 N 条记录需要处理,或者例如,为每条记录发出 API 请求以获取数据。

通常情况下我们都是使用promise.all方法来实现这一需求:

// 记录
const data = [{}, {}, {}];// 处理所有的数据(获取所有记录的数据)
const results = await Promise.all(data.map(processRecord)); 

所以这里发生的是我们正在“并行”执行异步代码,所以它在时间线上可能看起来像这样。

在这里插入图片描述

可能我们都知道promise.all方法就是当最后一个promise也解决完才会有结果。 正如上图所看到的,Promise.all()的整体运行时间与批次中最慢的一样长。所以我们的主线程基本上是“什么都不做”,正在等待最慢的请求完成。

为了解决这个问题,本文将讲解如何使用更好的优化这个方法。

promise pool

Promise Pool 的想法是充分利用 Node.js 的主线程的潜力。为了实现更好的利用率,我们需要密集地打包 API 调用(或任何其他异步任务),这样我们就不会等待最长时间的调用完成,而是在第一个调用完成后立即安排下一个调用。

在这里插入图片描述
通过这种方式,我们可以实现多种目标:

  • 通过设置Promise Pool的并发数来控制我们服务的吞吐量;
  • 通过设置Promise Pool的并发度来管理下游服务的负载;
  • 提高我们应用程序的性能;减少 CPU 的空闲时间。

实现

让我们从需求开始。我们希望能够为 Promise Pool 提供一些记录和并发“请求”的最大数量以及可以进行处理的回调。处理后,我们希望收到每个单独记录的处理结果数组。

类似下面的方式:

const results = await PromisePool.for([]).withConcurrency(50).process(async (data) => {return result;});

我们需要跟踪当前正在处理的记录数、总共处理了多少条记录以及其他参数。

const { EventEmitter } = require("events");
class PromisePool {constructor(data,concurrency,processor,) {this.data = data;this.results = [];this.concurrency = concurrency;this.inFlightTasks = 0;this.processedEntries = 0;this.processor = processor;this.eventsEmitter = new EventEmitter();this.executionPromise = null;}
}

让我们添加一个结构方法以及公共函数来配置并发并指定处理器回调。

static for(data) {return new this(data, 10, () => {})}withConcurrency(concurrency) {this.concurrency = concurrency;return this;}process(processor) {this.processor = processor;return this._processRecords();}

我们已经建立了基础,现在我们需要实现 Promise Pool 的关键。这个想法很简单:

我们迭代数组中的每个记录并等待可用的坐位(基本上是我们拥有的并发坐位数量中的空闲坐位),如果我们有空闲坐位,我们将继续安排以下作业,除非所有坐位都被占用。如果所有座位都被占用,我们会等待至少一项工作完成。

 _waitAvailableSit() {if (this.inFlightTasks >= this.concurrency) {return new Promise((res) => {this.eventsEmitter.once(PromisePool.TASK_COMPLETED, res);});} else {return Promise.resolve();}}async _processRecord(data) {try {this.inFlightTasks++;const result = await this.processor(data, this);this.results.push(result);} catch (e) {} finally {this.inFlightTasks--;this.processedEntries++;this.eventsEmitter.emit(PromisePool.TASK_COMPLETED);if (this.inFlightTasks === 0 && this.processedEntries === this.data.length) {this.eventsEmitter.emit(PromisePool.DRAIN)}}}_processRecords() {if (this.executionPromise !== null) {return this.executionPromise;}this.executionPromise = new Promise(async (res, rej) => {try {for (const element of this.data) {await this._waitAvailableSit();this._processRecord(element);}this.eventsEmitter.once(PromisePool.DRAIN, () => res(this.results));} catch (e) {rej(e)}});return this.executionPromise;}

那么,一个可以处理并发“请求”的promise pool就写好了。

性能测试

我们将把处理器模拟为需要 150 到 1150 毫秒执行的函数。

const processor = (i) => {return new Promise((res, rej) => {console.log(`[${Date.now()}] 开始处理 item. i=${i}`);setTimeout(() => {console.log(`[${Date.now()}] 开始处理 item. i=${i}`);res(i);}, 150 + Math.random() * 1000);});
}

然后我们将同时处理 1000 条记录的数组,分成 50 条记录的块。首先,我们使用 Promise.all 方法对其进行测试。

async function main() {const timeStart = Date.now();const data = Array.from({ length: 1000 }).map((d,i) => i);while (data.length > 0) {const arr = data.splice(0, 50);await Promise.all(arr.map(processor));}const timeEnd = Date.now();console.log(`Promise.all=${timeEnd-timeStart}`);
}

然后是使用刚刚写的PromisePool

async function main() {const timeStart = Date.now();const data = Array.from({ length: 1000 }).map((d,i) => i);const promisePool = PromisePool.for(data).withConcurrency(50);await promisePool.process(processor);const timeEnd = Date.now();console.log(`PromisePool=${timeEnd-timeStart}`);
}

在这里插入图片描述

经过测试,使用 Promise.all 处理所有记录平均需要 22.9 秒,而 PromisePool 需要 12.9 秒,快了大约 40%!(大家可以代码复制下来去跑一下)

Promise Pool是同时处理多个记录的绝佳模式,它不仅可以帮助我们提高应用程序的性能,还可以让我们控制下游服务的速率限制,如果大家感兴趣可以自行扩展一下。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/51289.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

枚举和反射

枚举 枚举 枚举是一种特殊的类,它可以有自己的属性、方法和构造方法。 两种枚举的方法 自定义枚举 a.将构造器私有化,防止外部直接new b.去掉set方法,防止属性被修改 c.在内部直接创建固定的对象 通过类名直接去访问 关键字枚举 用…

2023.8各大浏览器11家对比:Edge/Chrome/Opera/Firefox/Tor/Vivaldi/Brave,安全性,速度,体积,内存占用

测试环境:全默认设置的情况下,均在全新的系统上进行测试,系统并未进行任何改动,没有杀毒软件,浏览器进程全部在后台,且为小窗模式,小窗分辨率均为浏览器厂商默认缩放大小(变量不唯一)&#xff0…

DevExpress WinForms数据编辑器组件,提供丰富的数据输入样式!(二)

DevExpress WinForms超过80个高影响力的WinForms编辑器和多用途控件,从屏蔽数据输入和内置数据验证到HTML格式化,DevExpress数据编辑库提供了无与伦比的数据编辑选项,包括用于独立数据编辑或用于容器控件(如Grid, TreeList和Ribbon)的单元格。…

nginx会话保持

ip_hash:通过IP保持会话 作用: nginx通过后端服务器地址将请求定向的转发到服务器上。 将客户端的IP地址通过哈希算法加密成一个数值 如果后端有多个服务器,第一次请求到服务器A, 并在务器登录成功,那么再登录B服务器就要重新…

AIGC ChatGPT 制作地图可视化分析

地图可视化分析是一种将数据通过地图的形式进行展示的方法,可以让人们更加直观、快速、准确的理解和分析数据。以下是地图可视化分析的一些主要好处: 加强数据理解:地图可视化可以将抽象的数字转化为直观的图形,帮助我们更好地理解…

科技赋能,教育革新——大步迈向体育强国梦

在 "全民健身"、"体育强国建设"战略的推进下,体育考试成绩被纳入重要升学考试且分值不断提高,体育科目的地位逐步上升到前所未有的高度,在此趋势下,体育教学正演变出更多元化、个性化的需求。然而现实中却面临…

Python标准库概览

Python标准库概览 知识点 标准库: turtle库(必选)标准库: random库(必选)、time库(可选) 知识导图 1、turtle库概述 turtle(海龟)是Python重要的标准库之一,它能够进行基本的图形绘制。turtle库绘制图形有一个基本框架&#x…

RabbitMQ特性介绍和使用案例

❤ 作者主页:李奕赫揍小邰的博客 ❀ 个人介绍:大家好,我是李奕赫!( ̄▽ ̄)~* 🍊 记得点赞、收藏、评论⭐️⭐️⭐️ 📣 认真学习!!!🎉🎉 文章目录 RabbitMQ特性…

Web 开发 Django 管理工具

上次为大家介绍了 Django 的模型,通过模型就可以操作数据库,从而就可以改变页面的展示内容,那问题来了,我们只能通过手动编辑模型文件来配置模型吗?当然不是,Django 为我们提供了强大的工具,可以…

【JavaEE】Spring全家桶实现AOP-统一处理

【JavaEE】AOP(2) 文章目录 【JavaEE】AOP(2)1. 统一登录校验处理1.1 自定义拦截器1.2 将自定义拦截器加入到系统配置1.3 测试1.4 对于静态资源的处理1.5 小练习:统一登录拦截处理1.6 拦截器原理1.6.1 执行流程1.6.2 源…

matlab 最小二乘拟合二维直线(直接求解法)

目录 一、算法原理二、代码实现三、结果展示本文由CSDN点云侠原创,原文链接。如果你不是在点云侠的博客中看到该文章,那么此处便是不要脸的爬虫。 一、算法原理 平面直线的表达式为: y = k x + b

死锁的典型情况、产生的必要条件和解决方案

前言 死锁:多个线程同时被阻塞,他们中的一个或全部都在等待某个资源被释放。由于线程被无限期地阻塞,因此程序不可能正常终止。 目录 前言 一、死锁的三种典型情况 (一)一个线程一把锁 (二)…

==和===的区别(经典面试题,你不知道的细节)

全等运算符 又叫全等运算符,结果会返回一个布尔值,在数据类型相同的情况下,会比较值,值相同才返回true "1" 1 // false NaN NaN // false undefined undefined // true相等运算符 相等运算符在比较两个变量是否相…

dolphinscheduler的僵尸任务清理和清理一直在运行的任务状态

dolphinscheduler的僵尸任务清理 界面操作不了的 只能去数据库更改状态或则删除掉 原因:海豚调度中有几百条僵尸任务, 界面怎么也删不掉,想从数据库中删除,开始查找从数据库删除的办法。 参考以下脚本,结合我库中僵尸…

缓存的设计方式

问题情况: 当有大量的请求到内部系统时,若每一个请求都需要我们操作数据库,例如查询操作,那么对于那种数据基本不怎么变动的数据来说,每一次都去数据库里面查询,是很消耗我们的性能 尤其是对于在海量数据…

抖音火山引擎推出免费域名DNS和公共DNS服务

抖音旗下的云计算服务火山引擎最近推出了"TrafficRoute DNS 套件"服务,其中包括两款产品,对软希网来说非常有用。 1.域名DNS: 这是一个用于网站域名的DNS服务,可以加速域名解析速度,从而提升网站的速度。如…

回归预测 | MATLAB实现GA-RF遗传算法优化随机森林算法多输入单输出回归预测(多指标,多图)

回归预测 | MATLAB实现GA-RF遗传算法优化随机森林算法多输入单输出回归预测(多指标,多图) 目录 回归预测 | MATLAB实现GA-RF遗传算法优化随机森林算法多输入单输出回归预测(多指标,多图)效果一览基本介绍程…

mysql的登录与退出

mysql是c/s架构,意味着同时要有客户端和服务端 1 找到客户端。mysql.exe的安装目录 打开命令行 2 输入对应的服务器的ip,如果是本地,就是Localhost,如果是远程服务器,那就输入对应ip/域名。并且指定mysql监听的端口 …

做一个蛋糕店小程序需要哪些步骤?

对于一些不懂技术的新手来说,创建蛋糕店小程序可能会感到有些困惑。但是,有了乔拓云平台的帮助,你可以轻松地创建自己的蛋糕店小程序。下面,我将为大家详细介绍一下具体的操作步骤。 首先,登录乔拓云平台并进入后台管理…

Mysql报错 mysqladmin flush-hosts

出现这个的原因是错误连接达到数据库设置的最大值。 此时需要释放重置连接最大值。 进入mysql使用命令 flush-hosts;环境说明: 内网测试服务器192.168.18.251 为WEB服务器,安装了mysql; 内网音视频转码服务器192.168.18.253安装了转码工具&#xff0…