increment 不应该导致并发问题,在 sql 级别,它使用COALESCE(total, 0) + api_response_total 自动更新。只有当您手动添加然后保存对象时,才会出现竞争条件。
report = Report.find(report_id)
report.total += api_response_total
report.save # NOT SAFE
注意:即使使用increment!,Rails 级别的值也可能是陈旧的,但在数据库级别它将是正确的:
# suppose initial `total` is 0
report = Report.find(report_id) # Thread 1 at time t0
report2 = Report.find(report_id) # Thread 2 at time t0
report.increment!(:total) # Thread 1 at time t1
report2.increment!(:total) # Thread 2 at time t1
report.total #=> 1 # Thread 1 at time t2
report2.total #=> 1 # Thread 2 at time t2
report.reload.total #=> 2 # Thread 1 at time t3, value was stale in object, but correct in db
这是跟踪运行总数的好方法吗?会有 Postgresql 并发问题吗?有更好的方法吗?
我更喜欢使用Sidekiq Batches 来执行此操作。它允许您运行一批作业并为批处理分配一个回调,该回调在处理完所有作业后执行。示例:
batch = Sidekiq::Batch.new
batch.description = "Batch description (this is optional)"
batch.on(:success, MyCallback, :to => user.email)
batch.jobs do
rows.each { |row| RowWorker.perform_async(row) }
end
puts "Just started Batch #{batch.bid}"
我们需要得到一个整数作为每个作业的结果,并将所有结果汇总在一起。
请注意 Sidekiq 作业 doesn't do anything with the returned value 并且该值已被 GC 处理并被忽略。因此,在上述批处理策略中,回调中不会有作业数据。您可以量身定制该解决方案。例如,在 redis 中有一个LIST,其键为批处理 id,并推送每个完整作业的值(在perform 中)。在回调中,只需使用列表并对其进行求和即可。