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