ActiveRecord:处理工人之间的DB比赛
我有一个运行在PostgreSQL 9.0之上的Rails 3项目。
使用案例:用户可以按名称请求关注Artists
。 为此,他们向REST资源提交名称列表。 如果我在本地集合中找不到名称的Artist
,请查阅last.fm以获取有关它们的信息,并在本地缓存该信息。 此过程可能需要一些时间,因此会将其委派给名为IndexArtistJob
的后台作业。
问题 : IndexArtistJob
将并行运行。 因此,两个用户可能同时请求添加相同的Artist
。 两个用户都应该将Artist
添加到他们的集合中,但只有一个Artist
应该在本地数据库中结束。
Artist
模型的相关部分是:
require 'services/lastfm' class Artist 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
添加到指定的用户。
我知道Railsvalidation器不能替代数据库级别的实际数据完整性检查。 为了解决这个问题,我在Artist表的小写名称字段中添加了一个唯一索引来处理这个(并用于搜索)。 现在,如果我正确理解文档,AR的关联集合会对正在添加的项(本例中为Artist
)和事务中的基础集合进行更改。 但我无法保证将添加Artist
。
我这样做了吗? 如果是这样,有没有更好的方法呢? 我觉得围绕exception构建它会强调这个问题是并发性的问题,因此有点微妙。
听起来你可以使用简单的排队机制。 您可以使用数据库表执行此操作:
-
当“前端”线程发现丢失的艺术家时,让它将艺术家名称写入状态为“等待”的表(在艺术家名称上有唯一索引,因此这只能发生一次)。
-
同时,后台线程/进程位于循环中并查询表中的新作业:
a)开始交易
b)找到状态=“等待”的第一位艺术家
c)将艺术家状态更新为“处理”
d)结束交易 -
后台线程然后索引Artist。 没有其他人会尝试,因为他们可以看到状态为“处理”。
-
完成后,后台线程将从表中删除Artist。
使用此方法,您可以运行多个后台线程以增加Artist索引的并发性。
还要看看像beanstalk这样的东西来管理这个过程。 请参阅http://railscasts.com/episodes/243-beanstalkd-and-stalker 。