【问题标题】:How do I avoid a simple race condition in Rails?如何避免 Rails 中的简单竞争条件?
【发布时间】:2012-12-11 18:42:17
【问题描述】:

我有一个简单的竞争条件。我有一个网站,人们可以对照片进行投票,但最多允许 10 票。

当用户提交投票时,我会为该特定照片更新照片表中的 num_votes 列。我这样做是为了方便查找票数。

如何确保 vote.save 和 num_votes 更新发生在同一个事务中?

谢谢!

【问题讨论】:

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


    【解决方案1】:

    为了实现这一点,您必须使用某种锁定。基本上你有 3 个选项:乐观/悲观的 Rails 锁定和一些外部锁定后端(如 Redis::Lock)。

    如果这里不是真正的高性能,我个人会选择悲观锁定

    photo = Photo.find(photo_id)
    photo.with_lock do
      photo.num_votes += 1
      photo.save!
    end
    

    我还应该指出,坚持只包装递增的 num_votes 并保存到一个事务中并不能解决竞争条件。大多数 RDBMS 默认工作在读提交模式。这并不能阻止这种竞争条件。

    仅供参考,请参阅 PessimisticOptimistic 锁定参考

    【讨论】:

      【解决方案2】:

      如果它是一个简单的竞争条件,那么您应该将它作为一个竞争条件来解决。 尝试使用一些锁定机制。 Redis 很不错: redis locking for ruby

      RedisLocker.new('vote_#{@photo.id}').run! { @photo.vote }
      
      # ... photo model
      def vote
        if num_votes <= 10   
          self.num_votes += 1
          save
        end
      end
      

      【讨论】:

        【解决方案3】:

        好吧,Rails/Postgres 支持事务。您可以在任何 ActiveRecord 模型上简单地声明一个:

        Photo.transaction do
         Vote.create(:whatever)
         Photo.votes = thing
         Photo.save!
        end
        

        如果在事务块期间引发异常(例如,通过在无效模型上调用 .save!),事务将回滚,并且不会提交任何可能在其中发生的数据库更改(在这种情况下,投票记录没有被插入)。当然,您仍然需要抢救和处理异常。

        顺便说一句,将关联对象的数量存储在记录中以便于查找是一种非常常见的模式,称为计数器缓存,Rails 也支持这些 - 您可能想考虑正式将num_votes 设为计数器缓存(默认名称为photos.votes_count,但不是必需的)。不过,您可能仍希望事务检查它是否超出限制。

        【讨论】:

        • 我相信如果两个用户同时投票(并行交易),这不会解决竞争条件问题。 photo.num_votes 将包含无效值(将错误地增加 1,而不是 2)。将 postgresql 事务隔离模式设置为“可序列化”可以解决问题,但它是一个巨大的性能杀手
        • 这是一个公平的观点。我是在回答被问到的问题(“我怎样才能让这些事情在事务中发生”),而不是更普遍的竞争条件问题。
        【解决方案4】:

        你不需要明确的锁

        Photo.where(:id => photo_id).where('num_votes < 10').update_all('num_votes = num_votes+ 1')
        

        将更新该照片的投票数,但前提是投票数少于 10。您可以检查update_all 的返回值以查看是否实际更新了任何内容:返回值是更新的行数。如果更新失败,则不要创建投票(或者如果您已经创建了投票,则回滚事务)。

        乐观锁定使用类似的技术来检测并发更新的尝试:它对更新设置一个条件,以确保如果有人在您之前潜入其中,则不会发生任何事情,然后检查更新的行数。

        【讨论】:

          猜你喜欢
          • 2019-06-12
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2021-05-24
          • 1970-01-01
          • 1970-01-01
          • 2015-01-30
          相关资源
          最近更新 更多