记一次node程序性能调优

  1. node性能指标
    衡量一个程序的性能无非关注CPU占用、内存占用、磁盘读写等,为了更方便的采集这些指标,我借用了阿里出品的Node.js性能平台,成功接入后就可以看到这样的界面

Node.js性能监控
需要关注的是右下角【抓取性能数据】功能:

【堆快照】通过抓取堆快照可以查看到当前内存都被谁占用了,查内存泄露的利器。
【CPU profile】抓取一段时间内cpu的占用并生成火焰图,性能调优的关键点。
【GC Trace】抓取一段时间内的内存垃圾回收记录。
首先通过压测程序将被压机器的cpu打满,然后点击【CPU profile】,抓取成功后可以在右侧菜单栏【文件】中找到记录文件。

文件列表
点击【转储】-【分析】即可看到对应的火焰图

CPU火焰图
可以看到GC占比达到了惊人的58.3%,此时接口的并发能力只有6000/s,那此时的优化已经有了方向,到底是什么导致了GC如此频繁。

  1. V8的内存分布和垃圾回收
    由于JavaScript是单进程语言,当V8进行垃圾回收时势必会阻塞当前进程,为了减缓对程序运行的影响,V8将内存分为了新生代和老生代,两种类型的内存采用了不同的回收策略。对于新生代内存,垃圾回收通过Scavenge算法进行,Scavenge的优点是速度很快,但缺点是空间的利用率低,只有50%,因为Scavenge将新生代内存一分为二,每部分都叫做Semispace,在同一时间两个Semispace只有一个处于使用状态,处于使用状态的Semispace称为From,未使用的Semispace则称为To。

新生代内存回收流程
老生代内存相对较大,如果仍采用Scavenge算法未免太过奢侈,老生代共有两种回收算法:

标记清除(Mark Sweep)

算法会将需要清除的部分直接释放,但是这样会导致内存碎片化严重。

标记合并(Mark Compact)

为了解决内存碎片化的问题,V8引入了合并算法,算法会将需要保留的内存集中归并到一端,但合并算法效率很低,所以V8引擎仅在剩余空间不足以安置新晋对象时才会触发。

新生代晋升必须满足以下两个条件之一:

对象已经经历过一次新生代内存回收,这次依旧存活。
To空间已经使用了超过25%,则将From中的对象直接复制到老生代。
了解了GC的基本逻辑后,回到最初的问题,我们的CPU到底被哪些GC消耗了?通过性能平台,拿到当时的GC Trach如下图:

GC Trach
可以看到,这段时间内GC次数734次,其中新生代算法Scavenge执行了728次,老生代算法Mark Sweep执行了6次,看来问题就出在新生代内存里了。新生代中都是一些临时对象,首先可以想到通过增加新生代内存大小来解决,但是新生代大小增加后不仅会导致内存利用率降低,还会导致单次GC的时间变长,所以先不着急更改内存大小,先看下自己的代码是否有可以优化的部分,遇到问题首先怀疑自己是个很好的习惯。

  1. 堆内存
    与此同时,我抓取了当时的堆快照,如下图:

堆快照
我注意到上方红色标记的部分,这里是因为代码使用了kafka作为通信组件,消息不停地写入kafka,在高压情况下,就导致了非常频繁的创建和回收消息对象,而且注意到这里的内存占用为78.62MB,已经超过了默认的新生代半空间(默认为16MB),而且这些对象生命周期特别短,不会被转移到老生代中,所以就造成了新生代内存频繁GC。由于业务原因,这里无法变更,所以最终我选择了通过修改新生代大小来解决问题。在启动时添加参数

node --max_semi_space_size=64 app.js
此时将新生代半空间内存调整为了64MB,再次打压观测结果如下:

新生代半空间修改为64MB
GC时间占比几乎不可见,仅为4次,接口并发能力从6000/s提升到了10000/s,至此问题解决。


Feeling.. 发布于  2023-9-1 14:16 

ChatGPT用Node.js做反向代理完整配置方法

ChatGPT最近非常火爆,由于某些原因,国内服务器无法直接调用api进行开发,需要用到国外服务器进行反向代理,一般都是用宝塔nginx进行反代,这样操作,固然没有什么问题,但小编不想使用服务器,想用阿里、腾讯的云函数捣鼓一下,稳定省事,所以这里用Node.js来反代是最好的选择了。

这是我用腾讯云函数反代后,做的ChatGPT助手:www.250ai.cn,可以直接使用

好了,话不多说,这里是一个使用 Node.js 做 OpenAI 反代的完整示例代码:

const express = require('express');
const { createProxyMiddleware } = require('http-proxy-middleware');

const app = express();

// OpenAI API Key
const OPENAI_API_KEY = 'YOUR_OPENAI_API_KEY';

// OpenAI API URL
const OPENAI_API_URL = 'https://api.openai.com/v1/';

// Target URL for proxy
const TARGET_URL = OPENAI_API_URL;

// Options for proxy middleware
const proxyOptions = {
target: TARGET_URL,
changeOrigin: true,
headers: {
Authorization: Bearer ${OPENAI_API_KEY},
'Content-Type': 'application/json',
},
onProxyReq: (proxyReq, req, res) => {
if (req.method === 'POST' && req.body) {
const bodyData = JSON.stringify(req.body);
proxyReq.setHeader('Content-Type', 'application/json');
proxyReq.setHeader('Content-Length', Buffer.byteLength(bodyData));
proxyReq.write(bodyData);
}
},
};

// Add the proxy middleware
app.use(createProxyMiddleware(proxyOptions));

// Start the server
app.listen(3000, () => {
console.log('Server listening on port 3000');
});
在这里,我们使用了 http-proxy-middleware 库来创建反代。我们设置了 OpenAI API 的 URL 和 API Key,然后将它们传递给 http-proxy-middleware 的 createProxyMiddleware 函数。我们使用了 onProxyReq 选项来确保请求的 Content-Type 是正确的。最后,我们启动了服务器并监听端口 3000。

使用该代码,您可以通过访问 http://localhost:3000 来访问 OpenAI API。例如,如果您想使用 OpenAI 的 davinci 模型进行文本生成,您可以向 http://localhost:3000/completions 发送 POST 请求,并在请求体中包含您的文本和模型名称。反代服务器将转发该请求到 OpenAI API,OpenAI API 将生成文本并将其返回给您。

需要在package.json中添加 http-proxy-middleware 和 express 作为依赖项。在 dependencies 部分中添加以下代码:

{
"dependencies": {
"http-proxy-middleware": "^2.0.1",
"express": "^4.17.1"
}
}
如果您想在生产环境中使用这个代码,请将这个代码添加到您的 dependencies 中。如果您只想在开发环境中使用它,则将其添加到 devDependencies 中:

{
"devDependencies": {
"http-proxy-middleware": "^2.0.1",
"express": "^4.17.1"
}
}
在添加完依赖项后,使用 npm install 命令来安装这些依赖项。这将确保您的项目中包含了所需的库。


Feeling.. 发布于  2023-9-1 14:09 

Node.js嵌入式NoSQL数据库Nedb

Nedb是Node.js中使用比较广泛的一个NoSQL数据库,Api接口类似于MongoDB。相较于MongoDB,Nedb接口简单,使用也更方便,生成的本地数据文件可以非常方便的复制和移动。
本文主要介绍Nedb的安装、实例化及数据的插入,是Nedb快速上手的第一步。


Feeling.. 发布于  2023-9-1 14:06