大量回调的 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 性能 的相关文章

  • 在 Vue.js 中从父组件执行子方法

    目前 我有一个 Vue js 组件 其中包含其他组件的列表 我知道使用 vue 的常见方式是将数据传递给孩子 并从孩子向父母发出事件 但是 在这种情况下 我想在子组件中的按钮出现时执行子组件中的方法 parent被点击 哪种方法最好 一种建
  • 如何重定向到 instagram://user?username={username}

    我的 html 页面上有这个链接 可以在特定用户上打开 Instagram 应用程序 a href Link to Instagram Profile a 我一直在寻找自动运行 url instagram user username USE
  • Node.js:无法从同一网络上的不同设备访问服务器

    注意 还有其他一些人也遇到过类似的问题 但这些问题是通过修复代码中涉及服务器如何侦听的小花絮来解决的 在我看到的示例中 他们将 127 0 0 1 作为参数放在http createServer listen 但是 我没有同样的问题 当我尝
  • 我想检查 $('#td1').text() === "x" 是否?

    我想检查innerHtml是否有X或O 所以我不能再次添加任何其他东西 但它不起作用 添加检查代码后它就停止了 我在这里尝试做一个简单的XO游戏来更熟悉javascript和jquery 我也不确定是否可以用 jQuery 做到这一点
  • Socket.io - “套接字 ID”是否被视为敏感信息?

    我正在使用 Node js 和 socket io 实现一个简单的聊天应用程序 想知道在整个对象中共享所有客户端的套接字 ID 是否被认为是一个好的做法 为了解释一下 我的每个用户都是这样表示的 nick John Doe dateJoin
  • 检查 JavaScript 字符串是否为 URL

    JavaScript 有没有办法检查字符串是否是 URL 正则表达式被排除在外 因为 URL 很可能是这样写的stackoverflow 也就是说它可能没有 com www or http 如果你想检查一个字符串是否是有效的 HTTP UR
  • Javascript正则表达式用于字母字符和空格? [关闭]

    这个问题不太可能对任何未来的访客有帮助 它只与一个较小的地理区域 一个特定的时间点或一个非常狭窄的情况相关 通常不适用于全世界的互联网受众 为了帮助使这个问题更广泛地适用 访问帮助中心 help reopen questions 我需要一个
  • Node.js:如何在检索数据(块)时关闭响应/请求

    我正在用 node js 构建一个应用程序 它加载多个页面并分析内容 因为 node js 发送块 所以我可以分析这些块 如果一个块包含例如索引 nofollow 我想关闭该连接并继续其余部分 var host example com to
  • 从未用 @flow 标记的导入文件中获取类型定义

    TL DR我怎么告诉flow从未声明的导入模块导入类型定义 flow 加长版 流接缝能够从不使用流语法的文件中派生类型 请参阅示例 示例文件 flow js if Math random lt 0 5 var y hello else va
  • 标签获取 href 值

    我有以下 html div class threeimages a img alt Australia src Images Services 20button tcm7 9688 gif a div class text h2 a hre
  • 音频 blob 的 URL.createObjectURL 在 Firefox 中给出 TypeError

    我正在尝试从创建的音频 blob 创建对象 URLgetUserMedia 该代码在 Chrome 中可以运行 但在 Firefox 中存在问题 错误 当我打电话时stopAudioRecorder 它停在audio player src
  • 表单计算器脚本基本价格未加载 OnLoad

    我的表单中有一个计算器来计算我的下拉选项选择 function select calculate on change calc input type checkbox calculate on click calc function cal
  • 通过 CDN 使用 Dojo 时如何加载自定义 AMD 模块?

    我正在使用 google 的 CDN 并尝试使用他们的加载程序加载我自己的 AMD 模块 我知道我做错了什么 但我被困住了 有任何想法吗
  • Grails 在 javascript 内的 GSP 站点中使用 grails var

    我有一个在 GSP 文件中的 javascript 代码中使用 grails 变量值的问题 例如 我有一个会话值session getAttribute selectedValue 我想在 javascript 代码部分使用这个值 我现在的
  • 为什么在 Internet Explorer 中访问 localStorage 对象会引发错误?

    我正在解决一个客户端问题 Modernizr 意外地没有检测到对localStorageInternet Explorer 9 中的对象 我的页面正确使用 HTML 5 文档类型 并且开发人员工具报告该页面具有 IE9 的浏览器模式和 IE
  • 为什么我不能在 AngularJS 中使用 data-* 作为指令的属性名称?

    On the t他的笨蛋 http plnkr co edit l3KoY3 p preview您可以注意到属性名称模式的奇怪行为data 在指令中 电话 Test of data named attribute br
  • 摆脱node-jsx

    在我的 NodeJS 应用程序的路由器中 我想渲染一个 React 应用程序 由于它没有被浏览器化 并且已反应 因此它返回unexpected token lt 构建时出错 我发现如果我require node jsx install 它不
  • JQuery 图像上传不适用于未来的活动

    我希望我的用户可以通过帖子上传图像 因此 每个回复表单都有一个上传表单 用户可以通过单击上传按钮上传图像 然后单击提交来提交帖子 现在我的上传表单可以上传第一个回复的图像 但第二个回复的上传不起作用 我的提交过程 Ajax 在 php 提交
  • 为什么 jquery 没有检测到单选按钮未被选中的情况? [复制]

    这个问题在这里已经有答案了 可能的重复 JQuery radioButton change 在取消选择期间不会触发 https stackoverflow com questions 5176803 jquery radiobutton c
  • 导致回发到与弹出窗口不同的页面

    我有一个主页和一个详细信息页面 详细信息页面是从主页调用的 JavaScript 弹出窗口 当单击详细信息页面上的 保存 按钮时 我希望主页 刷新 是否有一种方法可以调用主页的回发 同时还可以从详细信息页面维护保存回发 Edit 使用win

随机推荐

  • 如何替换回车符

    我有一个变量 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应用程序