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