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

Tom*_*ond 2 ruby-on-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
Run Code Online (Sandbox Code Playgroud)

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

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

Gly*_*oko 5

您可以为此使用线程

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

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
Run Code Online (Sandbox Code Playgroud)

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

其次,红宝石线是绿色线。(假设您使用的是 MRI Ruby,而不是 Rubinius 或 JRuby。)这意味着,如果您只使用 ruby​​ 线程来完成某些任务的加速。幸运的是,Web 请求是不会阻塞 ruby​​ 线程的事情之一,这意味着上述解决方案将同时发出所有请求。您应该会看到此解决方案的加速。

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

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

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