简体   繁体   English

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

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

Sidekiq recommends that all jobs be idempotent (able to run multiple times without being an issue) as it cannot guarantee a job will only be run one time. Sidekiq建议所有作业都是幂等的(可以多次运行而不会出现问题),因为它不能保证一项作业只能运行一次。

I am having trouble understanding the best way to achieve that in certain cases. 在某些情况下,我无法理解实现此目标的最佳方法。 For example, say you have the following table: 例如,假设您有下表:

User id email balance 用户ID电子邮件余额

The background job that is run simply adds some amount to their balance 运行的后台作业只会使他们的余额增加一些

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

If this job is run more than once their balance will be incorrect. 如果此作业多次运行,则其余额将不正确。 What is best practice for something like this? 这样的最佳实践是什么?

If I think about it a potential solution I can come up with is to create a record before scheduling the job that is something like 如果我考虑一下,我可以想到的一个潜在解决方案是在安排工作之前创建一条记录,例如

PendingBalanceAdjustment user_id balance_adjustment PendingBalanceAdjustment user_id balance_adjustment

When the job runs it will need to acquire a lock for this user so that there's no chance of a race condition between two workers and then will need to both update the balance and delete the record from pending balance adjustment before releasing the lock. 作业运行时,它将需要为该用户获取一把锁,这样就不会在两个工作人员之间出现竞争状况,然后需要在释放该锁之前更新余额并从未决余额调整中删除该记录。

The job then looks something like this? 那么工作看起来像这样吗?

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

This seems to solve both 这似乎解决了

a) Race condition between two workers taking the job at the same time (though you'd think Sidekiq could guarantee this already?) b) A job being run multiple times after running successfully a)两名同时工作的工人之间的种族状况(尽管您认为Sidekiq可以保证做到这一点?)b)一项工作成功运行后被多次运行

Is this pattern a good solution? 这是一个好的解决方案吗?

You're on the right track; 您走在正确的轨道上; you want to use a database transaction, not a redis lock. 您要使用数据库事务,而不是redis锁。

I think you're on the right track too but you're solution might be overkill since I don't have full knowledge of your application. 我认为您也走在正确的道路上,但由于我对您的应用程序并不了解,因此您的解决方案可能会过大。

BUT, a simpler solution would simply be to have a flag on you User model like balance_updated:datetime . 但是,一个更简单的解决方案将只是在您的User模型上带有一个标志,例如balance_updated:datetime So, you could check that before updating. 因此,您可以在更新之前进行检查。

As Mike mentions using a Transaction block should ensure it's thread safe. 正如Mike提到的,使用Transaction块应确保其线程安全。

In any case, to answer your question more generally... having an updated_ column is usually good enough to start with, and then if it gets complicated you can move this stuff to another model. 无论如何,要更笼统地回答您的问题...拥有一个updated_列通常足以开始,然后,如果它变得复杂,则可以将其移至另一个模型。

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

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