我创建了一个简单的项目,以了解Node.js的内部结构(事件循环,libuv,线程),并最终遇到了我无法理解的意外行为。
这是代码。基本上,服务器侦听请求并进行计算需要花费一些时间。 (大约750ms)
process.env.UV_THREADPOOL_SIZE = 1;
const express = require('express');
const crypto = require('crypto');
const cluster = require('cluster');
const serverStartTime = Date.now();
const consoleColors = ["\x1b[31m", "\x1b[31m", "\x1b[32m", "\x1b[33m", "\x1b[34m"];
if (cluster.isMaster) {
cluster.fork();
} else {
const app = express();
const color = consoleColors[cluster.worker.id];
let requestCount = 0;
let firstRequestTime = null;
app.get('/', (req, res) => {
const requestStartTime = Date.now();
if (firstRequestTime === null) {
firstRequestTime = requestStartTime;
}
const requestNum = requestCount + 1;
console.log(color, `[Request ${requestNum}][PID ${process.pid}][Started: ${requestStartTime - serverStartTime}]`);
requestCount += 1;
crypto.pbkdf2('a', 'b', 100000, 512, 'sha512', () => {
const requestEndTime = Date.now();
const sinceFirstRequest = requestEndTime - firstRequestTime;
const tookTime = requestEndTime - requestStartTime;
console.log(color, `[Request ${requestNum}][PID ${process.pid}][Ended: ${requestEndTime - serverStartTime}][Took: ${tookTime}ms][SinceFirstRequest: ${sinceFirstRequest}ms]`);
res.send('Hi there');
});
});
app.listen(3000);
}
运行命令(同时发出5个请求)后:ab -c 5 -n 5 localhost:3000/
我得到以下日志:
[Request 1][PID 41495][Started: 1395]
[Request 1][PID 41495][Ended: 2072][Took: 677ms][SinceFirstRequest: 677ms]
[Request 2][PID 41495][Started: 2077]
[Request 3][PID 41495][Started: 2082]
[Request 4][PID 41495][Started: 2084]
[Request 5][PID 41495][Started: 2086]
[Request 2][PID 41495][Ended: 2797][Took: 720ms][SinceFirstRequest: 1402ms]
[Request 3][PID 41495][Ended: 3465][Took: 1383ms][SinceFirstRequest: 2070ms]
[Request 4][PID 41495][Ended: 4132][Took: 2048ms][SinceFirstRequest: 2737ms]
[Request 5][PID 41495][Ended: 4801][Took: 2715ms][SinceFirstRequest: 3406ms]
有人可以解释为什么第二个请求总是等待第一个请求完成而随后的所有请求几乎同时开始吗?无论我同时发出多少个请求,第一个总是结束,然后所有其他请求开始。
我读到网络使用操作系统中的资源,某些阻止操作(例如pbkdf2
)使用线程池。我期望的行为是:记录所有请求的开始,然后一个接一个地记录每个请求的结束。
我的第一个猜想是:事件循环的各个阶段及其执行顺序。
所以我再放一个cluster.fork()
,(现在有两个事件循环)。如果此行为是由于阶段引起的,则应执行每个事件循环的第一个请求。但事实并非如此。最初只执行第一个cluster.fork()
的一个请求,然后其他所有事情都发生。
我不知道我的思路是否正确,请帮助我理解。