如何在Heroku上使用Node.js通信Web和Worker dynos?

37

Web Dynos可以处理HTTP请求

Worker Dynos可以处理来自Web Dynos的作业。

但我不知道如何让Web DynosWorker Dynos相互通信。

例如,我想通过Web Dynos接收HTTP请求

将其发送到Worker Dynos

处理该作业并将结果发送回Web Dynos

在Web上显示结果。

在Node.js中是否可能实现这一点?(使用RabbitMQ或Kue等)?

我在Heroku文档中找不到示例。

还是应该在Web Dynos中实现所有代码,并仅扩展Web Dynos

2个回答

41
正如后台作业和排队的高级文章建议的那样,您的Web dynos需要通过中间机制(通常是队列)与工作dynos通信。
为了实现您希望做的事情,请按照以下一般步骤操作:
  • Web dyno接收Web请求
  • Web dyno将作业添加到队列中
  • Worker dyno从队列中接收作业
  • Worker dyno执行作业,并将增量进度写入共享组件
  • 浏览器端轮询从Web dyno请求作业状态
    • Web dyno查询后台作业的共享组件以获取进度并将状态发送回浏览器
  • Worker dyno完成执行作业并在共享组件中标记其为已完成
  • 浏览器端轮询从Web dyno请求作业状态
    • Web dyno查询共享组件以获取后台作业的进度并将已完成状态发送回浏览器
至于实际实现,我对Node.js中最好的库不太熟悉,但是将这个过程粘合在一起的组件可以作为Heroku上的附加组件获得。
队列: AMQP是一个得到很好支持的队列协议,而CloudAMQP附加组件可以作为Web和worker dynos之间的消息队列。
共享状态: 您可以使用其中一个Postgres附加组件来共享正在处理的作业的状态或者使用更高性能的组件,例如Memcache或者Redis
因此,总结一下,在Heroku上,您必须使用中间附加组件在dynos之间进行通信。虽然这种方法涉及一些工程工作,但结果是一个正确分离和可扩展的架构。

我有另一个关于这个问题的问题。当我使用AMQP时,你如何确保每个作业的处理都由一个worker dyno处理而不是重复?对我来说,AMQP类似于TCP Socket,广播事件并侦听事件然后执行某些操作。如果发生了"enqueue"事件,多个worker dynos将响应"enqueue"事件并尝试同时"dequeue"事件。我该如何解决这个问题? - jwchang
1
虽然队列行为因每个队列和客户端库而异,但默认行为通常是 广播的。因此,默认情况下,当消息从队列中被消耗时,它会被第一个到达的接收者消耗,并从队列中删除。 - Ryan Daigle
1
在AMQP中,您有交换机来发布消息,还有队列从中获取消息,然后您之间有“绑定”,它将消息从一个交换机路由到一个或多个队列。如果在交换机和队列之间只有一个绑定(这是默认值),则保证每个订阅者仅收到唯一的消息。 - Carl Hörberg
此外,AMQP 还有其他很好的优点,比如顺序保证,以及消息持久化、高可用性(镜像)队列等功能。(声明,我拥有 CloudAMQP) - Carl Hörberg

-4
据我所知,Heroku没有提供一种通信方式,因此您将不得不自己构建。为了使用Node与另一个进程进行通信,您可能需要手动处理该进程的stdin/out/err,类似于以下内容:
var attachToProcess = function(pid) {
    return {
        stdin: fs.createWriteStream('/proc/' + pid + '/fd/0'),
        stdout: fs.createReadStream('/proc/' + pid + '/fd/1'),
        stderr: fs.createReadStream('/proc/' + pid + '/fd/2')
    };
};

var pid = fs.readFile('/path/to/worker.pid', 'utf8', function(err, pid) {
    if (err) {throw err;}
    var worker = attachToProcess(Number(pid));
    worker.stdin.write(...);
});

然后,在您的工作进程中,您将不得不将pid存储在该pid文件中:

fs.writeFile('/path/to/worker.pid', process.pid, function(err) {
    if (err) {throw err;}
});

我实际上还没有测试过这些内容,所以可能需要一些工作和改进,但我认为基本思路是清晰的。

编辑

我刚刚注意到你也标记了“redis”,并且想补充说明一下,你也可以使用redis pub/sub在各个进程之间进行通信,如node_redis readme中所解释的那样。


8
Heroku Dynos是虚拟化的,这意味着即使在同一个应用程序中,它们也不共享同一文件系统。因此,从一个Dyno通过进程ID与另一个Dyno进行通信是行不通的。 - Ryan Daigle
@RyanDaigle 是的,我也觉得可能会有一些问题。不过关于 Redis 的想法仍然是有效的。 - kbjr
使用Redis作为中介(或其他队列库)是正确的方法。 - Ryan Daigle

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接