ActiveRecord:处理工作人员之间的数据库竞争

问题描述:

我有一个运行在PostgreSQL 9.0之上的Rails 3项目。ActiveRecord:处理工作人员之间的数据库竞争

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

问题IndexArtistJob将平行运行。因此,两个用户可能会同时请求添加相同的Artist。两个用户都应该将Artist添加到他们的收藏中,但只有一个Artist应该在本地数据库中。

Artist模型的相关部分:

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它:

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类定义发现,希望最好。如果发生冲突,我希望较慢的工作人员放弃他们所支持的Artist刚刚插入的工作,并将Artist添加到指定的用户。

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

我是否正确地做到了这一点?如果是这样,是否有更好的方法来做到这一点?我觉得围绕异常进行构造会强调这个问题是并发性问题,因此有点微妙。

听起来像你可以使用简单的排队机制。你可以做到这一点使用一个数据库表:

  1. 当一个“前端”线程发现失踪的艺术家,把它写作者姓名与身份“等待”表(对艺人的唯一索引所以这只能发生一次)。

  2. 同时后台线程/进程位于一个循环和查询表的新工作:
    一)开始交易
    B)找到状态第一艺术家=“等待”
    三)更新艺术家地位“处理”
    d)结束事务

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

  4. 完成后,后台线程从表格中删除Artist。

使用此方法,您可以运行多个后台线程来提高艺术家索引的并发性。

也看一些像beanstalk来管理这个过程。见http://railscasts.com/episodes/243-beanstalkd-and-stalker

+0

我应该提到我正在研究SimpleWorker或delayed_job/Resque队列。你的解决方案使用队列作为一种互斥体,这非常聪明,但我不确定我是否可以再次发明*。 – 2011-05-03 01:58:11

+0

完全同意......世界有足够的*。 – scaganoff 2011-05-03 09:02:09