MST
星途 面试题库

面试题:Node.js中async/await在高并发场景下的性能调优

在Node.js应用中,当使用async/await处理高并发的异步任务(如大量的HTTP请求)时,可能会遇到性能瓶颈。请详细分析造成性能瓶颈的原因,并提出至少两种有效的性能调优策略,包括但不限于对资源的合理分配、使用队列控制并发数量等,并结合代码示例说明。
12.9万 热度难度
前端开发Node.js

知识考点

AI 面试

面试题答案

一键面试

性能瓶颈原因分析

  1. 资源限制
    • CPU资源:Node.js是单线程运行的,虽然事件循环机制使其能高效处理异步任务,但当高并发的异步任务需要大量CPU计算时,单线程会成为瓶颈。例如,在HTTP请求处理中,如果对每个响应进行复杂的加密解密或数据处理,CPU会不堪重负。
    • 内存资源:大量并发的HTTP请求可能会导致内存占用急剧增加。每个请求可能会产生中间数据,如果没有及时释放,可能会导致内存泄漏,最终耗尽系统内存。
  2. I/O阻塞:尽管Node.js的I/O操作是异步的,但如果同时发起过多的I/O操作(如HTTP请求),底层的网络I/O资源可能会被耗尽。例如,网络带宽限制,过多的请求会导致网络拥塞,每个请求的响应时间变长。
  3. 事件循环压力:async/await本质上还是基于事件循环的。大量高并发的异步任务会使事件循环队列中堆积过多的任务,导致任务处理延迟。

性能调优策略

  1. 控制并发数量
    • 使用队列和Promise.allSettled
      • 思路:通过队列控制并发请求的数量,避免同时发起过多请求。当一个请求完成后,从队列中取出下一个请求执行。
      • 代码示例:
const { promisify } = require('util');
const axios = require('axios');
const sleep = promisify(setTimeout);

async function sendRequests(urls, maxConcurrent) {
    let results = [];
    let queue = [...urls];
    let running = 0;
    while (queue.length > 0 || running > 0) {
        while (running < maxConcurrent && queue.length > 0) {
            running++;
            const url = queue.shift();
            axios.get(url).then(response => {
                results.push(response.data);
            }).catch(error => {
                results.push(error);
            }).finally(() => {
                running--;
            });
        }
        await sleep(100);
    }
    return results;
}

const urls = Array.from({ length: 100 }, (_, i) => `https://example.com/api/${i}`);
sendRequests(urls, 10).then(results => {
    console.log(results);
});
  • 使用async - semaphore
    • 思路:async - semaphore是一个用于控制并发的库,它允许设置最大并发数。
    • 安装:npm install async - semaphore
    • 代码示例:
const semaphore = require('async - semaphore');
const axios = require('axios');

const maxConcurrent = 10;
const sem = semaphore(maxConcurrent);

async function sendRequest(url) {
    return sem(() => axios.get(url)).then(response => response.data);
}

const urls = Array.from({ length: 100 }, (_, i) => `https://example.com/api/${i}`);
Promise.all(urls.map(url => sendRequest(url))).then(results => {
    console.log(results);
});
  1. 优化资源使用
    • 连接池
      • 思路:对于HTTP请求,可以使用连接池来复用TCP连接,减少连接建立和销毁的开销。在Node.js中,http模块的Agent对象可以实现连接池功能。
      • 代码示例:
const http = require('http');
const agent = new http.Agent({ keepAlive: true, maxSockets: 10 });

async function sendHttpGetRequest(url) {
    return new Promise((resolve, reject) => {
        const req = http.get(url, { agent }, res => {
            let data = '';
            res.on('data', chunk => {
                data += chunk;
            });
            res.on('end', () => {
                resolve(data);
            });
        });
        req.on('error', err => {
            reject(err);
        });
        req.end();
    });
}

const urls = Array.from({ length: 100 }, (_, i) => `http://example.com/api/${i}`);
Promise.all(urls.map(url => sendHttpGetRequest(url))).then(results => {
    console.log(results);
});
  • 缓存
    • 思路:如果部分HTTP请求的数据是不变的或者变化频率较低,可以使用缓存。例如,使用node - cache库。
    • 安装:npm install node - cache
    • 代码示例:
const NodeCache = require('node - cache');
const axios = require('axios');

const myCache = new NodeCache();

async function sendCachedRequest(url) {
    const cached = myCache.get(url);
    if (cached) {
        return cached;
    }
    const response = await axios.get(url);
    myCache.set(url, response.data);
    return response.data;
}

const urls = Array.from({ length: 100 }, (_, i) => `https://example.com/api/${i}`);
Promise.all(urls.map(url => sendCachedRequest(url))).then(results => {
    console.log(results);
});