跟踪 Node.js 中的高内存使用率
在本文中,我将分享我在 Node.js 中跟踪和修复高内存使用率的方法。
内容
- 上下文
-
方法
- 理解代码
- 单独复制问题
- 从暂存服务捕获配置文件
- 验证修复
- 结果
- 结论
语境
最近我收到了一张标题为“修复库 x 中的内存泄漏问题”的票证。该描述包括一个 Datadog 仪表板,其中显示了十几个因内存使用率过高并最终因 OOM(内存不足)错误而崩溃的服务,并且它们都有共同的 x 库。
我最近才接触到代码库(不到 2 周),这使得这项任务充满挑战性,也值得分享。
我开始使用两条信息:
- 有一个所有服务都使用的库导致内存使用率很高,它涉及到redis(库的名称中包含redis)。
- 受影响的服务列表。
下面是链接到票证的仪表板:
服务在 Kubernetes 上运行,很明显,服务会随着时间的推移积累内存,直到达到内存限制、崩溃(回收内存)并重新启动。
方法
在本节中,我将分享我如何处理手头的任务,找出高内存使用率的罪魁祸首并随后修复它。
理解代码
由于我对代码库相当陌生,我首先想了解代码、相关库的作用以及它应该如何使用,希望通过这个过程可以更容易地识别问题。不幸的是,没有适当的文档,但通过阅读代码和搜索服务如何利用该库,我能够理解它的要点。它是一个围绕 redis 流的库,并为事件生成和消费提供方便的接口。花了一天半的时间阅读代码,由于代码结构和复杂性(很多我不熟悉的类继承和rxjs),我无法掌握所有细节以及数据如何流动。
因此我决定暂停阅读,并在观察代码运行情况并收集遥测数据的同时尝试发现问题。
单独复制该问题
由于没有可用的分析数据(例如连续分析)可以帮助我进一步调查,因此我决定在本地复制该问题并尝试捕获内存配置文件。
我发现了几种在 Node.js 中捕获内存配置文件的方法:
- 使用堆快照
- 使用堆分析器
- JavaScript 性能分析
- Clinic.js
由于不知道去哪里寻找,我决定运行我认为是库中最“数据密集”的部分,即 redis 流生产者和消费者。我构建了两个简单的服务,它们可以生成和使用来自 redis 流的数据,然后我继续捕获内存配置文件并比较一段时间内的结果。不幸的是,在对服务产生负载并比较配置文件几个小时后,我无法发现这两个服务中任何一个服务的内存消耗有任何差异,一切看起来都很正常。该库公开了一系列不同的接口以及与 Redis 流交互的方式。我很清楚,复制该问题比我预期的要复杂得多,尤其是在我对实际服务的特定领域知识有限的情况下。
所以问题是,如何找到合适的时机和条件来捕获内存泄漏?
从暂存服务捕获配置文件
如前所述,捕获内存配置文件的最简单、最方便的方法是对受影响的实际服务进行连续分析,但我没有这个选项。我开始研究如何至少利用我们的暂存服务(它们面临同样的高内存消耗),这将使我无需额外的努力即可捕获所需的数据。
我开始寻找一种将 Chrome DevTools 连接到其中一个正在运行的 Pod 并随着时间的推移捕获堆快照的方法。我知道内存泄漏发生在暂存阶段,因此,如果我能够捕获该数据,我希望能够至少发现一些热点。令我惊讶的是,有一种方法可以做到这一点。
执行此操作的过程
- 通过向 pod 上的节点进程发送 SIGUSR1 信号来启用 pod 上的 Node.js 调试器。
kubectl exec -it <nodejs-pod-name> -- kill -SIGUSR1 <node-process-id>
更多关于 Signal Events 中的 Node.js 信号
如果成功,您应该会看到来自服务的日志:
Debugger listening on ws://127.0.0.1:9229/.... For help, see: https://nodejs.org/en/docs/inspector
- 通过运行在本地公开调试器正在侦听的端口
kubectl port-forward <nodejs-pod-name> 9229
- 将 Chrome Devtools 连接到您在之前步骤中启用的调试器。访问 chrome://inspect/,您应该在目标列表中看到您的 Node.js 进程:
如果没有,请确保您的目标发现设置正确设置
现在您可以开始捕获超时快照(时间段取决于发生内存泄漏所需的时间)并进行比较。 Chrome DevTools 提供了一种非常方便的方法来做到这一点。
您可以在记录堆快照中找到有关内存快照和 Chrome 开发工具的更多信息
创建快照时,主线程中的所有其他工作都会停止。根据堆内容,甚至可能需要一分多钟的时间。快照内置在内存中,因此它可以使堆大小加倍,从而导致填满整个内存,然后使应用程序崩溃。
如果您要在生产中获取堆快照,请确保从中获取快照的进程可以崩溃,而不会影响应用程序的可用性。
来自 Node.js 文档
回到我的例子,选择两个快照进行比较并按增量排序,我得到了您在下面看到的内容。
我们可以看到最大的正增量发生在字符串构造函数上,这意味着该服务在两个快照之间创建了很多字符串,但它们仍在使用中。现在的问题是它们是在哪里创建的以及谁在引用它们。幸运的是,捕获的快照包含了这些信息,也称为 Retainers。
在深入研究快照和永不缩小的字符串列表时,我注意到一种类似于 id 的字符串模式。单击它们,我可以看到引用它们的链对象 - 又名保留器。这是一个名为 sendEvents 的数组,其类名是我可以从库代码中识别出来的。哎呀,我们找到了罪魁祸首,一个不断增长的 id 列表,到目前为止我认为这些列表从未被发布过。我超时拍摄了一堆快照,这是唯一一个不断重新出现为具有较大正增量的热点的地方。
验证修复情况
有了这些信息,我不需要尝试完全理解代码,而是需要关注数组的用途、何时填充和何时清除。在一个地方,代码将项目推送到数组,而在另一个地方,代码将项目弹出,这缩小了修复的范围。
可以安全地假设数组在应该清空的时候没有被清空。跳过代码的细节,基本上发生的事情是这样的:
- 该库公开了用于消费、生成事件或生成和消费事件的接口。
- 当它既消耗又产生事件时,它需要跟踪进程本身产生的事件,以便跳过它们而不重新消耗它们。 sendEvents 在生成时被填充,并在尝试使用时被清除,它会跳过消息。
你能看出这是怎么回事吗? ?当服务仅使用该库来生成事件时,sentEvents 仍会填充所有事件,但没有代码路径(使用者)用于清除它。
我修补了代码以仅跟踪生产者、消费者模式上的事件并部署到登台。即使存在暂存负载,很明显该补丁也有助于减少高内存使用率,并且没有引入任何回归。
结果
当补丁部署到生产环境中时,内存使用量大幅减少,服务的可靠性得到提高(不再出现 OOM)。
一个很好的副作用是处理相同流量所需的 Pod 数量减少了 50%。
结论
对于我来说,这是一个很好的学习机会,可以跟踪 Node.js 中的内存问题并进一步熟悉可用的工具。
我认为最好不要详细讨论每个工具的细节,因为这值得单独发表一篇文章,但我希望这对于任何有兴趣了解更多有关此主题或面临类似问题的人来说是一个很好的起点。
以上是跟踪 Node.js 中的高内存使用率的详细内容。更多信息请关注PHP中文网其他相关文章!

热AI工具

Undresser.AI Undress
人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover
用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool
免费脱衣服图片

Clothoff.io
AI脱衣机

Video Face Swap
使用我们完全免费的人工智能换脸工具轻松在任何视频中换脸!

热门文章

热工具

记事本++7.3.1
好用且免费的代码编辑器

SublimeText3汉化版
中文版,非常好用

禅工作室 13.0.1
功能强大的PHP集成开发环境

Dreamweaver CS6
视觉化网页开发工具

SublimeText3 Mac版
神级代码编辑软件(SublimeText3)

JavaScript是现代Web开发的基石,它的主要功能包括事件驱动编程、动态内容生成和异步编程。1)事件驱动编程允许网页根据用户操作动态变化。2)动态内容生成使得页面内容可以根据条件调整。3)异步编程确保用户界面不被阻塞。JavaScript广泛应用于网页交互、单页面应用和服务器端开发,极大地提升了用户体验和跨平台开发的灵活性。

JavaScript的最新趋势包括TypeScript的崛起、现代框架和库的流行以及WebAssembly的应用。未来前景涵盖更强大的类型系统、服务器端JavaScript的发展、人工智能和机器学习的扩展以及物联网和边缘计算的潜力。

不同JavaScript引擎在解析和执行JavaScript代码时,效果会有所不同,因为每个引擎的实现原理和优化策略各有差异。1.词法分析:将源码转换为词法单元。2.语法分析:生成抽象语法树。3.优化和编译:通过JIT编译器生成机器码。4.执行:运行机器码。V8引擎通过即时编译和隐藏类优化,SpiderMonkey使用类型推断系统,导致在相同代码上的性能表现不同。

Python更适合初学者,学习曲线平缓,语法简洁;JavaScript适合前端开发,学习曲线较陡,语法灵活。1.Python语法直观,适用于数据科学和后端开发。2.JavaScript灵活,广泛用于前端和服务器端编程。

JavaScript是现代Web开发的核心语言,因其多样性和灵活性而广泛应用。1)前端开发:通过DOM操作和现代框架(如React、Vue.js、Angular)构建动态网页和单页面应用。2)服务器端开发:Node.js利用非阻塞I/O模型处理高并发和实时应用。3)移动和桌面应用开发:通过ReactNative和Electron实现跨平台开发,提高开发效率。

本文展示了与许可证确保的后端的前端集成,并使用Next.js构建功能性Edtech SaaS应用程序。 前端获取用户权限以控制UI的可见性并确保API要求遵守角色库

从C/C 转向JavaScript需要适应动态类型、垃圾回收和异步编程等特点。1)C/C 是静态类型语言,需手动管理内存,而JavaScript是动态类型,垃圾回收自动处理。2)C/C 需编译成机器码,JavaScript则为解释型语言。3)JavaScript引入闭包、原型链和Promise等概念,增强了灵活性和异步编程能力。

我使用您的日常技术工具构建了功能性的多租户SaaS应用程序(一个Edtech应用程序),您可以做同样的事情。 首先,什么是多租户SaaS应用程序? 多租户SaaS应用程序可让您从唱歌中为多个客户提供服务
