Nodejs 请求接口在高并发下耗时很大,而单个请求非常快

发布于 1周前 作者 vueper 来自 nodejs/Nestjs

Nodejs 请求接口在高并发下耗时很大,而单个请求非常快

情况: request.js 库请求接口, express.js 做 server ,实现了 curl http://localhost:8080/proxy-api 本地一个地址,在 router 里用 request.js 请求接口,统计了一下请求耗时,单个请求耗时很低,如下:

get http://ip:9190/user/getUserInfo 13 ms

然后分别使用 webbench 和 ab 做并发测试,并发 500 ,发现接口有非常大的耗时。

# 测试命令
ab -n 1000 -c 200 -r http://localhost:8080/proxy-api
webbench -t 10 -c 500 http://localhost:8080/proxy-api
# 截取部分响应耗时:
get http://ip:9190/user/getUserInfo 2019 ms
cost time:  2020
get http://ip:9190/user/getUserInfo 2062 ms
cost time:  2062
get http://ip:9190/user/getUserInfo 2064 ms
cost time:  2065
get http://ip:9190/user/getUserInfo 2063 ms
cost time:  2063
get http://ip:9190/user/getUserInfo 2062 ms
cost time:  2063
get http://ip:9190/user/getUserInfo 2063 ms
cost time:  2063
get http://ip:9190/user/getUserInfo 2061 ms
cost time:  2062
get http://ip:9190/user/getUserInfo 2063 ms
cost time:  2064
get http://ip:9190/user/getUserInfo 2063 ms
...
...
get http://ip:9190/user/getUserInfo 1362 ms
cost time:  1362
get http://ip:9190/user/getUserInfo 1361 ms
cost time:  1362
get http://ip:9190/user/getUserInfo 1362 ms
cost time:  1362
get http://ip:9190/user/getUserInfo 1362 ms
cost time:  1362
get http://ip:9190/user/getUserInfo 1362 ms
cost time:  1362
get http://ip:9190/user/getUserInfo 1363 ms
cost time:  1363
get http://ip:9190/user/getUserInfo 1362 ms
cost time:  1362
...
...
get http://ip:9190/user/getUserInfo 1006 ms
cost time:  1006
get http://ip:9190/user/getUserInfo 627 ms
cost time:  628
get http://ip:9190/user/getUserInfo 629 ms
cost time:  629
get http://ip:9190/user/getUserInfo 628 ms
cost time:  629
get http://ip:9190/user/getUserInfo 1403 ms
cost time:  1403
get http://ip:9190/user/getUserInfo 1402 ms

请问哪位朋友有没有解决这类问题的经验?


47 回复

关注下,公司一个系统准备换成 node 跑,有结果后希望分享下:-)


好的,目前就这个并发问题了。

是不是只开了一个进程?

所以 8080 上是个无限牛逼的后端接口,然后 9190 上是你的问题的 node ,里面的逻辑是用 request 捅 8080 ,然后并发有问题?

没记错的话, request 库是带连接池的,默认并发只开了 5 ,所以你用并发 500 去压耗时很长是正常的

没有做任何进程操作,默认的简单程序

你理解反了, 8080 上是 node sever, 9190 是后端接口,并发 node 上一个地址,该地址又用了 request 去请求接口,统计了一下 request 开始到完成的耗时。 然后你说的 request 连接池是 ‘pool’: { maxSockets: 5000 } ?改为 5000 也没有明显提升。

你看下系统 cpu 内存 和 load ?

你直接压后端的接口的耗时是多少?

看看是否是内存不足 发生内存换页

直接压后端耗时也很小的,所以是前端请求问题。
ab 压前端转发:
Requests per second: 171.05 [#/sec] (mean)
Time per request: 1169.261 [ms] (mean)
Time per request: 5.846 [ms] (mean, across all concurrent requests)
Transfer rate: 1981.31 [Kbytes/sec] received

ab 直接压后端接口:
Requests per second: 858.12 [#/sec] (mean)
Time per request: 233.068 [ms] (mean)
Time per request: 1.165 [ms] (mean, across all concurrent requests)
Transfer rate: 190.23 [Kbytes/sec] received


我都是在本地测试,只有后端接口是局域网内一个服务器, mac pro 13 中配,应该不是你们所说的问题

发下转发的代码

你这样测试很可能测试的是 request 请求的后端的接口的返回吧, 很可能是后面的接口不行了

把每个步骤的耗时都弄清楚一些。注意 console.log 也可能耗时。
网上宣传的高并发是高端服务器跑出来的,普通机器不要期望太高。

先用 nginx 把请求转发到后端接口, 压一下看问题是不是 node 层的。
确定是 node 层的问题后,换一个请求库例如 superagent 跑一下,看看是不是所有库都有这个问题。
我们之前不是局域网时 ab 也是这样,但最后发现是本地带宽跑满了,解决后就没问题了

用 visualstudio + node 扩展看下, 里面带有性能分析工具的

superagent 跑起来还是有延迟的话,再换原生 http 模块试试,当然你有权限的话可以直接看后台接口的请求耗时对不对。
我觉得把 node 层请求用 nginx 转给后台可能有些效果

是不是你的 node 版本问题,我用 node.js 7.0 测你给出来的代码,每个请求的耗时相差不大(第一个与最后一个相差都不超过 80ms )

做排队啊。
关注下设备的性能,是什么原因找到的,网络还是 cpu 还是内存

我从 v6.9.1 升级到了 v7.0.0 ,耗时减少一半,但依然有超过 1000ms 的。 api 地址干脆使用了 http://localhost ,是本机 nginx 默认页面,耗时从 150ms ~ 500ms 左右都有。

不懂 node 。
考虑一下系统和网络性能,服务端 tcp 监听端口有接受队列, 频繁发起请求可能会有点耗时。
可以试一下在单个连接上测试

简单代码已附言

你试下用以下的代码测下,如果结果差别不大,可能是后端的问题了:

<br>const http = require("http");<br><br>const server = http.createServer((req, res) =&gt; {<br>&nbsp;&nbsp;&nbsp;&nbsp;setTimeout(function () {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;res.writeHead(200);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;res.end();<br>&nbsp;&nbsp;&nbsp;&nbsp;}, Math.random() * 100);<br>});<br><br>server.listen(8888);<br>

=============================
然后你上面贴出来的脚本也改成这样:
<br>const request = require('request').defaults({<br>&nbsp;&nbsp;&nbsp;&nbsp;pool: { maxSockets: 5000 }<br>});<br><br>const c = 500;<br>const api = 'http://localhost:8888';<br><br>const costs = [];<br>function doGet(i) {<br>&nbsp;&nbsp;&nbsp;&nbsp;const start = Date.now();<br>&nbsp;&nbsp;&nbsp;&nbsp;const index = i;<br>&nbsp;&nbsp;&nbsp;&nbsp;request.get(api, () =&gt; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;const end = Date.now() - start;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;costs.push(`${index}: ${end}ms`);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if (costs.length === c) {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;console.log(costs.join("\n"));<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;});<br>}<br><br>for (let i = 0; i &lt; c; i++) {<br>&nbsp;&nbsp;&nbsp;&nbsp;doGet(i);<br>}<br>

用上 pm2 结果会不会不一样?

用利用多进程多核的特性没?
我用的 PM2 测的,服务器是 CPU:Intel® Xeon® E5-2640 0 @ 2.50GHz 共 24 核 MEM:32G

并发数 TPS 响应时间 CPU 成功数 写文件 丢失率 时间
5000 10331.5 0.446 55% 19327244 19323366 0.02% 1 小时
10000 10392.1 0.86 55% 23631728 23626987 0.02% 1 小时
2000 9673.45 0.198 55% 416065314 415983941 0.0195% 11 小时+

没有实用多核多进程

用了你的代码,用时变的比较均匀,截取最长耗时的和最短耗时,如下:

<br># 最短耗时:<br>127: 235ms<br>34: 285ms<br>62: 283ms<br>72: 283ms<br>97: 283ms<br>55: 289ms<br>89: 286ms<br>124: 283ms<br><br># 最长耗时:<br>279: 611ms<br>267: 612ms<br>437: 600ms<br>467: 599ms<br>421: 608ms<br>417: 609ms<br>436: 608ms<br>479: 606ms<br>461: 607ms<br>443: 608ms<br>448: 607ms<br>438: 609ms<br>430: 609ms<br>

用 pm2 简单部署了下,结果还是一样

为啥不把 node 换成 nginx 试试呢?直接 nginx 反向代理试试昂。。。

反向代理不是根本目的, node 做页面渲染的,转发接口这是其中一部分,不是 ningx 干的事儿。

mark 等下电脑回复

这相差还是很大的,或许你可以用 wireshark 抓包,然后统计下时间,看能不能发现问题。

基于 Visual Studio ? osx 系统比较尴尬。。。我安装个虚拟机吧

nonono,问题是你需要确认是 node 的问题还是转发的问题。没问题就可以忽略掉 api 的故障了。。
我在使用 nodejs 的过程中转发发现也很诡异,经常堵得严严实实的。。。

是这样的,发起 1000 个请求,并发为 1 ,每次耗时都很低,但一旦并发数加大,加到 10 , 50 , 100 ,耗时就随之增加,可以认为是并发下的问题。

了解了一下, VS Code 有 mac 版本

vscode 没 profile 功能,记得

而且 windows 上安装 nodejs 后支持这个

服务器 24 核,性能不要太好。想问下,你用的测试软件是什么?

上次 pm2 用法错了,开双核后转发效率有提高了

我司测试大神测的,软件名是 loadrunner

看不懂 尴尬

那就好 理论上 pm2 多开就是提高并发啊

求教楼主最后解决了吗?我这里 4 核服务器 20W 请求,每个请求要压到 5s 以下。。不知道怎么优化

问题解决了:![123]( https://imgchr.com/i/V4sQl4 ‘‘123’’)

在Node.js中,单个请求处理速度快但高并发下耗时增大,通常与事件循环的阻塞、资源竞争(如数据库连接池耗尽)、或者网络I/O的瓶颈有关。以下是一些可能的优化策略和示例代码:

  1. 使用异步I/O: Node.js的强项在于其非阻塞I/O模型。确保所有数据库查询、文件读写等操作都使用异步方法。

    const fs = require('fs').promises;
    
    async function readFile(path) {
        try {
            const data = await fs.readFile(path, 'utf8');
            console.log(data);
        } catch (err) {
            console.error(err);
        }
    }
    
    readFile('example.txt');
    
  2. 连接池管理: 对于数据库操作,使用连接池来管理连接,避免在高并发下连接创建和销毁带来的开销。

    const mysql = require('mysql');
    const pool = mysql.createPool({
        connectionLimit: 10,
        // 其他配置
    });
    
    pool.getConnection((err, connection) => {
        if (err) throw err;
        connection.query('SELECT 1', (error, results, fields) => {
            connection.release();
            if (error) throw error;
            console.log(results);
        });
    });
    
  3. 负载均衡: 如果单个Node.js实例无法处理所有请求,可以考虑使用负载均衡器(如Nginx)将请求分发到多个Node.js实例。

  4. 性能监控: 使用工具(如Node.js的clinicpm2)监控应用的性能,找出瓶颈。

通过上述方法,可以有效提升Node.js应用在高并发下的性能。

回到顶部