delay_job:每个租户一次一份工作?

时间:2019-03-11 21:46:37

标签: ruby-on-rails scope multi-tenant delayed-job

我有一个多租户-Rails应用程序,其中有多个延迟工作的工人。

为了避免重叠特定于承租人的工作,我想将工人彼此分开,使得每个人一次只能执行一个特定于承租人的任务。

我考虑过使用(命名)队列列,并添加“ tenant_1”,“ tenant_2”等。不幸的是,在配置过程中必须对队列进行命名,因此对于许多租户来说,这种原理不够灵活。

有没有一种方法可以自定义delay_job选择下一个任务的方式?还有另一种定义范围的方法吗?

2 个答案:

答案 0 :(得分:2)

您最好的选择可能是旋转一个实现分布式锁的自定义解决方案-本质上,工作人员都可以正常运行并从通常的队列中拉出,但是在与另一个系统(Redis,RDBMS,API等)进行工作检查之前确认没有其他工人正在为该租户执行工作。如果该租户没有工作,则为有问题的租户设置锁,然后进行工作。如果租户被锁定,请勿执行该工作。您需要调用许多实现细节,例如是否继续尝试其他作业,将作业重新排入队列的后面,是否将其视为失败并将其绑定到重试限制或执行其他操作完全。这是开放式的,因此我将详细信息留给您,但是这里有一些提示:

  • 继承将是您的朋友;在基本作业上定义此行为,并从其上继承您希望您的工人运行的作业。如果您对即将出现的某些作业有“特殊”情况,而又不破坏其他所有情况,这也可以使您自定义行为。
  • 假设您没有通过ActiveJob运行(因为未提及),请阅读delayed_job钩子:https://github.com/collectiveidea/delayed_job/#hooks-它们可能是合适和/或有用的工具
  • 熟悉悲观和乐观锁定策略的一些差异和折衷-这个答案是一个很好的起点:Optimistic vs. Pessimistic locking
  • 阅读有关分布式锁概念的一般做法,以便您可以自己选择最佳工具和策略(不必是一个疯狂的复杂解决方案,数据库中存储租户标识符的简单表是足够,但您需要考虑故障情况-例如,如何管理被放弃的锁)

认真考虑不要这样做;系统正常运行真的需要严格吗?如果是这样,则可能表明您的数据模型存在潜在的缺陷,或者表明您如何围绕该数据构建转换。考虑数据操作时,努力在应用程序中使用ACIDity,这样可以避免很多此类问题。有一个原因是它不是后台求职者通常无法使用的“开箱即用”功能。如果存在潜在的缺陷,它不仅会在这个问题上咬您,而且还会在其他方面咬您-保证!

答案 1 :(得分:0)

如果您要避免让两个不同的工人在同一个租户上工作,那么这是一个糟糕的设计选择。有东西在闻。首先解决。但是,如果您希望下面的相同类型的工作实例在不同的租户上工作是最简单的解决方案。这些关系是我的假设。

ExpiredOrderCleaner = Struct.new(:tenant_id) do
  def perform
    Order.where(tenant_id: tenant_id).expired.delete_all
  end
end

Tenant.each do |tenant|
  Delayed::Job.enqueue ExpiredOrderCleaner.new(tenant.id)
end

这将为每个租户创建唯一的作业。单个工作人员实例将在特定租户上工作。但是,同一租户可以有其他类型的工作。这应该是不错的。如果您需要更小的作用域,只需为worker传递更多的参数,并在查询中使用,并使用数据库事务来避免冲突。

这些best practices对任何后台工作者都是正确的。

  • 使您的工作具有幂等性和交易性 意味着您的工作可以安全地执行多次
  • 拥抱并发性 设计工作,以便您可以并行运行许多工作

如果使用apartment gem和active job包装器,您的工作会容易得多。请参阅其中的示例文件。