【问题标题】:Using the result of concurrent Rails & Sidekiq jobs使用并发 Rails 和 Sidekiq 作业的结果
【发布时间】:2018-06-21 19:30:26
【问题描述】:

Sidekiq 将在我们的场景中运行 25 个并发作业。我们需要得到一个整数作为每个作业的结果,并将所有结果汇总在一起。在这种情况下,我们正在查询外部 API 并返回计数。我们想要所有 API 请求的总数。

Report 对象存储最终总数。 Postgresql 是我们的数据库。

在每个作业结束时,我们会使用找到的其他记录来增加报告。

Report.find(report_id).increment(:total, api_response_total)

这是跟踪运行总数的好方法吗?会有 Postgresql 并发问题吗?有更好的方法吗?

【问题讨论】:

  • increment 不应该导致并发问题,在 sql 级别,它使用COALESCE(total, 0) + api_response_total 自动更新。只有当您手动添加然后保存对象时,才会出现竞争条件。

标签: ruby-on-rails postgresql concurrency sidekiq


【解决方案1】:

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 中)。在回调中,只需使用列表并对其进行求和即可。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-01-30
    • 1970-01-01
    • 2021-01-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多