Rails中直接使用Sidekiq而不是Active Job的原因

187 阅读4分钟

如果你建立一个网络应用,你应该尽量减少响应每个用户的时间;一个快速的网站意味着一个快乐的用户。做到这一点的一个方法是在直接的网络请求之外异步运行长期运行的、可并行的、或潜在的慢速工作。这可能是发送电子邮件、计划清理、长期运行的计算,或任何使用外部API的工作。

Active Job是Rails中排队和运行后台工作的推荐方式。它为这个概念的一系列不同的适配器实现提供了一个封装。每个实现都利用了不同的底层技术,并有自己的优点和缺点。大多数的实现都存在于Active Job在Rails 4.2发布之前。

最流行的、经过实战检验的、高性能的异步框架之一是Sidekiq,它是多线程的,利用Redis进行排队存储。它是一个开源库,有两个付费层,可提供额外的高端功能。

而不是...

...使用Active Job定义你的作业。

class DoThingsInBackgroundJob < ApplicationJob
  queue_as :default

  def perform(an_active_record_object)
    an_active_record_object.do_things
  end
end

使用Active Job来定义你的作业:使用...

...直接使用Sidekiq。

class DoThingsInBackgroundJob
  include Sidekiq::Worker
  Sidekiq_options queue: "default"

  def perform(id)
    an_active_record_object = ActiveRecordObject.find_by(id: id)
    an_active_record_object.do_things
  end
end

为什么?

如果你不直接使用Sidekiq,你就会失去很多优势。

如果您在后台做大量的工作,按照通常的建议,排入许多快速运行的小作业,如果您直接使用Sidekiq,会使性能提高2-20倍。改进的程度将取决于你的具体设置。

如果你要枚举大量作业,你可以使用Sidekiq的批量枚举功能。在使用Sidekiq和Active Job时,这很难做到。

令人困惑的是,在作业失败的情况下有多级重试。Active Job有自己的重试机制,一旦完成,就会传递给Sidekiq自己的(独立的、不同的!)重试系统。当出错时,这是个很棘手的调试问题。

Active Job允许向#perform 方法传递一个Active Record对象,然后使用Global ID将其序列化为文本参数。这可以节省自己的查找工作,但如果在作业从队列中拉出之前删除了记录,就会引起错误。这种自动序列化也使Sidekiq作业的参数难以在Web仪表板上阅读。

如果你担心在没有Active Job包装的情况下将自己锁定在Sidekiq的依赖关系中,那就不要担心了。虽然比起交换你的主数据库,你还是很不可能把你的队列系统换成一个类似的有限的Active Job适配器。

如果你更换你的异步基础设施,你应该期待一个主要的迁移项目,而不是仅仅通过一个配置行切换出一个适配器。

为什么不呢?

直接使用Sidekiq时,您必须更多地考虑您的作业参数应该是什么,然后自己执行对象查找。Sidekiq只允许简单的值作为工作参数。

如果您已经通过Active Job使用Sidekiq,就没有必要改变您现有的所有作业。你不必非得使用其中一个,你可以同时定义和使用两个。这在管理上可能有点混乱,但这并不是一个糟糕的解决方案。当你需要提高性能和批量强化时,你可以直接使用Sidekiq作业。

如果你的工作量很轻,或者你在项目的早期,你可能根本就不需要Sidekiq(或者至少是它对Redis的依赖)。

Good JobQue(仅限postgres)或Delayed Job(任何SQL数据库)是广受好评的Active Job适配器。当使用这些选项之一时,你不需要运行一个额外的数据库基础设施。

建议

如果您在生产中使用Sidekiq,您应该购买专业版的许可证。在生产环境中,额外的可靠性使作业不会因崩溃而 "丢失",这就足够了,但还有其他额外的功能,包括作业删除(按作业ID和作业种类)和网络仪表板的增强。