Expressjs主循环在紧张操作期间被阻塞



我有一个expressjs服务器在运行,端点init在其中执行一些平均完成时间为10秒的密集操作。在这10秒内;"卡住";,使得无法向expressjs服务器发送请求。我已经在谷歌上搜索了一段时间,但没有发现任何能使expressjs同时处理请求的东西。如果这不可能的话,那就太傻了。对于任何提示或帮助,我都非常感谢。

示例代码:

routes.js

app.route('/v1/cv/random').get(init);

features/init.js

module.exports = async function init(req, res) {
try {
// perform some time consuming operation here
res.status(201).send(someVar);
} catch (err) {
res.status(500).send(`failed to init`).end();
}
};

可以以同步方式实现运行时间长的算法,例如河内塔:

function move(from, to, via, n) {
if (n > 1)
move(from, via, to, n - 1);
to.unshift(from.shift());
if (n > 1)
move(via, to, from, n - 1);
}
app.get("/tower", function(req, res) {
var a = [];
for (var i = 0; i < Number(req.query.n); i++) a.push(i);
var b = [];
var c = [];
move(a, b, c, a.length);
res.end("Done");
});

调用具有足够大的<N>GET /tower?n=<N>确实会阻塞express的主循环。

这种阻塞可以通过在算法中引入异步性来避免,例如使用setTimeout(nextAlgorithmicStep)命令。这将nextAlgorithmicStep函数放入队列中,但同一队列也有处理并发请求的函数的空间:

function tick(from, to, via, n) {
return new Promise(function(resolve, reject) {
setTimeout(function() {
move(from, to, via, n, resolve);
});
});
}
async function move(from, to, via, n, resolve) {
if (n > 1)
await tick(from, via, to, n - 1);
to.unshift(from.shift());
if (n > 1)
await tick(via, to, from, n - 1);
resolve();
}
app.get("/tower", async function(req, res) {
var a = [];
for (var i = 0; i < Number(req.query.n); i++) a.push(i);
var b = [];
var c = [];
await tick(a, b, c, a.length);
res.end("Done");
});

这样,您就可以(永远)等待请求GET /tower?n=64的返回,但您至少仍然可以对同一服务器发出并发请求。(简单地使用Promiseprocess.nextTick而不是setTimeout"不够异步"以允许在两者之间处理并发请求。)

然而,GET /tower?n=10的执行完成了";立即";在第一个版本中,现在需要更长的时间。最好不要在所有的n递归级别上使用setTimeout,而是只在每十级左右使用一次。你必须在RSA算法中找到类似的异步优点。


这就是使用单线程Node.js程序可以做的。但是还有一种替代方案,它使用多个Node.js进程。

app.get("/tower", function(req, res) {
spawn("node", ["tower.js", req.query.n]).stdout.pipe(res);
});

其中tower.js是附加的Javascript程序:

function move(from, to, via, n) {
if (n > 1)
move(from, via, to, n - 1);
to.unshift(from.shift());
if (n > 1)
move(via, to, from, n - 1);
}
var a = [];
for (var i = 0; i < Number(process.argv[2]); i++) a.push(i);
var b = [];
var c = [];
move(a, b, c, a.length);
process.stdout.write("Done");

在@Heiko Theißen更新他的答案前不久,我找到了答案。这是(我认为)类似的方法。

我找到了一种使用child_process的方法,并通过使用来执行某个文件所拥有的一切

const {fork} = require('child_process');
...
module.exports = async function init(req, res) {
try {
const childProcess = fork('./path/to/the/script.js');
childProcess.send({'body': req.body});
childProcess.on('message', (message) => {
res.status(201).json({someVar: message}).end();
});
} catch (err) {
res.status(500).send(`failed to init`).end();
}
};

script.js看起来像

process.on('message', async (message) => {
// perform a time consuming operation here
process.send(someVar);
process.exit();
});