Rails 在控制器中异步执行任务



当调用Controller中的路由时,我需要执行三个任务。现在我的代码看起来像这样(缩写):

def set_quotas
TaskOne.new().ex
TaskTwo.new().ex
TaskThree.new().ex

quotas = @user.quotas
render json: quotas, status: 200, each_serializer: Api::V1::QuotaSerializer
end

每个任务按顺序运行。但是,其中一些人呼叫外部服务。因此,完成此通话所需的总时间最终为 4-8 秒,我们真的想加快速度。

我想做的是同时运行所有三个任务,但等到每个任务完成后再渲染json响应。在 Rails 中实现此目的的最佳方法是什么?

你可以为此使用线程。

这是一个简单的解决方案:

def set_quotas
[
Thread.new { TaskOne.new().ex },
Thread.new { TaskTwo.new().ex },
Thread.new { TaskThree.new().ex }
].each &:join
quotas = @user.quotas
render json: quotas, status: 200, each_serializer: Api::V1::QuotaSerializer
end

但是,在 Ruby 中使用并发时需要注意几点。首先也是最重要的一点,当您使用线程时,您将受到并发带来的所有复杂性的影响。预计头痛。并发性可以非常快速地混淆简单的任务。例如,对于你的问题,你需要确保你的三个任务是真正独立的。一个任务是否依赖于另一个任务的输出?有副作用吗?比如说,是否将后续作业所依赖的数据库写入一些数据?如果是这种情况,那么上述方法将不起作用,您需要弄清楚如何适应它们的特定相互依赖关系。

其次,红宝石线是绿色线。(假设你使用的是MRI Ruby,而不是Rubinius或JRuby。这意味着,如果您只能通过 ruby 线程为某些任务获得加速。幸运的是,Web 请求是不会阻塞 ruby 线程的东西之一,这意味着上述解决方案同时发出所有请求。您应该会看到此解决方案的加速。

最后,这在某种程度上特定于您的用例,当您的应用程序调用第三方时,通常最佳做法是将该内容降级给工作人员。对第三方的调用容易出错,并且向客户端返回 400(500,如果您不小心处理错误!)很糟糕。最好将这项工作放入队列中,并给客户端一个响应,基本上是"收到请求,稍后再做"。

鉴于所有这些,如果您发现需要比原生红宝石线程更强大的东西,请查看并发红宝石。

@AbM的回答提到了Resque/DJ。有关更通用的解决方案,请查看ActiveJob。对于某些(但不是全部)变体,请参阅活动作业适配器列表。