为什么Resque将列数增加4-5而不是10?

时间:2012-04-20 22:37:52

标签: ruby-on-rails ruby-on-rails-3 resque

Foobar.find(1).votes_count返回0.

在rails控制台中,我正在做:

10.times { Resque.enqueue(AddCountToFoobar, 1) }

我的工作人员:

class AddCountToFoobar
  @queue = :low

  def self.perform(id)
    foobar = Foobar.find(id)
    foobar.update_attributes(:count => foobar.votes_count +1)
  end
end

我希望Foobar.find(1).votes_count10,但它会返回4.如果我再次运行10.times { Resque.enqueue(AddCountToFoobar, 1) },它会返回相同的行为。它仅将votes_count增加4,有时增加5。

任何人都能解释一下吗?

3 个答案:

答案 0 :(得分:3)

这是一种经典的竞赛条件。想象一下,只有2名工人存在,他们每个人都会运行一个增加投票的工作。想象一下以下序列。

  1. 工人1:加载foobar(投票数== 1)
  2. Worker2:加载foobar(投票数== 1,在单独的ruby对象中)
  3. 工人1:增加投票数(现在== 2)并保存
  4. 工人2:增加它的foobar副本(现在投票数== 2)并保存,覆盖工人1的行为
  5. 虽然每个工作人员分别运行1个更新工作,但计数仅增加1,因为他们都在自己的foobar副本上运行,而这个副本并不知道其他工作人员正在做的更改

    要解决此问题,您可以进行就地样式更新,即

    UPDATE foos SET count = count + 1
    

    或使用锁定活动记录支持的两种形式之一(悲观锁定和乐观锁定)

    前者有效,因为数据库确保您不会同时在同一行上进行并发更新。

答案 1 :(得分:0)

看起来ActiveRecord在Resque中不是线程安全的(或者更确切地说是redis,我猜)。 Here's a nice explanation

答案 2 :(得分:-1)

弗雷德里克说,你正在观察比赛情况。您需要从读取值时序列化对关键部分的访问并更新它。

我尝试使用悲观锁定:  http://api.rubyonrails.org/classes/ActiveRecord/Transactions/ClassMethods.html  http://api.rubyonrails.org/classes/ActiveRecord/Locking/Pessimistic.html

foobar = Foobar.find(id)
foobar.with_lock do
  foobar.update_attributes(:count => foobar.votes_count +1)
end