Sidekiq建议所有工作都是幂等的(能够多次运行而不会出现问题),因为它不能保证作业只能运行一次。
我无法理解在某些情况下实现这一目标的最佳方法。例如,假设您有下表:
用户 ID 电子邮件 平衡
运行的后台作业只会为其余额增加一些金额
def perform(user_id, balance_adjustment)
user = User.find(user_id)
user.balance += balance_adjustment
user.save
end
如果此作业多次运行,其余额将不正确。对于这样的事情,最佳做法是什么?
如果我认为这是一个潜在的解决方案,我可以提出的是在安排工作之前创建一个记录,如
PendingBalanceAdjustment 用户身份 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)成功运行后多次运行的作业
这种模式是一个很好的解决方案吗?
答案 0 :(得分:2)
你走在正确的轨道上;你想使用数据库事务,而不是redis锁。
答案 1 :(得分:0)
我认为你也走在了正确的轨道上,但是因为我对你的应用程序没有完全的了解,你的解决方案可能过度杀伤。
但是,一个更简单的解决方案就是在User
模型上设置一个标记balance_updated:datetime
。所以,你可以在更新之前检查一下。
正如迈克提到使用Transaction块应该确保它的线程安全。
在任何情况下,更一般地回答你的问题......拥有一个updated_列通常足以开始,然后如果它变得复杂,你可以将这些东西移动到另一个模型。