node性能指标(一)-内存分析与管理

南山 -
node性能指标(一)-内存分析与管理
关于node的前言

JavaScript运行在浏览器的沙盒中,他始终会受限于浏览器的中间层提供的能力。Node技术的出现给前端工作打开了新的局面。毫无疑问,现代的前端工程化已经离不开Node的应用了,而Node本身的设计是用作服务端语言,越来越多的前端团队,不再只将node局限于工程化应用,也开始去负责BFF层,比如SSR架构、数据适配、数据拼接裁剪、后端应用等。当作为服务端应用的时候,服务的【稳定】与【安全】是最重要的指标,提起性能指标

“首屏加载时长”、“可交互时长”等,这些每一个前端er都了解的h5性能指标。node用作服务端,有哪些性能指标是值得我们注意的呢?

影响node服务的因素

node用作服务端,相比较前端工程而言,我们需要关注的不仅仅是node自身的特性之外,还有依赖的服务器资源;如果依赖的服务器性能不好,node服务的性能必然也受到影响;

因此,我们去思考node服务的性能问题时候,需要从两个大的方面去思考:一是node运行时会出现的问题,二是服务器资源的性能

CPU内存磁盘I/O网络I/O

本文主要分析内存指标,以及内存泄漏的隐患排查

内存的限制

node的储存分为堆和栈,栈中存储基本数据类型,堆中存放引用类型:对象与变量; 对于node存储来说,堆内存是整个内存的主要占用。我们所关注的内存指标就是指堆内存的占用指标

一般的后端语言几乎没有内存限制的问题。但是node是基于V8引擎,在对象分配上遵从V8的方式,node通过Javascript使用内存是有限制的,在64位系统最大内存为1.4G左右,32位系统为0.7G左右; 之所以有内存的限制,一方面V8的设计之初是用于浏览器使用,这个限制的值对于一般网页来说是足够的。更深层的原因是V8的垃圾回收机制;

当我们声明一个变量并赋值的时候,就会存放在V8申请的堆内存中,当堆内存不够会继续申请内存,知道达到内存的限制。如果超出限制,那么就会出现内存泄漏的现象,出现卡顿等现象;这里的内存指标是最方便去量化的,通过node提供process.memoryUsage()即可查看与了解

rss:进程占用的内存总量。heapTotal:堆内存申请的总量。

<!---->

heapUsed:实际堆内存使用量。

\

垃圾回收

V8垃圾回收的基础是先将内存进行分代; 在V8中按照对象存活的时间将内存的进行分代。 存活时间短(可立即回收的变量)的放入新生代,常驻内存(全局变量、无法立即去回收的变量)放入老生代;

v8内存空间 = 新生代占用内存空间 + 老生代占用内存空间;

node也提供了扩宽内存的方法,在启动node的时候,可以通过传递--max-old-space-size 和 --max-old-space-size来调整内存的大小,这两个对应扩充的值就是上面提到的老生代内存与新生代的内存值,这个调整一旦启动,就不可更改,除非再次启动。在V8内存受限制的时候,可以按照这个值进行放宽

node --max-old-space-size=1800 server.js // 1800Mb

即使有调整,我们也不能全部都是用V8申请的内存,这源于v8的回收策略

目前的Node使用的是scavenge算法,它基于复制的方式实现垃圾回收,这在一定的程度是有对内存资源的浪费存在; 代码开发过程中,也可以按需使用global.gc()去主动触发垃圾回收,如果主动触发之后,查询heapUsed并没有下降,可以考虑是内存泄漏的存在了

内存泄漏的分析

通过上述,如果堆内存达到了堆内存的指标,无法再为新的变量/对象进行申请新内存的时候就是出现内存泄漏的现象了。老生代的常驻内存是不会被V8回收的,即使是手动出发。代码中常见的几项不会被垃圾立即回收,积累过多会造成内存泄漏隐患:

全局变量引用闭包作用域内变量

<!---->

模块的缓存

一个内存泄漏的简单案例, 仅供学习内存分析: 每次请求的时候会通过request传过来的的信息去数据库取数据,对读取的数据做了一层缓存,简单的示例代码如下:

const { json } = require('express');
const express = require('express');
const { v4: uuid } = require('uuid');

const app = express();


function getDataBase() {
    const cache = {}
    return function(key) {
        if (cache[key]) return cache[key]
        let data = new Array(10000).fill('cache')
        cache[key] = data
        return data
    }
    
}

const dataBase = getDataBase()
app.get('/memoryUsage', (req, res, next) => {
    let uid = uuid()
    let data  = dataBase(uid)
    res.json({
        msg: '内存数据',
        data: JSON.stringify({
            data
        })
    })
})
const port = 3100
app.listen(port, () => {
    console.log(`Example app listening at http://localhost:${port}`)
})

通过wrk压测工具压测,你会发现请求数量一多,请求就挂掉了。

这个时候就到了分析node内存的时候了,以下是 Chrome-Memory分析

node.js调试工具: Chrome调试

启动Chrome开发者工具: 启动node服务的时候传递--inspect

node --inspect server.js

打开chrome, 地址栏输入chrome://inspect, 界面如下图,点击Target 种的node服务,在弹出的弹框中选择Allocation sampling, 点击start按钮即开始记录

如果是单个的请求,看不出什么问题。所有的问题都是在请求量增长的情况下,使用压测工具向node服务发出并发请求,我这里使用的是wrk工具,先模拟高并发请求

wrk -t12 -c1000 -d30s http://localhost:3100/memoryUsage

发送之前的内存情况:

发送之后10S左右, 内存就到了700多,选择了stop

结束录制后,会有录制报告如下图,可以看出99%的内存占用全在缓存的代码中,由此可以分析出来内存隐患的代码

更多的node调试,可以参阅:https://www.ruanyifeng.com/bl...

内存监控的实际应用

一个完善node服务的性能指标不仅仅只有内存这一项,还包括cpu利用率、相应时间、状态码监控等。这些线上的服务,社区内也有很多好用的工具,推荐一个非常简单好用的监控工具express-status-monitor, 这个工具引入到工程后,通过默认路由(/status)或者指定的路由既可以访问各项指标的实时监控

const statusMonitor = require('express-status-monitor')({
  title: 'XXX服务实时监控',
  spans: [
    {
      interval: 1,           // Every 15 seconds
      retention: 100          // 在内存中保留60个数据点
    }
  ]
});

app.use(statusMonitor)

特别申明:本文内容来源网络,版权归原作者所有,如有侵权请立即与我们联系(cy198701067573@163.com),我们将及时处理。

Tags 标签

node.js前端

扩展阅读

加个好友,技术交流

1628738909466805.jpg