【发布时间】:2014-07-08 02:44:20
【问题描述】:
我有一个复杂的系统,其中涉及许多 Resque 工人、作业和监控过程。这些作业具有父子依赖关系,并且它们通过一系列状态运行(使用state-machine),这就是监视过程的原因。我们依靠数据库状态来确保跨进程跟踪是同步的。
这是一个粗略的想法:
class ParentMonitor < ActiveRecord::Base
has_many children, class: ChildMonitor
state_machine :state, initial: :work_needed do
event :succeed do
transition :work_needed => :work_succeeded
end
event :fail do
transition :work_needed => :work_failed
end
end
def child_transition
return if children.any? { |child| child.work_needed? }
if children.any? { |child| child.work_succeeded? }
succeed
else
fail
end
end
end
class ChildMonitor < ActiveRecord::Base
belongs_to: owner, class: ParentMonitor
state_machine :state, initial: :work_needed do
event :succeed do
transition :work_needed => :work_succeeded
end
after_transition :to => :work_succeeded, :do => :notify_owner
event :fail do
transition :work_needed => :work_failed
end
after_transition :to => :work_failed, :do => :notify_owner
end
def notify_owner
owner.child_transition
end
end
发生的情况是,对于最初的几个这样的工作(比如几百个或两个),即使所有子级都处于 work_succeeded 或 work_failed,但 ParentMonitors 仍处于 work_needed 状态.通过跟踪和测试,我确定每次调用ParentMonitor#child_transition 时,处于“需要工作”状态的孩子列表会连续减少,直到某个时候它会加载数据库并将所有孩子的值替换为“需要工作”。尽管有些之前已经完成。
此外,在它突然开始记录更新之前,我在这些最初的几个孩子的日志文件中看不到任何 UPDATE 日志。该日志记录与它似乎重置其所有子项的状态同时进行。
这让我觉得由于某些缓存状态,所有更改都发生在内存中,但我在整个过程中添加了reload、save 和find 调用,它们似乎没有影响更改。我也尝试将这些调用包装在 uncache 中,但这没有帮助。
【问题讨论】:
-
将持久性包装在
ActiveRecord.transaction中,这将确保任何失败都会导致所有事务回滚,并且您将始终处于可预测的状态 -
我认为您可能想要获取互斥锁并在更经典的多线程场景中执行一些此类操作,以检查两个不同的作业是否相互干扰,这很简单,但效果很好。
-
@bjhaid 我知道 ActiveRecord 已经在 added transactions 周围像
#save这样的东西。我将尝试将其包装在另一个事务中,以防提交它强制数据库写入。 -
@MikeH-R 感谢您的建议,我将更多地研究互斥体模式,但是 postgresql 是否通过锁定记录来处理来自多个进程的干扰?你能详细说明我将如何应用互斥锁吗?
-
@jwadsack 引用您发布的链接
As a rule, transactions are only needed when changes to multiple records must succeed as a single unit
标签: ruby-on-rails ruby activerecord resque