大量回调的 NodeJS 性能

2024-01-03

我正在开发 NodeJS 应用程序。有一个特定的 RESTful API (GET),当用户触发时,它要求服务器执行大约 10-20 个网络操作,以从不同来源提取信息。所有这些网络操作都是异步回调,一旦它们全部完成,结果将由nodejs应用程序合并并发回客户端。所有这些操作都是通过 async.map 函数并行启动的。

我只是想了解,由于nodejs是单线程的,并且它不使用多核机器(至少在没有集群的情况下),当节点有很多回调需要处理时,它如何扩展?回调的实际处理是否取决于节点的单个线程是否空闲,或者回调是否与主线程并行处理?

我之所以问这个问题,是因为我发现从第一个回调到最后一个回调,我的 20 个回调的性能都在恶化。例如,第一个网络操作(10-20 个)需要 141 毫秒才能完成,而最后一个网络操作大约需要 4 秒(以从执行函数到函数回调返回一个值或一个错误)。它们都是相同的网络操作,访问相同的数据源,因此数据源不是瓶颈)。我知道数据源响应单个请求的时间不会超过200ms。

我找到了这个thread https://stackoverflow.com/questions/18080953/nodejs-callback-mechanism-which-thread-handles-the-callback,所以在我看来,一个线程需要处理所有回调和即将出现的新请求。

所以我的问题是,对于会触发许多回调的操作,优化其性能的最佳实践是什么?


对于网络操作,node.js 实际上是单线程的。然而,人们一直存在一个误解,即处理 I/O 需要持续的 CPU 资源。你的问题的核心归结为:

回调的实际处理是否取决于节点的单个线程是否空闲,或者回调是否与主线程并行处理?

答案是肯定和否定。是的,回调仅在主线程空闲时执行。不,线程空闲时不会完成“处理”。具体来说:没有“处理”——如果您所说的“进程”正在等待,那么节点“处理”数千个回调所需的 CPU 时间为零。

异步 I/O 的工作原理(在任何编程语言中)

Hardware

如果我们真的需要了解节点(或浏览器)内部如何工作,不幸的是我们必须首先了解计算机如何工作 - 从硬件到操作系统。是的,这将是一个深入的研究,所以请耐心等待。

这一切都始于中断的发明。

这是一项伟大的发明,也是潘多拉魔盒——埃兹格·迪杰斯特拉 (Edsger Dijkstra)

是的,上面的引文出自同一个“Goto 被认为有害”Dijkstra。从一开始,将异步操作引入计算机硬件就被认为是一个非常困难的话题,即使对于业内的一些传奇人物来说也是如此。

引入中断是为了加速 I/O 操作。硬件不需要用软件轮询某些输入(占用 CPU 时间进行有用的工作),而是向 CPU 发送信号,告诉它发生了事件。然后CPU将挂起当前正在运行的程序并执行另一个程序来处理中断——因此我们将这些函数称为中断处理程序。 “处理程序”这个词一直停留在 GUI 库的堆栈中,这些库将回调函数称为“事件处理程序”。

如果您一直注意的话,您会注意到中断处理程序的概念实际上是callback。您可以将 CPU 配置为在事件发生时稍后调用某个函数。因此,即使是回调也不是一个新概念——它比 C 语言还要古老。

OS

中断使现代操作系统成为可能。如果没有中断,CPU 就无法暂时停止程序运行操作系统(当然,存在协作式多任务处理,但我们暂时忽略它)。操作系统的工作原理是,它在CPU中设置一个硬件定时器来触发中断,然后告诉CPU执行您的程序。正是这个周期性的定时器中断运行着你的操作系统。除了定时器之外,操作系统(或者更确切地说是设备驱动程序)还为 I/O 设置中断。当 I/O 事件发生时,操作系统将接管您的 CPU(或多核系统中的一个 CPU)并检查其数据结构,接下来需要执行哪个进程来处理 I/O(这称为抢占式多任务处理)。

因此,处理网络连接甚至不是操作系统的工作 - 操作系统只是跟踪其数据结构(或更确切地说,网络堆栈)中的连接。真正处理网络 I/O 的是您的网卡、路由器、调制解调器、ISP 等。因此,等待 I/O 占用的 CPU 资源为零。它只是占用一些 RAM 来记住哪个程序拥有哪个套接字。

流程

现在我们已经清楚地了解了这一点,我们可以理解该节点的作用。各种操作系统有各种不同的 API 来提供异步 I/O - 从 Windows 上的重叠 I/O 到 Linux 上的 poll/epoll 到 BSD 上的 kqueue 到跨平台select()。 Node 在内部使用 libuv 作为这些 API 的高级抽象。

这些 API 的工作原理相似,但细节有所不同。本质上,它们提供了一个函数,当调用该函数时,该函数将阻塞您的线程,直到操作系统向其发送事件。所以,是的,即使是非阻塞 I/O 也会阻塞你的线程。这里的关键是阻塞 I/O 会在多个地方阻塞你的线程,但非阻塞 I/O 只会在一处阻塞你的线程 - 等待事件的地方。

这允许您以面向事件的方式设计您的程序。这类似于中断让操作系统设计者实现多任务处理的方式。实际上,异步 I/O 之于框架就像中断之于操作系统。它允许节点花费恰好 0% 的 CPU 时间来处理(等待)I/O。这就是节点快速的原因——它并不是真正更快,但不会浪费时间等待。

回调处理

通过了解节点如何处理网络 I/O,我们可以了解回调如何影响性能。

  1. 有数千个回调等待,CPU 损失为零

    当然,节点仍然需要在 RAM 中维护数据结构来跟踪所有回调,因此回调确实会带来内存损失。

  2. 处理回调的返回值是在单个线程中完成的

    这有一些优点和一些缺点。这意味着节点不必担心竞争条件,因此节点内部不会使用任何信号量或互斥体来保护数据访问。缺点是任何 CPU 密集型 JavaScript 都会阻塞所有其他操作。

你提到:

我发现 20 个回调的性能从第一个回调到最后一个回调都在恶化

回调全部在主线程中顺序同步执行(只有等待实际上是并行完成的)。因此,您的回调可能正在执行一些 CPU 密集型计算,并且所有回调的总执行时间实际上是 4 秒。

但是,对于如此数量的回调,我很少看到此类问题。这仍然是可能的,我仍然不知道你在回调中做了什么。我只是觉得不太可能。

您还提到:

直到函数的回调返回值或错误

一种可能的解释是您的网络资源无法处理那么多同时连接。您可能认为这不算什么,因为只有 20 个连接,但我见过很多服务在每秒 10 个请求时会崩溃。问题是所有 20 个请求都是同时发生的。

您可以通过将节点排除在外并使用命令行工具同时发送 20 个请求来测试这一点。就像是curl or wget:

# assuming you're running bash:
for x in `seq 1 20`;do curl -o /dev/null -w "Connect: %{time_connect} Start: %{time_starttransfer} Total: %{time_total} \n" http://example.com & done

减轻

如果问题是同时执行 20 个请求给其他服务带来了压力,您可以做的就是限制同时请求的数量。

您可以通过批量请求来做到这一点:

async function () {
    let input = [/* some values we need to process */];
    let result = [];

    while (input.length) {
        let batch = input.splice(0,3); // make 3 requests in parallel

        let batchResult = await Promise.all(batch.map(x => {
            return fetchNetworkResource(x);
        }));

        result = result.concat(batchResult);
    }
    return result;
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

大量回调的 NodeJS 性能 的相关文章

  • 如何检查 webgl(two.js) 的客户端性能

    我有一个使用 Three JS 的图形项目 现在我想自动检查客户端 GPU 性能并计算可以在应用程序中加载多少元素 我想到了诸如 GPU 基准测试之类的东西 看一眼stats js https github com mrdoob stats
  • 使用 classed("active",true) 鼠标悬停时 D3 颜色变化

    我是 js 和 D3 的新手 我已经生成了某种热图 并且想使用 D3 的 on mouseover 更改图块的颜色 我可以显式更改颜色 但想使用 CSS 活动规则 可能是一些简单的东西可以修复 任何帮助将不胜感激 完整代码如下 Thanks
  • 我可以在 .split() 之后直接访问数组的第二个值吗?

    我有这个代码 var tmp this attr id split 我想将分割后的第二个值存储在 tmp 上 因此 如果 this attr id hello marco 我想在 tmp 中存储值 marco 而不是数组 当然 我想直接用一
  • 从 Flow 中的对象值定义联合类型

    我有一个这样的枚举 const Filter ALL ALL COMPLETED COMPLETED UNCOMPLETED UNCOMPLETED 我想做的是声明一个联合类型 如下所示 type FilterType Filter ALL
  • 当位置从相对固定变为固定时,div 的宽度会发生变化

    当 div topNav 的位置样式从相对更改为固定时 其宽度会更改几个像素 我找到了一个 jquery 插件 http imakewebthings github com jquery waypoints 它可以优雅地执行我正在寻找的相同
  • “调用”C:\Program Files\nodejs\\node.exe”错误

    我一直在尝试安装节点js并安装浏览器同步 C Users Aly gt npm install g browser sync CALL C Program Files x86 nodejs node exe C Program Files
  • chrome.extension.getBackgroundPage() 函数示例

    我正在开发一个需要在后台运行的小型 Chrome 扩展 但是 我知道当我使用弹出窗口时这是不可能的 经过一番阅读后 似乎最好的选择是创建popup js为了运行background js using chrome extension get
  • npm 错误! asyncWrite 不是一个函数

    npm 安装 g firebase tools npm 错误 asyncWrite 不是一个函数 npm 错误 pna nextTick 不是函数 npm 错误 此运行的完整日志可以在以下位置找到 npm 错误 home developer
  • 对数组进行分组并获取计数[重复]

    这个问题在这里已经有答案了 假设我有这样的数组 foo bar foo bar bar bar zoom 我想将其分组 这样我就可以得到这样的计数 foo 2 bar 4 zoom 1 有没有一个实用程序可以做到这一点 只需使用该功能Arr
  • 在 GeoJson 数据接收到的 Google 地图多边形上放置标签

    我想将带有信息的标签 或带有标签的 div 放在由下面的代码片段绘制的多边形上 样式属性已成功应用于要素 多边形类型 有谁知道如何向该特征添加文本并将其显示在多边形的中心 function handleGeoJson data map da
  • 如何从客户端 JavaScript 调用特定的 Node.js 方法

    在我的应用程序中 我在 node js 文件中创建了许多方法 我如何从客户端 JavaScript 调用特定方法 下面是我的node js 文件 exports method1 function exports method2 functi
  • 垂直滚动,与 div/元素/锚点对齐/对齐

    我发现了一些可爱的网站 http www mini jp event campaign big point http www mini jp event campaign big point http www twenty8twelve c
  • 使用 multer 上传来自不同字段的多个文件?

    如何让 multer 接受来自多个文件类型字段的文件 我有以下代码 使用 node js 中的 multer 上传单个文件 var storage multer diskStorage destination function req fi
  • 如何检查令牌过期和注销用户?

    当用户单击注销按钮时 他 她可以自己注销 但是如果令牌过期 他 她就无法注销 因为在我的应用程序中 令牌在服务器端和前端都使用 当用户单击注销按钮时 如果令牌有效 则服务器和浏览器中的令牌都会被清除 当用户未注销并且他 她的令牌过期但未在浏
  • Mongodb 的 Mongoose 与 Mongoose

    我正在学习 NodeJ 要从 NodeJS 连接并使用 MongoDB 我看到很多使用 Monk 或 Mongoose 的示例 这两个库等效吗 它们具有相同的功能还是都有特定的用途 作为 NodeJS 的初学者 我应该使用哪个 以下是使用
  • 猫鼬递归填充

    我已经搜索了一段时间 但没有找到任何好的答案 我有n deep我存储在数据库中并且想要填充的树所有的父母所以最后我得到了完整的树 node parent parent parent 到目前为止 我已达到 2 级 正如我提到的 我需要达到 2
  • ng-show 令人不安的 div 布局 - angularJS

    我在用ng show notesOpened 如果notesOpened 变量为true 则隐藏div 然而 当隐藏时 它会扰乱布局 有没有办法让 ng show 的行为与 css 属性相同visibility hidden 以便被隐藏的
  • 将 JSON var 从路由传递到 ejs

    我在express EJS中有一些代码 1 在 app js 中 创建 mongo 集合对象 app locals userCollection db get userData 2 在user js快速路由文件中 我从这个数据库连接获取数据
  • 是否可以使用 fs.createWriteStream 在文件中间写入文本? (或者一般在 Node.js 中)

    我正在尝试写入文本文件 但不是像appendFile 那样在最后写入或通过替换整个内容 我看到可以选择从 fs createwritestream 的启动参数开始的位置 gt https nodejs org api fs html fs
  • AngularJS 中的全局模拟对象用于 jasmine/karma 测试

    我有一个正在模拟进行单元测试的对象 基本上在我的测试文件中 我将其模拟如下 var mockObject mockMethod1 function return true mockMethod2 function return true b

随机推荐

  • 如何替换回车符

    我有一个变量 myClass 0 gt comment 其中有回车符 我想将该变量中的所有回车替换为 n 我怎样才能做到这一点 下面可能会有所帮助 myClass 0 gt comment 这是一些输出 输出 array 0 gt stri
  • 使用 React Router 在页面的某个部分内导航

    我的导航栏包含以下内容 Home About Login Home is 具有多个部分的垂直滚动页面 e g About和其他部分 虽然登录是单独的反应组件 它被渲染在 login route 这是我的route js file
  • 如何强制 SDL_Init() 失败?

    有没有可靠的方法SDL Init 在测试用例中使用失败 我认为您可以通过不包含 SDL 动态链接库 SDL dll 来强制它失败
  • 忽略“检测到源架构漂移”错误,继续更新

    我在 Visual Studio 2017 中有一个 SQL 项目 我正在使用 SSDT 从 SQL 数据库更新我的项目 如下所示 通常 我用作源的数据库正在发生变化 通常以小且不相关的方式 当发生这种情况时 我无法更新我的项目 我得到 c
  • 如何在 Laravel 5 中删除会话

    我正在尝试删除基本会话 但它没有删除 这是代码 欢迎 blade php if Session has key Session get key a href logout Sign Out a else please signin endi
  • 如何使用streamreader以当前编码读取byte[]

    我想读byte 使用 C 和文件的当前编码 正如 MSDN 中所写 当构造函数没有编码时 默认编码将为 UTF 8 var reader new StreamReader new MemoryStream data 我也尝试过 但仍然以 U
  • mysql_insert_id() 返回 0

    我知道有很多主题具有相同的标题 但主要是查询插入到了错误的位置 但我认为我的定位是正确的 所以问题是 即使数据插入到数据库中 我仍然得到 0 有人知道我可能错的答案吗 这是我的代码 mysql query SET NAMES utf8 th
  • 如何在 Visual Studio Team Services 中的托管代理上查找 Android SDK 的位置?

    我想在 VSTS 中的托管构建代理上构建我的 android 项目 因此 我创建了一个 Android 构建定义 将其在 托管 Linux 预览 代理上排队 但我的构建失败并出现以下错误 未找到 SDK 位置 使用 sdk dir 定义位置
  • 如何在没有 FlexBuilder 的情况下将 Cairngorm 的 SWC 文件添加到我的应用程序中?

    我没有使用 FlexBuilder 我使用的是免费的 Flex SDKTextMate http macromates com 我有点不明白什么这个FlexBuilder进程 http nwebb co uk blog p 58实际上 在所
  • 如何在 F# 交互中使用断点?

    我已经开始使用 VS2010 和 F 交互式研究算法中的一些想法 所以 我创建了一个DebugScript fsx 我在那里编写了一些代码 并最终将其发送到 F Int 进行测试 有时我需要捕捉一个错误 但即使是简单的我也无法放置断点for
  • 将背景图像叠加到背景颜色上

    我的网站 http www webbuddies co za http www webbuddies co za 工作完美 但是当以 1280x1024 分辨率查看时 页面底部有一点可见的白色 我想去掉它 背景是渐变图像 我只想更改背景颜色
  • 如何将 TextBlock 设置为属性值?

    I used this http www c sharpcorner com uploadfile mahesh user control in wpf 构建自定义控件的教程 现在 我想向用户控件添加一条简单的消息 文本块 来为用户提供一些
  • 禁用 mongo docker 中的默认身份验证

    我想禁用默认身份验证 避免使用 mongo authenticationDatabase auth db 在 mongo 中使用 docker compose 这是我的docker 撰写 file version 2 services mo
  • 为什么增加 Nginx 中的worker_connections 会使应用程序在node.js 集群中变慢?

    我正在将我的应用程序转换为 Node js 集群 我希望它能够提高我的应用程序的性能 目前 我正在将该应用程序部署到 2 个 EC2 t2 medium 实例 我有 Nginx 作为代理和 ELB 这是我的 Express 集群应用程序 从
  • Keras 的 TensorBoard 回调中嵌入不匹配的张量数量

    我使用的是 CIFAR 10 数据集 因此有 10000 张测试图像 我成功创建了一个 tsv包含元数据的文件 10000 行中每一行的测试集标签 以人类可读的文本形式 而不是索引 但是 在 TensorBoard 中 当我打开嵌入选项卡时
  • Qt fitInView 和调整大小

    我正在尝试做一个QGraphicsView宽度与窗口中心的高度相同 我创建了一个普通的QGraphicsView在 Qt Designer 中并设置最小尺寸 添加了一些居中的计算QGraphicsView进入主窗口的中心 并将宽度设置为与高
  • 使用形状或 9 块图像创建聊天气泡

    我正在尝试在我目前正在开发的 Android 应用程序中为我的聊天气泡创建一个模板 最终结果应该是这样的 我尝试使用 形状 但无法获得正确的多个图层 我还尝试了 9 补丁图像 但创建 9 补丁是我所能做到的 我不知道如何使用它 特别是头像
  • 莺:我的影像在哪里

    我正在使用 Jruby 和 Warbler 将 Jruby on Rails 应用程序部署到 Tomcat 服务器 当我使用 Webrick 部署服务器时 我可以看到所有图像 jruby S server script 但是 当我使用 jr
  • 如何使用 runc 列出 docker 容器

    据我所知runc list允许传递容器存储的根目录 但我不知道要为 docker 传递什么根目录 我试过 var lib docker containers但它说容器不存在 我确实有容器出现在docker ps fyi 或者我假设 dock
  • 大量回调的 NodeJS 性能

    我正在开发 NodeJS 应用程序 有一个特定的 RESTful API GET 当用户触发时 它要求服务器执行大约 10 20 个网络操作 以从不同来源提取信息 所有这些网络操作都是异步回调 一旦它们全部完成 结果将由nodejs应用程序