Node.js 应用故障排查手册 —— 冗余配置传递引发的内存溢出

楔子

前面一小节我们以一个真实的压测案例来给大家讲解如何利用 Node.js 性能平台 生成的 CPU Profile 分析来进行压测时的性能调优。那么与 CPU 相关的问题相比,Node.js 应用中由于不当使用产生的内存问题是一个重灾区,而且这些问题往往都是出现在生产环境下,本地压测都难以复现,实际上这部分内存问题也成为了很多的 Node.js 开发者不敢去将 Node.js 这门技术栈深入运用到后端的一大阻碍。

本节将以一个开发者容易忽略的生产内存溢出案例,来展示如何借助于性能平台实现对线上应用 Node.js 应用出现内存泄漏时的发现、分析、定位问题代码以及修复的过程,希望能对大家有所启发。

本书首发在 Github,仓库地址:https://github.com/aliyun-node/Node.js-Troubleshooting-Guide,云栖社区会同步更新。

最小化复现代码

因为内存问题相对 CPU 高的问题来说比较特殊,我们直接从问题排查的描述可能不如结合问题代码来看比较直观,因此在这里我们首先给出了最小化的复现代码,大家运行后结合下面的分析过程应该能更有收获,样例基于 Egg.js:如下所示:

'use strict';const Controller = require('egg').Controller;const DEFAULT_OPTIONS = { logger: console };class SomeClient {constructor(options) {this.options = options;}async fetchSomething() {return this.options.key;}
}const clients = {};function getClient(options) {if (!clients[options.key]) {clients[options.key] = new SomeClient(Object.assign({}, DEFAULT_OPTIONS, options));}return clients[options.key];
}class MemoryController extends Controller {async index() {const { ctx } = this;const options = { ctx, key: Math.random().toString(16).slice(2) };const data = await getClient(options).fetchSomething();ctx.body = data;}
}module.exports = MemoryController;

然后在 app/router.js 中增加一个 Post 请求路由:

router.post('/memory', controller.memory.index);

造成问题的 Post 请求 Demo 这里也给出来,如下所示:

'use strict';const fs = require('fs');
const http = require('http');const postData = JSON.stringify({// 这里的 body.txt 可以放一个比较大 2M 左右的字符串data: fs.readFileSync('./body.txt').toString()
});function post() {const req = http.request({method: 'POST',host: 'localhost',port: '7001',path: '/memory',headers: {'Content-Type': 'application/json','Content-Length': Buffer.byteLength(postData)}});req.write(postData);req.end();req.on('error', function (err) {console.log(12333, err);});
}setInterval(post, 1000);

最后我们在启动完成最小化复现的 Demo 服务器后,再运行这个 Post 请求的客户端,1s 发起一个 Post 请求,在平台控制台可以看到堆内存在一直增加,如果我们按照本书工具篇中的 Node.js 性能平台使用指南 - 配置合适的告警 一节中配置了 Node.js 进程堆内存告警的话,过一会就会收到平台的 短信/邮件 提醒。

问题排查过程

收到性能平台的进程内存告警后,我们登录到控制台并且进入应用首页,找到告警对应实例上的问题进程,然后参照工具篇中的 Node.js 性能平台使用指南 - 内存泄漏 中的方法抓取堆快照,并且点击 分析 按钮查看 AliNode 定制后的分解结果展示:

这里默认的报表页面顶部的信息含义已经提到过了,这里不再重复,我们重点来看下这里的可疑点信息:提示有 18 个对象占据了 96.38% 的堆空间,显然这里就是我们需要进一步查看的点。我们可以点击 对象名称 来看到这18 个 system/Context 对象的详细内容:

这里进入的是分别以这 18 个 system/Context  为根节点起始的支配树视图,因此展开后可以看到各个对象的实际内存占用情况,上图中显然问题集中在第一个对象上,我们继续展开查看:

很显然,这里真正吃掉堆空间的是 451 个 SomeClient 实例,面对这样的问题我们需要从两个方面来判断这是否真的是内存异常的问题:

  • 当前的 Node.js 应用在正常的逻辑下,是否单个进程需要 451 个 SomeClient 实例
  • 如果确实需要这么多 SomeClient 实例,那么每个实例占据 1.98MB 的空间是否合理

对于第一个判断,在对应的实际生产面临的问题中,经过代码逻辑的重新确认,我们的应用确实需要这么多的 Client 实例,显然此时排查重点集中在每个实例的 1.98MB 的空间占用是否合理上,假如进一步判断还是合理的,这意味着 Node.js 默认单进程 1.4G 的堆上限在这个场景下是不适用的,需要我们来通过启动 Flag 调大堆上限。

正是基于以上的判断需求,我们继续点开这些 SomeClient 实例进行查看:

这里可以很清晰的看到,这个 SomeClient 本身只有 1.97MB 的大小,但是下面的 options 属性对应的 Object@428973 对象一个就占掉了 1.98M,进一步展开这个可疑的 Object@428973 对象可以看到,其 ctx 属性对应的 Object@428919 对象正是 SomeClient 实例占据掉如此大的对空间的根本原因所在!

我们可以点击其它的 SomeClient 实例,可以看到每一个实例均是如此,此时我们需要结合代码,判断这里的 options.ctx 属性挂载到 SomeClient 实例上是否也是合理的,点击此问题 Object 的地址

进入到这个 Object 的关系图中:

Search 展示的视图不同于 Dom 结果图,它实际上展示的是从堆快中解析出来的原始对象关系图,所以边信息是一定会存在的,靠边名称和对象名称,我们比较容易判断对象在代码中的位置。

但是在这个例子中,仅仅依靠以 Object@428973 为起始点的内存原始关系图,看不到很明确的代码位置,毕竟不管是 Object.ctx 还是 Object.key 都是相当常见的 JavaScript 代码关系,因此我们继续点击 Retainer 视图:

得到如下信息:

这里的 Retainer 信息和 Chrome Devtools 中的 Retainer 含义是一样的,它代表了节点在堆内存中的原始父引用关系,正如本文的内存问题案例中,仅靠可疑点本身以及其展开无法可靠地定位到问题代码的情况下,那么展开此对象的 Retainer 视图,可以看到它的父节点链路可以比较方便的定位到问题代码。

这里我们显然可以通过在 Retainer 视图下的问题对象父引用链路,很方便地找到代码中创建此对象的代码:

function getClient(options) {if (!clients[options.key]) {clients[options.key] = new SomeClient(Object.assign({}, DEFAULT_OPTIONS, options));}return clients[options.key];
}

结合看 SomeClient 的使用,看到用于初始化的 options 参数中实际上只是用到了其 key 属性,其余的属于冗余的配置信息,无需传入。

代码修复与确认

知道了原因后修改起来就比较简单了,单独生成一个 SomeClient 使用的 options 参数,并且仅将需要的数据从传入的 options 参数上取过来以保证没有冗余信息即可:

function getClient(options) {const someClientOptions = Object.assign({ key: options.key }, DEFAULT_OPTIONS);if (!clients[options.key]) {clients[options.key] = new SomeClient(someClientOptions);}return clients[options.key];
}

重新发布后运行,可以到堆内存下降至只有几十兆,至此 Node.js 应用的内存异常的问题完美解决。

结尾

本节中也比较全面地给大家展示了如何使用 Node.js 性能平台 来排查定位线上应用内存泄漏问题,其实严格来说本次问题并不是真正意义上的内存泄漏,像这种配置传递时开发者图省事直接全量 Assign 的场景我们在写代码时或多或少时都会遇到,这个问题带给我们的启示还是:当我们去编写一个公共组件模块时,永远不要去相信使用者的传入参数,任何时候都应当只保留我们需要使用到的参数继续往下传递,这样可以避免掉很多问题。

原文链接
本文为云栖社区原创内容,未经允许不得转载。

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

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

相关文章

一分钟在Linux 环境 搭建 SFTP服务器

文章目录一、实战1. 创建sftp组:2. 创建一个用户sftpuser:3. 设置sftpuser用户的密码,会要求你输入两次密码确认:4. 创建一个sftp的上传目录:5. 修改用户sftpuser所在的目录:6. 配置sshd_config&#xff1a…

微服务架构何去何从?

戳蓝字“CSDN云计算”关注我们哦!作者 | 孙玄本文经授权转载自架构之美前言微服务架构模式经过5年多的发展,在各行各业如火如荼地应用和实践。如何在企业中优雅地设计微服务架构?是企业面对的一个重要问题。本文将讲述微服务架构1.0设计与实践…

天马行空脚踏实地,阿里巴巴有群百里挑一的天才应届生

阿里巴巴有一群天马行空脚踏实地的阿里星。 天下网商记者 王安忆 阿里巴巴的春季校招已经启动。在阿里的技术大咖储备团队中,有三分之一是来自高校招聘,这当中,有一项名为阿里星的神秘计划。这是校招中专门针对高校顶尖大学生的一个培养计划&…

linux Nas挂载

文章目录一、NAS服务端1. 首先查看服务器上是否有nfs和rpc相关软件2. 设置共享3. 使配置生效:4. 查看所有可挂载5. rpcbind启和动nfs6.配置防火墙7. 查看防火墙的状态8. 启动防火墙9. 开放端口10. 重新启动防火墙11. 查看已开放的端口二、客户端(应用服务器)2.1.查看空间分布2.…

GitHub 标星 14000+,阿里开源的 SEATA 如何应用到极致?

戳蓝字“CSDN云计算”关注我们哦!作者简介:袁鸣凯,家乐福技术总监, 高知特有限技术公司中国区架构师,HP上海研发技术专家,夸客金融首席架构师,现任家乐福中国区技术总监。多年互联网、企业级SOA…

阿里小二的日常工作要被TA们“接管”了!

昨天有人偷偷告诉我说 阿里巴巴其实是一家科技公司! 我想了整整一夜 究竟是谁走漏了风声 那么重点来了,阿里到底是如何在内部的办公、生活中,玩转“黑科技”的呢? AI取名:给你专属的“武侠”花名 花名是阿里巴巴独…

对话Ruby创始人松本行弘、阿里高级技术专家朴灵!

4月25日,云栖社区联合阿里云国际站,特别邀请了Ruby创始人Matz(松本行弘)、阿里云高级技术专家朴灵,来为开发者们分享干货。 在本次活动上可以了解到Ruby语言最新的动态,Ruby和新语言golang在性能方面的差别…

Serverless 终结 Kubernetes?

戳蓝字“CSDN云计算”关注我们哦!Kubernetes 是基础架构管理的终极解决方案吗?还是说我们更加需要 Serverless?究竟哪种技术能够解救运维团队?不要忘记,NoOps 的世界即将到来。作者 | Daniele Fontani译者 | 弯月封图 …

代号“凤凰”,阿里新零售秘密武器,今年要打入100个城市

2019年,淘鲜达要在100个城市完成1000个门店的新零售改造。 天下网商记者 张文政 过去一年,初平三分之二的时间在出差,足迹遍及山东、河南、江西、湖南等地。 初平是天猫超市事业群战略合作部总经理,负责天猫超市旗下“淘鲜达”…

VS Code 批量操作

文章目录1. 关键词批量选中2. 末尾批量编辑3. 垂直批量编辑4. 垂直批量选中5. 批量选中整行6. 批量复制整行1. 关键词批量选中 Ctrld 选中相同的词句,批量编辑 2. 末尾批量编辑 shirtalti移动到最后,批量编辑 3. 垂直批量编辑 Ctrlalt↓垂直批量编…

Node.js 应用故障排查手册 —— 利用 CPU 分析调优吞吐量

楔子 在我们想要新上线一个 Node.js 应用之前,尤其是技术栈切换的第一个 Node.js 应用,由于担心其在线上的吞吐量表现,肯定会想要进行性能压测,以便对其在当前的集群规模下能抗住多少流量有一个预估。本案例实际上正是在这样的一…

python idea控制台中文乱码_解决IntelliJ IDEA 控制台输出中文乱码问题(史上最简单)...

解决IntelliJ IDEA 控制台输出中文乱码问题(史上最简单)首先,找到 IntelliJ IDEA 的安装目录,进入bin目录下,定位到idea.vmoptions文件,如下图所示:双击打开idea.vmoptions文件,如下图所示:然后…

VS Code 下载

官网地址: https://code.visualstudio.com/

通过DataWorks数据集成归档日志服务数据至MaxCompute进行离线分析

但是会遇到大家在分区上或者DataWorks调度参数配置问题,具体拿到真实的case模拟如下: 创建数据源: 步骤1 进入数据集成,点击作业数据源,进入Tab页面。 步骤2 点击右上角 新增数据源,选择消息队列…

这是我见过最卡通的 Python 算法了,通俗易懂

戳蓝字“CSDN云计算”关注我们哦!普通程序员,不学算法,也可以成为大神吗?对不起,这个,绝对不可以。可是算法好难啊~~看两页书就想睡觉……所以就不学了吗?就一直当普通程序员吗?如果…

在 IntelliJ IDEA 中部署应用到服务器

在之前的文章《在 Intellij IDEA 中部署 Java 应用到 阿里云 ECS》中讲解了如何将一个本地应用部署到阿里云 ECS 上去,有些读者反馈目前还有一些测试机器是在经典网络,甚至是在本地机房中,咨询是否可以通过 Cloud Toolkit 插件将应用部署到这…

对话行癫:解密阿里云顶层设计和底层逻辑

几十个问题,万字长文,阿里云新任总裁行癫履新后首次出面与钛媒体独家深入讨论了一下阿里云对云计算未来的判断,深度解读未来阿里云生态战略,揭秘阿里技术委员会和阿里中台思想的原生思考。 阿里云智能总裁张建锋 钛媒体注&#x…

Python获取2019-nCoV疫情实时追踪数据

戳蓝字“CSDN云计算”关注我们哦!来源 | 三行科创责编 | Carol出品 | CSDN云计算(ID:CSDNcloud) 2019-nCoV新型冠状病毒引发的肺炎牵动全国人民的心,无数无畏的英雄儿女逆行而上奔赴前线,作者也于2020年2月…

idea菜单栏消失如何调整回来

有一天, 把idea中 view下面的Appearance里面的Main Menu 给反选了 导致idea顶部的菜单栏给消失了, 就像下图这样 可以双击shift键,输入view,选择action里面的View 选择Appearance里面的Main Menu 即可把菜单栏调回来.

这款神秘的移动端OCR引擎,如何做到“所见即所得”?

阿里妹导读:随着深度学习,尤其是CNN和RNN等技术的飞速发展,文字识别技术(OCR)近几年得到了迅速的提升。与此同时,在智能化终端的大趋势下,本地化智能识别凭借更高效快捷的体验以及高度的隐私保护和零流量消耗等优势备受…