rails 有 sidekiq,竞争条件
rails with sidekiq, race condition
在我的 rails (v 4.2.11.3) 应用程序中,我有一些遗留逻辑,当我创建一个新模型时,它会被创建然后更新几次,在同一次调用后有额外的数据.导致对数据库的三个单独提交。
我有一个提交后回调,用于启动一些使用 sidekiq 使数据与远程服务保持一致的异步作业。
现在发生了 sidekiq 排队 3 次,当作业同时开始时我遇到了竞争条件。
我将作业包装在转换中(在正在处理的实际对象上,我不想将整个模型包装在转换中),但这似乎不起作用,我调试了代码并看到不同的工作同时进入过渡期。
这是sidekiq作业中调用的方法,
def save_object(id)
object = Object.find(id)
object.transaction do
object.reload
byebug
if object.service_id.blank?
create_object(object)
else
update_object(object)
end
end
end
这是 byebug 日志,显示两个 sidekiq 作业同时被踢,并且它们都可以到达事务块内的 byebug 调用。
[23, 29] in /Users/giulio/my-app/app/services/object_saving_service.rb
23:
24: byebug
=> 25: if object.service_id.blank?
26: create_object(object)
27: else
28: update_object(object)
29: end
(byebug) th current
+ 1 #<Thread:0x00007fad30298dc8@/Users/giulio/.rvm/gems/ruby-2.3.8/gems/sidekiq-4.2.10/lib/sidekiq/util.rb:24 run> /Users/giulio/my-app/app/services/object_saving_service.rb:25
(byebug) object.service_id
nil
(byebug) n
[23, 29] in /Users/giulio/my-app/app/services/object_saving_service.rb
23:
24: byebug
=> 25: if object.service_id.blank?
26: create_object(object)
27: else
28: update_object(object)
29: end
(byebug) th current
+ 3 #<Thread:0x00007fad30299ef8@/Users/giulio/.rvm/gems/ruby-2.3.8/gems/sidekiq-4.2.10/lib/sidekiq/util.rb:24 run> /Users/giulio/my-app/app/services/object_saving_service.rb:25
(byebug) object.service_id
nil
(byebug)
我期望事务的工作方式是这些块中只有一个可以随时执行,所以第一个发现:object.service_id.nil? == true
并执行 create_object,而第二个发现object.service_id.nil? == false
并执行更新。
在上面的日志中,两次 object.service_id
都为零,并且都进行了 create_object 调用。生成竞争条件。
关于转换为何不起作用的任何想法?我怎样才能让它工作?
谢谢。
数据库事务不是锁,它们不保证代码不会被多个线程/进程同时访问。
在我看来,您的示例需要一个行锁 https://api.rubyonrails.org/v6.1.3/classes/ActiveRecord/Locking/Pessimistic.html。所以锁定(在你的例子中是对象)可能是要走的路。
object.with_lock do
object.reload
byebug
if object.service_id.blank?
create_object(object)
else
update_object(object)
end
end
end
在我的 rails (v 4.2.11.3) 应用程序中,我有一些遗留逻辑,当我创建一个新模型时,它会被创建然后更新几次,在同一次调用后有额外的数据.导致对数据库的三个单独提交。
我有一个提交后回调,用于启动一些使用 sidekiq 使数据与远程服务保持一致的异步作业。
现在发生了 sidekiq 排队 3 次,当作业同时开始时我遇到了竞争条件。
我将作业包装在转换中(在正在处理的实际对象上,我不想将整个模型包装在转换中),但这似乎不起作用,我调试了代码并看到不同的工作同时进入过渡期。
这是sidekiq作业中调用的方法,
def save_object(id)
object = Object.find(id)
object.transaction do
object.reload
byebug
if object.service_id.blank?
create_object(object)
else
update_object(object)
end
end
end
这是 byebug 日志,显示两个 sidekiq 作业同时被踢,并且它们都可以到达事务块内的 byebug 调用。
[23, 29] in /Users/giulio/my-app/app/services/object_saving_service.rb
23:
24: byebug
=> 25: if object.service_id.blank?
26: create_object(object)
27: else
28: update_object(object)
29: end
(byebug) th current
+ 1 #<Thread:0x00007fad30298dc8@/Users/giulio/.rvm/gems/ruby-2.3.8/gems/sidekiq-4.2.10/lib/sidekiq/util.rb:24 run> /Users/giulio/my-app/app/services/object_saving_service.rb:25
(byebug) object.service_id
nil
(byebug) n
[23, 29] in /Users/giulio/my-app/app/services/object_saving_service.rb
23:
24: byebug
=> 25: if object.service_id.blank?
26: create_object(object)
27: else
28: update_object(object)
29: end
(byebug) th current
+ 3 #<Thread:0x00007fad30299ef8@/Users/giulio/.rvm/gems/ruby-2.3.8/gems/sidekiq-4.2.10/lib/sidekiq/util.rb:24 run> /Users/giulio/my-app/app/services/object_saving_service.rb:25
(byebug) object.service_id
nil
(byebug)
我期望事务的工作方式是这些块中只有一个可以随时执行,所以第一个发现:object.service_id.nil? == true
并执行 create_object,而第二个发现object.service_id.nil? == false
并执行更新。
在上面的日志中,两次 object.service_id
都为零,并且都进行了 create_object 调用。生成竞争条件。
关于转换为何不起作用的任何想法?我怎样才能让它工作?
谢谢。
数据库事务不是锁,它们不保证代码不会被多个线程/进程同时访问。 在我看来,您的示例需要一个行锁 https://api.rubyonrails.org/v6.1.3/classes/ActiveRecord/Locking/Pessimistic.html。所以锁定(在你的例子中是对象)可能是要走的路。
object.with_lock do
object.reload
byebug
if object.service_id.blank?
create_object(object)
else
update_object(object)
end
end
end