简而言之,我遇到了一个问题,即对我的Node.js服务器发出多个并行GET
请求导致服务器被堵塞""并挂起,从而导致客户端超时(503,服务不可用)。
经过大量的性能分析后,我发现它是一个CPU问题。特定请求(我们称之为GET /foo
)通过HTTP查询来自多个服务的数据,然后进行大量计算,并将结果返回给客户端,如下所示:
GET /foo
/foo
控制器通过HTTP查询来自多个其他服务的数据/foo
控制器对数据进行一堆迭代,为客户端编译一些输出步骤3大约需要2秒钟才能完成。但是,如果我并行向/foo
发送2个请求,则每个客户端将在大约4秒内收到响应。当我在使用更多内核的群集中运行应用程序时,请求运行得更快,但不是我想要的。
好像我有几个选择:
/foo
将CPU阻塞计算异步发送到另一个进程(使用Heroku,这将是另一个dyno),然后我可以使用websocket或其他东西将结果推送到客户端(同样,非常我的情况很复杂),或愿意做类似选项3的事情。
get('/foo', function*(request) {
// I/O, so not blocking the event loop (I think)
let data = yield getData(request)
// make this happen in a different process
let response = yield doSomeHeavyProcessing(data)
return response
})
我上面省略了很多实施细节,但如果有必要知道,我使用Koa和Node.js 6。
理想情况下,doSomeHeavyProcessing
会在某个单独的过程中执行CPU密集型计算,并且当它完成时,仍会将结果发送回"同步"时尚的请求客户。
一直试图围绕儿童流程,网络工作者,纤维等,并一直在做一些基本的"你好世界"用这些来让他们基本上做到以上,但无济于事。如有必要,可以发布更多细节。
答案 0 :(得分:2)
以下是您可以尝试的一些方法:
<强> 1 强>
拆分小块中的阻塞计算,并使用setImmediate
将下一块工作放在事件队列的末尾。因此计算不再阻塞,可以处理其他请求。
<强> 2 强> 微软最近发布了napajs。如其自述文件中所述
随着它的发展,我们发现在CPU绑定任务中补充Node.js很有用,能够在多个V8隔离区中执行JavaScript并在它们之间进行通信。
我还没有尝试过,但看起来很有希望:
var napa = require('napajs');
var zone1 = napa.zone.create('zone1', { workers: 4 });
get('/foo', function*(request) {
let data = yield getData(request)
let response = yield zone1.execute(doSomeHeavyProcessing, [data])
return response
})
3。如果上述情况都不够,并且您需要将负载分散到多台计算机上,那么您可能无法避免使用某种消息队列将工作分配到不同的服务器。在这种情况下,请查看ZeroMQ。它非常易于从节点使用,您可以使用它实现任何类型的分布式消息传递模式。
答案 1 :(得分:1)
为方便起见,您可以将Child process与其他包装一起使用。
worker.js - 此模块将在一个单独的进程中运行,并将执行繁重的工作
const crypto = require('crypto');
function doHeavyWork(data) {
return crypto.pbkdf2Sync(data, 'salt', 100000, 64, 'sha512');
}
process.on('message', (message) => {
const result = doHeavyWork(message.data);
process.send({ id: message.id, result });
});
client.js - 子进程的便利(但原始)包装器
const cp = require('child_process');
let worker;
const resolves = new Map();
module.exports = {
init(moduleName, errorCallback) {
worker = cp.fork(moduleName);
worker.on('error', errorCallback);
worker.on('message', (message) => {
const resolve = resolves.get(message.id);
resolves.delete(message.id);
if (!resolve) {
errorCallback(new Error(`Got response from worker with unknown id: ${message.id}`));
return;
}
resolve(message.result);
});
console.log(`Service PID: ${process.pid}, Worker PID: ${worker.pid}`);
},
doHeavyWorkRemotly(data) {
const id = `${Date.now()}${Math.random()}`;
return new Promise((resolve) => {
worker.send({ id, data });
resolves.set(id, resolve);
});
}
}
我使用fork()
来使用文档中声明的其他通信渠道。
此外,我保留所有提交给工作进程请求(const resolves = new Map();
)的记录,并仅在工作进程返回特定请求的响应时解析Promises(resolve(message.result);
)(const resolve = resolves.get(message.id);
)
run.js - 一个启动模块,它利用co
来“执行”生成器。
const co = require('co');
const client = require('./client');
function errorCallback(error) {
console.log('Got an unexpected error!');
console.log(error);
}
client.init('./worker.js', errorCallback);
function* run() {
while(true) {
yield client.doHeavyWorkRemotly('mydata');
}
}
co(run);
要测试它只需运行node run.js
,它就会打印
服务PID:XXXX,工人PID:XXXX
然后看看CPU利用率,工作进程可能需要大约100%的CPU,而服务将非常闲置。