繁体   English   中英

Sidekiq Ruby on Rails背景工作的幂等设计

[英]Idempotent Design with Sidekiq Ruby on Rails Background Job

Sidekiq建议所有作业都是幂等的(可以多次运行而不会出现问题),因为它不能保证一项作业只能运行一次。

在某些情况下,我无法理解实现此目标的最佳方法。 例如,假设您有下表:

用户ID电子邮件余额

运行的后台作业只会使他们的余额增加一些

def perform(user_id, balance_adjustment)
 user = User.find(user_id)
 user.balance += balance_adjustment
 user.save
end

如果此作业多次运行,则其余额将不正确。 这样的最佳实践是什么?

如果我考虑一下,我可以想到的一个潜在解决方案是在安排工作之前创建一条记录,例如

PendingBalanceAdjustment user_id balance_adjustment

作业运行时,它将需要为该用户获取一把锁,这样就不会在两个工作人员之间出现竞争状况,然后需要在释放该锁之前更新余额并从未决余额调整中删除该记录。

那么工作看起来像这样吗?

def perform(user_id, balance_adjustment_id)
  user = User.find(user_id)
  pba = PendingBalanceAdjustment.where(:balance_adjustment_id => balance_adjustment_id).take
  if pba.present?
    $redis.lock("#{user_id}/balance_adjustment") do
      user.balance += pba.balance_adjustment
      user.save
      pba.delete    
    end
  end
end

这似乎解决了

a)两名同时工作的工人之间的种族状况(尽管您认为Sidekiq可以保证做到这一点?)b)一项工作成功运行后被多次运行

这是一个好的解决方案吗?

您走在正确的轨道上; 您要使用数据库事务,而不是redis锁。

我认为您也走在正确的道路上,但由于我对您的应用程序并不了解,因此您的解决方案可能会过大。

但是,一个更简单的解决方案将只是在您的User模型上带有一个标志,例如balance_updated:datetime 因此,您可以在更新之前进行检查。

正如Mike提到的,使用Transaction块应确保其线程安全。

无论如何,要更笼统地回答您的问题...拥有一个updated_列通常足以开始,然后,如果它变得复杂,则可以将其移至另一个模型。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM