[英]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.