ActiveRecord:处理工人之间的DB比赛

时间:2011-04-29 17:57:10

标签: ruby-on-rails activerecord concurrency

我在PostgreSQL 9.0上运行了一个Rails 3项目。

使用案例:用户可以按名称请求关注Artists。为此,他们向REST资源提交名称列表。如果我在本地集合中找不到名称Artist,请查阅last.fm以获取有关它们的信息,并在本地缓存该信息。此过程可能需要一些时间,因此会将其委派给名为IndexArtistJob的后台作业。

问题IndexArtistJob将并行运行。因此,两个用户可能会请求同时添加相同的Artist。两个用户都应该在其集合中添加Artist,但只有一个Artist应该在本地数据库中结束。

Artist模型的相关部分是:

require 'services/lastfm'

class Artist < ActiveRecord::Base
  validates_presence_of :name
  validates_uniqueness_of :name, :case_sensitive => false

def self.lookup(name)
  artist = Artist.find_by_name(name)
  return artist if not artist.nil?

  info = LastFM.get_artist_info(name)
  return if info.nil?

  # Check local DB again for corrected name.
  if name.downcase != info.name.downcase
    artist = Artist.find_by_name(info.name)
    return artist if not artist.nil?
  end

  Artist.new(
      :name => info.name,
      :image_url => info.image_url,
      :bio => info.bio
  )
  end
end

IndexArtistJob类定义为:

class IndexArtistJob < Struct.new(:user_id, :artist_name)
  def perform
    user = User.find(user_id)

    # May return a new, uncommitted Artist model, or an existing, committed one.
    artist = Artist.lookup(artist_name)
    return if artist.nil?

    # Presume the thread is pre-empted here for a long enough time such that
    # the work done by this worker violates the DB's unique constraint.
    user.artists << artist

  rescue ActiveRecord::RecordNotUnique  # Lost race, defer to winning model
    user.artists << Artist.lookup(artist_name)
  end
end

我在这里要做的是让每个工作人员提交它找到的新Artist,希望最好。如果确实发生冲突,我希望较慢的工作人员放弃他们所做的工作而转而使用刚刚插入的Artist,并将Artist添加到指定的用户。

我知道Rails验证器不能替代数据库级别的实际数据完整性检查。为了解决这个问题,我在Artist表的小写名称字段中添加了一个唯一索引来处理这个(并用于搜索)。现在,如果我正确理解文档,AR的关联集合会对正在添加的项(在本例中为Artist)和事务中的基础集合进行更改。但我不能保证会添加Artist

我这样做是否正确?如果是这样,有没有更好的方法呢?我觉得围绕异常构建它会强调问题是并发性的问题,因此有点微妙。

1 个答案:

答案 0 :(得分:1)

听起来你可以使用简单的排队机制。您可以使用数据库表执行此操作:

  1. 当“前端”线程发现缺少艺术家时,让它将艺术家名称写入状态为“等待”的表格(艺术家名称上有唯一索引,因此这只能发生一次)。 / p>

  2. 同时,后台线程/进程位于循环中并查询表中的新作业:
    a)开始交易
    b)找到状态=“等待”的第一位艺术家 c)将艺术家状态更新为“处理”
    d)结束交易

  3. 后台线程然后索引艺术家。没有其他人会尝试,因为他们可以将状态视为“处理”。

  4. 完成后,后台线程会从表格中删除艺术家。

  5. 使用此方法,您可以运行多个后台线程以增加Artist索引的并发性。

    还要查看beanstalk之类的东西来管理这个过程。见http://railscasts.com/episodes/243-beanstalkd-and-stalker