Node.js / express:立即响应客户端请求并在 nextTick 中继续任务

Posted

技术标签:

【中文标题】Node.js / express:立即响应客户端请求并在 nextTick 中继续任务【英文标题】:Node.js / express: respond immediately to client request and continue tasks in nextTick 【发布时间】:2014-02-12 20:05:13 【问题描述】:

我想将服务器高消耗 CPU 任务与用户体验分开:

./main.js:

var express = require('express');
var Test = require('./resources/test');
var http = require('http');
var main = express();

main.set('port', process.env.PORT || 3000);
main.set('views', __dirname + '/views');
main.use(express.logger('dev'));
main.use(express.bodyParser());
main.use(main.router);

main.get('/resources/test/async', Test.testAsync);

main.configure('development', function() 
  main.use(express.errorHandler());
);

http.createServer(main).listen(main.get('port'), function()
  console.log('Express server app listening on port ' + main.get('port'));
);

./resources/test.js:

function Test() 
module.exports = Test;

Test.testAsync = function(req, res) 
  res.send(200, "Hello world, this should be sent inmediately");
  process.nextTick(function() 
    console.log("Simulating large task");
    for (var j = 0; j < 1000000000; j++) 
      // Simulate large loop
    
    console.log("phhhew!! Finished!");
  );
;

当请求“localhost:3000/resources/test/async”时,我希望浏览器呈现“Hello world,这应该立即发送”非常快,node.js 继续处理,一段时间后在控制台中出现“完成”消息。

相反,浏览器会一直等待,直到 node.js 完成大型任务然后呈现内容。我试过res.set( 'Connection': 'close' );res.end();,但没有任何效果。我也没有运气用谷歌搜索。

如何立即向客户端发送响应,服务器继续执行任务?

编辑

解决方案中的posted fork方法

【问题讨论】:

你试过用res.end代替res.send 是的,试过res.set( 'Connection': 'close' ); res.send(200, "Hello world, this should be sent inmediately"); res.end(); 【参考方案1】:

一个好的解决方案是使用child_process.fork():它允许您在不同的 Node 实例中执行应用程序的另一个 javascript 文件,从而在不同的事件循环中。当然,您仍然可以通过发送消息两个进程之间进行通信:因此,从您的 UI 进程,您可以向分叉的进程发送消息以要求它执行东西。

例如在ui.js:

var ChildProcess = require('child_process');
var heavyTaskWorker = ChildProcess.fork('./heavyTaskWorker.js');
...
var message = 
    operation: "longOperation1",
    parameters: 
        param1: "value1",
        ...
    
;
heavyTaskWorker.send(message);

heavyTaskWorker.js:

process.on('message', function (message) 
    switch (message.operation) 
    case 'longOperation1':
        longOperation1.apply(null, message.parameters);
        break;
    ...
    
);

在这里测试过,效果很好!

希望有帮助!

【讨论】:

【参考方案2】:

感谢 Peter Lyons 的帮助,最后主要问题是 firefox 缓冲区:响应时间没有刷新它(所以 firefox 一直在等待)。

无论如何,对于高 CPU 执行任务,节点会一直挂起直到完成,因此不会参与新的请求。如果有人需要,可以通过 fork 来实现(使用 child_process,参见http://nodejs.org/api/child_process.html 中的示例)

不得不说,通过分叉来改变上下文可能比将任务分成不同的滴答声需要更长的时间。

./resources/test.js:

var child = require('child_process');
function Test() 
module.exports = Test;

Test.testAsync = function(req, res) 
  res.send(200, "Hello world, this should be sent inmediately");
  var childTask = child.fork('child.js');
  childTask.send( hello: 'world' );
;

./resources/child.js:

process.on('message', function(m) 
  console.log('CHILD got message:', m);
);

【讨论】:

【参考方案3】:

尝试等待而不是占用 CPU:

res.send("Hello world, this should be sent inmediately");
console.log("Response sent.");
setTimeout(function() 
  console.log("After-response code running!");
, 3000);

node.js 是单线程的。如果你用繁忙的循环锁定 CPU,整个事情就会停止,直到完成。

【讨论】:

谢谢,但我真正需要的是将复杂的 CPU 任务与用户体验分开。因此,当服务器继续处理请求时,用户会很快得到响应。 “处理”很好。正常处理是一点点 CPU 时间,然后是大量 I/O 时间,节点可以正常工作。 Node 不适用于加密、图像处理等全 CPU 工作负载。您需要将 CPU 密集型工作负载分叉到单独的进程,以便您的节点 Web 服务器可以保持响应。我怀疑您并没有真正的全 CPU 工作负载要做,而且您只是有一个误解,但如果是这样,节点就不能在进程中做这些事情。 这就是我要找的东西:分叉。问题是,在一个响应速度非常快的服务器中,我想在它被烘焙后立即向客户端提供响应。响应时间是一个关键点:如果响应时间超出预期,客户可以拒绝响应。所以我想回应一下,然后在数据库上做一些工作。当我们谈到数千个 qps 时,这是最关键的,所以这里涉及到分叉甚至平衡。我原以为process.nextTick(f) 会成功,我也会尝试setTimeout(f,0) 好吧,同样,如果您的工作负载是 CPU 密集型的,process.nextTicksetTimeout 都不可行。您必须使用child_process 模块来生成单独的工作进程——单独的操作系统进程是必不可少的。或者您必须使用某种类型的工作负载队列系统,例如 zeromq、rabbitmq 或类似的。 你能提供一个分叉的工作样本吗?目前,我在尝试从child 向孩子发送 hello: "world" 消息时收到“无法写入 IPC 通道”的消息

以上是关于Node.js / express:立即响应客户端请求并在 nextTick 中继续任务的主要内容,如果未能解决你的问题,请参考以下文章

从 node.js/express.js 中的多个异步源构建对象

node.js 和 express:如何等待 udp 响应

如何在 node.js + Express 中同时支持 json 和 jsonp 响应?

Node.js + Express 接口请求(GETPOSTPUT)事例

Node.js - Express.js JWT 总是在浏览器响应中返回一个无效的令牌错误

在单个 Node.js + Express 响应中发送图像和 JSON 数据