2

Foobar.find(1).votes_count0を返します。

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_countしますが10、代わりに4を返します。10.times { Resque.enqueue(AddCountToFoobar, 1) }もう一度実行すると、同じ動作が返されます。増分votes_countは4、場合によっては5だけです。

誰かがこれを説明できますか?

4

3 に答える 3

3

これは、典型的な競合状態のシナリオです。ワーカーが 2 つしか存在せず、それぞれが投票インクリメント ジョブの 1 つを実行するとします。次のシーケンスを想像してください。

  1. Worker1: load foobar(投票数 == 1)
  2. Worker2: foobar を読み込む (投票数 == 1、別の ruby​​ オブジェクトで)
  3. ワーカー 1: 投票数を増やして (現在 == 2)、保存します
  4. ワーカー 2: foobar のコピーをインクリメントし (現在の投票数 == 2)、保存し、ワーカー 1 が行ったことを上書きします

2 人のワーカーがそれぞれ 1 つの更新ジョブを実行しましたが、カウントは 1 しか増加しませんでした。これは、2 つのワーカーがそれぞれの foobar のコピーを操作していたため、他のワーカーが行っていた変更を認識していなかったからです。

これを解決するには、インプレース スタイルの更新を行うことができます。

UPDATE foos SET count = count + 1

またはアクティブなレコードをロックする 2 つの形式 (悲観的ロックと楽観的ロック) のいずれかを使用します。

前者が機能するのは、データベースが同時に同じ行を同時に更新しないことを保証するためです。

于 2012-04-21T03:18:01.773 に答える
0

ActiveRecordはResqueではスレッドセーフではないようです(つまり、redisだと思います)。これがいい説明です。

于 2012-04-20T22:44:28.477 に答える
-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
于 2012-04-21T04:26:56.080 に答える